What does this print? (AfterFunc on already-canceled and live contexts)
Question 290HardGo 1.22 to 1.25
func main() {
ctx, cancel := context.WithCancel(context.Background())
cancel()
done := make(chan struct{})
stop := context.AfterFunc(ctx, func() {
fmt.Println("callback ran")
close(done)
})
<-done
fmt.Println(stop())
ctx2, cancel2 := context.WithCancel(context.Background())
stop2 := context.AfterFunc(ctx2, func() { fmt.Println("never") })
fmt.Println(stop2())
fmt.Println(stop2())
cancel2()
time.Sleep(10 * time.Millisecond)
fmt.Println("end")
}
Output:
callback ran
false
true
false
end
- Registering on a context that is already done starts
fat once in a new goroutine. By the time we callstop(),fhas started, so it returns false. - On a live context, the first
stop2()removes the registration and returns true. The second call returns false because it was already stopped. After that,cancel2()has nothing to run, so "never" is never printed. falways runs on its own goroutine, never on the goroutine that callscancel. So a slow or blocking callback cannot stall the canceler, but you need your own synchronization (thedonechannel here) to know when it has finished.
What the interviewer is looking for: that false is ambiguous (already started, or already stopped), and that stop never waits for f.
More on Context
- Q288Is a context safe for concurrent use? What happens if you read ctx.Done() from many goroutines, or call cancel concurrently?
- Q289How should a library expose context in its API, and how do you add context support to an existing API without breaking callers?
- Q291What does this print? (Cause across WithoutCancel)
- Q292How do you make a range-over-func iterator (iter.Seq) respect context cancellation, and why is it better than a channel-based generator?
- Q293How do semaphore.Weighted and rate.Limiter use context, and what subtle bugs appear with them?