Context Tree — Specification¶
This file is a precise, normative description of the cancellation tree built by Go's context package. It is written so that a careful reader could re-implement a compatible package from it.
Scope¶
The specification covers the public surface of the context package as of Go 1.21, including:
Background,TODOWithCancel,WithCancelCauseWithDeadline,WithDeadlineCause,WithTimeout,WithTimeoutCauseWithValueWithoutCancelAfterFuncCause,Canceled,DeadlineExceeded
It does not cover unexported implementation details that may change between versions.
Definitions¶
Context. A value satisfying the Context interface:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
Cancellation tree. The directed tree formed by all live contexts, where each non-root context has exactly one parent: the first argument passed to the constructor that produced it.
Root. A context with no parent. The values returned by Background() and TODO() are roots.
Derived context. A context produced by any With... function. Its parent is the constructor's first argument.
Descendant. Any context reachable from a node by following child edges.
Cancellation event. The moment a node's Done() channel transitions from open to closed.
Invariants¶
I1. Root immutability¶
For any root r:
r.Done()returnsnil.r.Err()returnsnil.r.Deadline()returns(time.Time{}, false).r.Value(k)returnsnilfor everyk.
A root never experiences a cancellation event.
I2. Edge stability¶
A context's parent is determined at construction and never changes. The tree is append-only with respect to nodes; nodes are not re-parented.
I3. Cancellation monotonicity¶
For any context c:
c.Done()either has been closed (cancellation event has occurred) or has not.- Once closed, it stays closed.
- Once
c.Err()returns a non-nil error, it returns the same non-nil error on every subsequent call. - Once
Cause(c)returns a non-nil error, the value it returns may not change (subject to the cause-walk rules in I7).
I4. Downward cascade¶
If a context p undergoes a cancellation event, then every descendant d of p for which the cancellation tree edge from p to d does not cross a WithoutCancel boundary will also undergo a cancellation event before p.Cancel returns to its caller.
A "cross of a WithoutCancel boundary" means: somewhere on the path from p to d, there is a node w such that w was created by WithoutCancel(parentOfW).
I5. No upward propagation¶
If a context c undergoes a cancellation event, the ancestors of c are not affected. Their Done() channels do not close as a consequence of c's cancellation.
I6. Sibling isolation¶
If a and b are siblings (share the same immediate parent), cancellation of a does not affect b.
I7. Cause walk¶
For any context c:
- If
cwas created withWithCancelCauseorWithTimeoutCause/WithDeadlineCause, and its cancel was invoked with a non-nil causee, then after the cancellation event,Cause(c) == e. - If
chas not undergone a cancellation event,Cause(c)returnsnil. - If
chas undergone a cancellation event but no node in the path fromcto root has set a cause,Cause(c)returnsc.Err(). - If
citself has no cause but an ancestor has one and is also cancelled,Cause(c)may return that ancestor's cause.
The exact walk is: find the nearest cancelled ancestor (including c itself) whose cause field is non-nil; return that. If none, return c.Err().
Constructors¶
Background() Context¶
Returns a process-global root. The returned value is the same on every call within a process.
TODO() Context¶
Returns a root semantically distinct from Background (for the purposes of String()), but behaviourally identical.
WithCancel(parent Context) (Context, CancelFunc)¶
Creates a node c whose parent is parent. Returns c and a function cancel such that:
- Calling
cancel()causescto undergo a cancellation event withErr() == CanceledandCause(c) == Canceled(subject to I7). - Calling
cancel()more than once has no further effect (it is idempotent). - If
parenthas already undergone a cancellation event beforeWithCancelreturns, thenchas already undergone one too, withErr()equal toparent.Err().
WithCancelCause(parent Context) (Context, CancelCauseFunc)¶
Like WithCancel, but cancel takes an error argument that becomes c's cause. If the argument is nil, the cause defaults to Canceled. Otherwise the cause is exactly the argument passed.
WithDeadline(parent Context, d time.Time) (Context, CancelFunc)¶
Creates a node c whose parent is parent, with the following behaviour:
- If
parent.Deadline()returns(t, true)witht.Before(d), thenWithDeadlineis equivalent toWithCancel(parent)— no timer is created, andc.Deadline()returns the parent's deadline. - Otherwise, a timer is started. When the timer fires at time
d,cundergoes a cancellation event withErr() == DeadlineExceededandCause(c) == DeadlineExceeded. - If
time.Now()is already after or atdwhenWithDeadlineis called,cis cancelled beforeWithDeadlinereturns. - The returned
cancelfunction may be called to cancelcearly. Calling it stops the timer; the cancellation Err becomesCanceled.
WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)¶
Like WithDeadline, but when the timer fires, the cause is set to cause instead of DeadlineExceeded. Err() is still DeadlineExceeded.
WithTimeout(parent Context, dur time.Duration) (Context, CancelFunc)¶
Equivalent to WithDeadline(parent, time.Now().Add(dur)).
WithTimeoutCause(parent Context, dur time.Duration, cause error) (Context, CancelFunc)¶
Equivalent to WithDeadlineCause(parent, time.Now().Add(dur), cause).
WithValue(parent Context, key, val any) Context¶
Creates a node c whose parent is parent and whose Value(k) returns:
valifk == key.parent.Value(k)otherwise.
c's Done(), Err(), and Deadline() delegate to parent. c undergoes a cancellation event if and only if parent does.
key must be comparable and should not be of a built-in type. Defining a private type for keys is required to avoid collisions.
WithoutCancel(parent Context) Context¶
Creates a node c whose parent is parent but:
c.Done()returnsnil.c.Err()returnsnil.c.Deadline()returns(time.Time{}, false).c.Value(k)delegates toparent.Value(k).
c never undergoes a cancellation event, regardless of the parent's state.
Descendants of c are not affected by parent's cancellation events. They may undergo cancellation if they have their own With... derivation.
AfterFunc¶
Registers f to be called when ctx undergoes a cancellation event. Semantics:
fis invoked at most once.fruns in its own goroutine, not on the cascade goroutine.- If
ctxhas already undergone a cancellation event whenAfterFuncis called,fis scheduled immediately. - If
ctxwill never undergo a cancellation event (e.g., a root, aWithoutCancel),fwill never be invoked. stop()deregistersf. It returns:trueiffhad not started and is now guaranteed not to run.falseiffhas already started (or completed) or had already been deregistered.
Multiple AfterFunc calls on the same context register independent callbacks. Each is invoked.
Cause¶
Returns the original cancellation cause, per I7. Returns nil if c has not undergone a cancellation event.
Canceled and DeadlineExceeded¶
Both are exported, comparable with errors.Is. DeadlineExceeded's Timeout() method returns true (it implements net.Error).
Memory and Goroutine Cost¶
The specification does not mandate specific implementation costs, but compatible implementations must:
- Not allocate a
Done()channel for nodes whoseDone()is never called and which are not cancelled. - Not allocate a
childrenmap for nodes that have no cancelable children. - Not spawn a permanent goroutine per derivation when both parent and child are built-in node types.
- Spawn at most one goroutine per derivation when the parent is a non-built-in
ContextwhoseDone()is non-nil.
Concurrency¶
All methods on Context are safe for concurrent use. The returned cancel functions are safe for concurrent use; calls from multiple goroutines do not interfere.
The order in which descendants undergo cancellation events during a cascade is not specified. Implementations are free to use any order, including non-deterministic ones.
Errors¶
Err()after aWithCancelcancellation returnsCanceled.Err()after a deadline-driven cancellation returnsDeadlineExceeded.Err()before any cancellation event returnsnil.Cause()follows I7.
Compatibility¶
Implementations claiming compatibility with this specification:
- Must satisfy I1–I7.
- Must implement every constructor with the described semantics.
- May add additional methods or types (e.g., for instrumentation) without breaking I1–I7.
- May choose any deterministic or non-deterministic cascade order.
Non-Goals¶
This specification does not describe:
- The exact wire-up algorithm (
propagateCancel). - The choice of data structure for
children. - The exact mechanism for AfterFunc registration.
- Custom contexts implemented outside the standard library.
Those are implementation details and may differ between versions of Go.