A common mistake is treating Done as a channel that receives a cancellation value. Context.Done returns a receive-only notification channel that is closed when cancellation or a deadline occurs. Channel closure notifies every consumer, so multiple goroutines can observe the same signal. section 8.6.4
Use select to make channel operations context-aware. A send or receive performed directly can block after cancellation; competing with ctx.Done lets the function stop instead of waiting indefinitely. Return ctx.Err() to distinguish cancellation from an expired deadline. section 8.6.4
func handler(ctx context.Context, ch chan Message) error {
for {
select {
case msg := <-ch: #1
// Do something with msg
case <-ctx.Done(): #2
return ctx.Err()
}
}
}
This handler keeps receiving messages until a message arrives or the context’s Done channel closes, then returns the cancellation reason. section 8.6.4
Protect each potentially blocking channel operation:
- Guard a send with select so cancellation can interrupt the operation. section 8.6.4
- Guard a receive with select for the same reason, returning ctx.Err() when Done closes. section 8.6.4
func f(ctx context.Context) error {
// ...
select { #1
case <-ctx.Done():
return ctx.Err()
case ch1 <- struct{}{}:
}
select { #2
case <-ctx.Done():
return ctx.Err()
case v := <-ch2:
// ...
}
}
The corrected function separately selects between context cancellation and completion of its send and receive operations. section 8.6.4

