Deadlines and Cancellations — Specification¶
This document is the formal description of the contract: what every implementation of context.Context must do, what the constructors guarantee, and the rules governing the cancellation tree.
1. Interface Contract¶
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
Every method must be safe to call concurrently from multiple goroutines.
1.1 Deadline¶
- Returns
(t, true)if the context will be canceled at or beforet. - Returns
(time.Time{}, false)if no deadline exists. - Once a deadline is set on a context, it must not change.
- The returned time is in absolute terms (UTC or any monotonic-bearing time); compare with
time.Now.
1.2 Done¶
- Returns a channel that is closed when the context is canceled or its deadline expires.
- May return
nilfor contexts that can never be canceled (e.g.Background(),TODO(),WithoutCancel(...)). - Successive calls must return the same channel instance.
- The channel must be closed at most once.
- Reading from a closed channel must always succeed; never blocks.
1.3 Err¶
- Returns
niluntilDone()'s channel is closed. - After
Done()closes, returns: Canceledif the context was canceled by a call to its associated cancel function.DeadlineExceededif the context was canceled because the deadline passed.- For derived chains: returns the cancellation reason from the closest cancelable ancestor.
- Successive calls must return the same error.
- Once non-nil, must not transition back to nil.
1.4 Value¶
Value(key)returns the value associated withkeyfor any ancestor in the chain that calledWithValuefor that key.- If multiple ancestors set the same key, returns the closest (deepest in the chain).
- Returns
nilif no ancestor has the key. - Must be deterministic and safe for concurrent calls.
- Must not allocate on lookup (most implementations do not).
2. Constructors¶
2.1 Background and TODO¶
- Both return a non-nil empty
Context. Backgroundis the documented root for the program (main, init, tests).TODOis a placeholder for code paths that should later be wired with a real context.- Both implementations must:
- Return
nilfromDone(). - Return
nilfromErr(). - Return
(time.Time{}, false)fromDeadline(). - Return
nilfromValue(any). BackgroundandTODOare global singletons (no allocation per call).
2.2 WithCancel¶
- Panics if
parent == nil. - Returns a new context that is canceled when:
cancelis called, or- The parent's
Done()channel is closed. Err()returnsCanceledifcancelwas called first; otherwise the parent'sErr.cancelis idempotent: calling it more than once has no additional effect.cancelmust be called by the caller; failing to call it before the context goes out of scope leaks resources.
2.3 WithDeadline¶
- Panics if
parent == nil. - Returns a new context that is canceled when:
- The deadline
dis reached, or cancelis called, or- The parent's
Done()channel is closed. - If the parent already has an earlier deadline, the new deadline is ignored and the new context inherits the parent's.
- If
dis already in the past, the returned context is already canceled andcancelimmediately becomes a no-op for the deadline portion. Err()returns:DeadlineExceededwhen the deadline is the cause.Canceledwhencancelwas called first.- The parent's
Err()when the parent canceled first. cancelmust be called by the caller for resource cleanup, even if the deadline will fire.
2.4 WithTimeout¶
- Equivalent to
WithDeadline(parent, time.Now().Add(d)). - All other guarantees match
WithDeadline.
2.5 WithValue¶
- Panics if
parent == nil. - Panics if
key == nil. - Panics if
key's dynamic type is not comparable. - Returns a context where
Value(key)returnsval, otherwise delegating toparent.Value. Done,Err, andDeadlineare passed through unchanged.- The key should typically be a private type to avoid collision; comparable but unique to the package.
2.6 WithCancelCause (Go 1.20+)¶
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)
type CancelCauseFunc func(cause error)
- Like
WithCancelbut records a cause. cancel(cause)cancels the context. Ifcause == nil, the cause defaults toCanceled.ctx.Err()returnsCanceled(for backward compatibility) when the cause is non-nil.Cause(ctx)returns the recorded cause, walking up to the closest ancestor that has one.
2.7 WithDeadlineCause / WithTimeoutCause (Go 1.21+)¶
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)
func WithTimeoutCause(parent Context, d time.Duration, cause error) (Context, CancelFunc)
- Like
WithDeadline/WithTimeoutbut specify the cause that will be reported byCause(ctx)when the deadline fires.
2.8 WithoutCancel (Go 1.21+)¶
- Returns a context whose
Done()returnsnil,Err()returnsnil,Deadline()returns(time.Time{}, false). Valuecalls delegate toparent.- The returned context is never canceled, even when the parent is.
- Useful for detaching long-running tasks while preserving request-scoped values.
2.9 AfterFunc (Go 1.21+)¶
- Registers
fto run on its own goroutine whenctxis canceled. - If
ctxis already canceled,fruns immediately on a new goroutine. stop()deregistersf. Returnstrueiffhad not been started;falseotherwise.- Multiple
AfterFunccalls on the same context register independently.
3. Cancellation Semantics¶
3.1 Cancellation Tree¶
- Every context derived via
WithCancel,WithDeadline,WithTimeout,WithCancelCause,WithDeadlineCause, orWithTimeoutCauseis a child of its parent. - The cancellation tree is acyclic by construction: each derived context has exactly one parent.
3.2 Cancellation Direction¶
- Cancellation propagates from parent to descendants.
- A child's cancel does not affect the parent or siblings.
- A
WithoutCancel-derived child is detached from the parent's cancellation but still inherits values.
3.3 Cancellation Atomicity¶
- A given context is canceled at most once.
- The
Donechannel close is the synchronization event that establishes happens-before for all subsequent reads ofErr,Cause, etc. - Concurrent calls to
cancelare safe; only the first effective call wins.
3.4 Cascade Order¶
- When a parent cancels, its children cancel synchronously, depth-first, before the parent's
cancelreturns. - Custom
Contextimplementations that are not recognized by the runtime cause the runtime to start a goroutine forwarder that will eventually call cancel on the child.
4. Sentinel Errors¶
var Canceled = errors.New("context canceled")
var DeadlineExceeded error // implements net.Error with Timeout() == true
Canceledis a plain sentinel.errors.Is(err, context.Canceled)matches it.DeadlineExceededsatisfiesnet.Error'sTimeout()returning true. Useful for retry logic.
5. Cause¶
- Returns the cause of the closest canceled ancestor that recorded one.
- If no ancestor recorded a cause, returns
ctx.Err(). - If
ctxis not canceled, returnsnil.
6. Value Semantics¶
- Lookup is linear in the depth of the value chain.
- Keys must be comparable; using a built-in type like
stringorintis allowed but discouraged because of collision risk. - The recommended pattern is a private unexported struct type:
- Storing very large values is discouraged; values cannot be cleaned up except by GC after the chain is unreferenced.
7. Concurrency Safety¶
- All methods on every
Contextreturned by the standard library are safe for concurrent use. - Custom
Contextimplementations must guarantee the same. - A
CancelFuncis safe for concurrent use. - The
Donechannel is safe to receive from concurrently; receivers all wake when it closes.
8. Memory Model¶
- The close of
Donehappens-before any subsequent read ofErr()orCause(ctx). - A successful receive from
ctx.Done()happens-before subsequent operations in the receiving goroutine. - A call to
cancel()happens-before any subsequent close ofctx.Done()(often the same instant).
9. Resource Lifecycle¶
- Each
WithCancel/WithDeadline/WithTimeoutallocates acancelCtxortimerCtxplus, lazily, aDonechannel. - A
timerCtxholds atime.Timer;cancel()stops the timer. - Failing to call
cancel()causes the entry to live in the parent'schildrenmap until the parent is itself canceled or GCed.
10. Error Wrapping¶
- Many standard library functions wrap context errors. For example,
*sql.DB.QueryContextmay returnfmt.Errorf("query: %w", ctx.Err()). - Always use
errors.Is(err, context.Canceled)anderrors.Is(err, context.DeadlineExceeded)rather than==.
11. Anti-Patterns (Normative)¶
The package documentation specifies:
- "Do not pass a nil Context, even if a function permits it."
- "Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it."
- "The Context should be the first parameter, typically named ctx."
- "Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions."
These are normative requirements; tooling (go vet, staticcheck) enforces them.
12. Compatibility¶
- The
contextpackage's interface must not change in a way that breaksContextimplementers. - New constructors (
WithCancelCause,WithoutCancel,AfterFunc, etc.) extend the package without breaking existing code. - Deprecation of
CanceledorDeadlineExceededwould be a Go 2-level change.
13. Build Tags and Variants¶
The runtime's context implementation is the same on all platforms. There are no build-tag-specific variants. The time.Timer used inside timerCtx uses the platform's runtime.timer.
14. Reserved Behaviour¶
context.TODO()andcontext.Background()may be optimized to return the same singleton — never assume distinctness via==.time.Time{}(zero value) is the canonical "no deadline" marker.- Custom contexts must not panic on any method call when constructed correctly.