TIL the different behaviors of time.After and time.Sleep in Go
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.