Implement a Sleep that returns early if its context.Context is cancelled
Write an interruptible version of time.Sleep. It must wait the full duration and return true, but return false immediately if the context is cancelled first.
Constraints:
- Do not leak the underlying timer when the context cancels before the duration elapses.
- Do not busy-wait or poll.
// Sleep waits d, or returns false early if ctx is cancelled.
func Sleep(ctx context.Context, d time.Duration) bool {
// your code here
}
Write the implementation.
select over two cases: case <-ctx.Done(): return false and case <-timer.C: return true. Don't use time.After, which leaks the underlying timer until it fires; instead t := time.NewTimer(d); defer t.Stop() so a cancellation reclaims the timer immediately.
- ✗Using
time.Sleepthen checking the context, which cannot return before the full duration elapses - ✗Using
time.Afterin the select, which leaks the timer until it fires when the context cancels first - ✗Adding an unnecessary goroutine when a single timer plus
ctx.Done()already covers both cases
- →Why does
time.Afterleak its timer, whiletime.NewTimerplusStopdoes not? - →Does calling
Stopon a timer that has already fired cause any problem here?
Solution
The select waits on two events at once: the timer firing and the context cancelling. Whichever happens first wins.
func Sleep(ctx context.Context, d time.Duration) bool {
t := time.NewTimer(d)
defer t.Stop() // reclaim the timer on any exit path
select {
case <-ctx.Done():
return false // cancelled before the deadline
case <-t.C:
return true // waited the full duration
}
}
Why NewTimer + Stop, not time.After
time.After(d) creates a *time.Timer under the hood but hands you no reference to it. If the context cancels before d, that timer still lives on the heap and in the runtime queue until it fires — a leak on every cancelled call. On a hot path (say, a loop with a small d and frequent cancellations) these timers pile up. time.NewTimer returns the *Timer, so defer t.Stop() reclaims it immediately on cancellation.
Calling t.Stop() on an already-fired timer is safe — it just returns false. There is no need to drain t.C here: we either already received from it (return true) or stop using it.