Which newer context features matter for concurrent code: WithCancelCause, AfterFunc, WithoutCancel?
Question 249MediumGo 1.22 to 1.25
WithCancelCause(1.20):cancel(err)records why the context was cancelled, andcontext.Cause(ctx)returns that error whilectx.Err()is stillCanceled. It makes logs from fan-out work far more useful.WithTimeoutCauseandWithDeadlineCausearrived in 1.21.AfterFunc(1.21): runs a function in its own goroutine when the context is done, and returns astopfunc. It replaces the hand-writtengo func(){ <-ctx.Done(); ... }()goroutine that leaks when the context is never cancelled. It is handy for waking async.Condor closing a connection on cancel.WithoutCancel(1.21): keeps the context's values but drops its cancellation. Use it for work that must outlive the request, such as audit logs or the singleflight body.
ctx, cancel := context.WithCancelCause(parent)
defer cancel(nil)
go func() {
if err := watchQuota(ctx); err != nil {
cancel(fmt.Errorf("quota exceeded: %w", err))
}
}()
stop := context.AfterFunc(ctx, func() { conn.Close() }) // unblock reads on cancel
defer stop()
<-ctx.Done()
log.Println(ctx.Err(), context.Cause(ctx)) // context canceled quota exceeded: ...
Rules the interviewer checks:
- ctx is the first parameter.
- Never store a ctx in a struct.
- Always call
cancel;go vet's lostcancel check catches misses. - Context values are for request-scoped data, not for optional parameters.
More on Concurrency Patterns & sync
- Q247What problem does singleflight solve, and what are its gotchas?
- Q248Implement graceful shutdown for an HTTP server with background workers.
- Q250Is time.After in a select loop a leak? What changed in Go 1.23 timers?
- Q251What happens here, and when does the runtime NOT detect a deadlock?
- Q252Design a thread-safe loading cache. Why is naive double-checked locking with a plain flag wrong in Go?
- Q253Channels or mutexes: how do you decide? ("Share memory by communicating")