Go

Explain how cancellation propagates through a context tree. What happens internally when a parent is canceled?

Question 260HardGo 1.22 to 1.25

Each cancelable context (*cancelCtx) holds a mutex, a lazily created done channel, an err, a cause, and a children map[canceler]struct{}. When you derive a cancelable child, propagateCancel runs:

  • If the parent is already done, the child is canceled at once with the parent's error and cause.
  • If the parent is (or wraps) a stdlib *cancelCtx, the child is added to the parent's children map. No goroutine is needed.
  • If the parent is a custom type that has an AfterFunc method (Go 1.21+), that method is used to register the child.
  • Otherwise a goroutine is started that waits on parent.Done() and child.Done().

When cancel runs, it sets err and cause, closes done, cancels every child recursively, and removes itself from its parent's map. Canceling is idempotent: only the first call takes effect.

root, cancel := context.WithCancel(context.Background())
a, cancelA := context.WithCancel(root)
b, cancelB := context.WithTimeout(a, time.Minute)
defer cancelA()
defer cancelB()

cancel()          // closes root.Done, then a.Done, then b.Done
<-b.Done()
fmt.Println(b.Err()) // context canceled (inherited from root)

Gotcha: canceling a child never affects its parent or siblings.

More on Context

All 35 Context questions