TIL the different behaviors of time.After and time.Sleep in Go

TIL the different behaviors of time.After and time.Sleep in Go
Photo by Artem Maltsev / Unsplash

While working on a simple monitoring library, I came across an issue of wanting to cancel a goroutine when it's context is cancelled, but the way I was using time.Sleep in my code meant that it would wait until the next tick of the interval to respect the context cancellation (because priority would be held up in the default block of my switch statement, and the <-ctx.Done() receive wouldn't happen until the next iteration of my for select loop.

The solution for me was to use a ticker and select over both the context cancel or the ticker, but in researching this solution I discovered that there is a fundamental difference in how goroutines handle time.Sleep and time.After.

time.Sleep will let the goroutine enter a "sleeping" sub-state, but time.After will crucially let the goroutine enter blocking state. This has subtle implications for thread handling that one should be aware of when using long-living goroutines (or really goroutines at all).

The difference is the function call time.Sleep(d) will let the current goroutine enter sleeping sub-state, but still stay in running state, whereas, the channel receive operation <-time.After(d) will let the current goroutine enter blocking state.
Quote from the StackOverflow answer that taught me this.