What is WithCancelCause, and how does ctx.Err() differ from context.Cause(ctx)?
Question 265HardGo 1.22 to 1.25
WithCancelCause (Go 1.20) returns a CancelCauseFunc that takes an error saying why the context was canceled. ctx.Err() still returns only context.Canceled, which keeps the Context interface stable. context.Cause(ctx) returns the specific reason.
var ErrQuotaExceeded = errors.New("tenant quota exceeded")
ctx, cancel := context.WithCancelCause(context.Background())
cancel(ErrQuotaExceeded)
cancel(errors.New("ignored")) // first cause wins
fmt.Println(ctx.Err()) // context canceled
fmt.Println(context.Cause(ctx)) // tenant quota exceeded
How Cause behaves:
- Not yet canceled: it returns
nil. - Canceled with
cancel(nil): the cause iscontext.Canceled. - Canceled by a parent: the child gets the parent's cause.
- Context never created with a cause-aware constructor:
Causereturnsctx.Err().
Use case: in fan-out, the first failing worker calls cancel(err). Every sibling then sees ctx.Err() == Canceled, while the coordinator reports context.Cause(ctx) as the real root cause. This is how golang.org/x/sync/errgroup works internally when built with Go 1.20 or later: the first error becomes the derived context's cause.
More on Context
- Q263What does this print?
- Q264What values can ctx.Err() return, and how should you check them?
- Q266What does this print? (WithTimeoutCause and its cancel function)
- Q267Why should context keys use an unexported custom type instead of a string? Show the idiomatic pattern.
- Q268What does this print? (context value keys)
- Q269How is Value lookup implemented, and what are its performance implications?