What does this print? (Cause across WithoutCancel)
Question 291HardGo 1.22 to 1.25
var errShutdown = errors.New("shutting down")
func main() {
parent, cancel := context.WithCancelCause(context.Background())
detached := context.WithoutCancel(parent)
child, cancelChild := context.WithCancel(detached)
defer cancelChild()
cancel(errShutdown)
fmt.Println(parent.Err(), "|", context.Cause(parent))
fmt.Println(detached.Err(), "|", context.Cause(detached))
fmt.Println(child.Err(), "|", context.Cause(child))
cancelChild()
fmt.Println(child.Err(), "|", context.Cause(child))
}
Output:
context canceled | shutting down
<nil> | <nil>
<nil> | <nil>
context canceled | context canceled
detachedhas anilDone()channel, so whenchildis created,propagateCancelsees a parent that can never be canceled and registers nothing. Cancelingparentdoes not reachchild.context.Causefinds the nearest*cancelCtxthrough an internalValuelookup.WithoutCanceldeliberately stops that lookup, soCause(detached)falls back todetached.Err(), which isnil. It does not leak the parent's "shutting down" cause.- For
child, the lookup findschilditself. Before cancellation its cause isnil. After a plaincancelChild(), the cause iscontext.Canceled. - Ordinary values (trace IDs, auth) still flow through
detached; only cancellation, deadline, and cause are cut off.
More on Context
- Q289How should a library expose context in its API, and how do you add context support to an existing API without breaking callers?
- Q290What does this print? (AfterFunc on already-canceled and live contexts)
- 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?