2026-10-31·作者 Sijie Wang

Context

Context: cancellation, deadlines, and request data down the call tree

The motivation: done channels do not scale across layers. A done channel answers "how to stop", but a cancellation signal in a real program has to cross many layers: HTTP handler, service, repository, database driver or downstream call. Passing a done channel through every signature pollutes all of them, and it carries no deadline and no request data. Context standardizes and upgrades done: one object passed down the call chain, bundling cancellation, deadline, and request-scoped data together.

The interface has four methods. Done() returns the done channel: closed means stop. Err() says why: canceled or deadline exceeded. Deadline() reports whether a timeout point exists and when. Value(key) carries request-scoped data, such as a request ID, along the request.

Derivation is a tree, and cancellation flows one way. Start from Background and derive children with WithCancel, WithTimeout, WithDeadline, or WithValue. Canceling a parent cancels all its descendants at once, the same broadcast idea as closing a done channel, only grown into a tree. Canceling a child affects neither its parent nor its siblings. Values are immutable: deriving produces a new object and never changes the parent.

First parameter, never stored in a struct. Three reasons. Lifetimes mismatch: a context belongs to one request, while a struct often lives much longer, so storing it binds one request's cancellation to a long-lived object, or keeps using a canceled context after the request ends. The chain breaks: explicit parameters keep the cancellation path visible to every reader, while a hidden field leaves the next reader guessing whether a function can be canceled. The signature documents: a first-parameter context says at a glance that the function is cancelable and has a deadline.

WithCancel and WithTimeout return a cancel function that must be called. The convention is defer cancel(). A child that is never canceled stays registered in its parent's children until the parent is canceled or times out: that is a context leak. WithTimeout is WithDeadline set to now plus a duration; when the time arrives it cancels by itself. Err() distinguishes the cause: manual cancel gives Canceled, a reached deadline gives DeadlineExceeded.

Two usage shapes. Inside a loop, select on ctx.Done() and return ctx.Err() when it closes. After a blocking call, check Err() to tell a real error apart from cancellation or timeout. Passing down means handing the same context to calls that accept it, such as http.NewRequestWithContext or a database QueryContext: one context walks the whole chain.

Value rules. Keys use a custom unexported type so packages cannot collide on the same key. Only request-scoped metadata belongs here: request IDs, trace IDs, authentication information. Business objects do not, and Value must not smuggle in things that belong in parameters.

The don'ts. Never pass a nil context: use Background when there is none, TODO as a placeholder. Never forget the cancel function. Never store a context in a struct. Never use Value for business data or optional parameters.

The React analogy, and its limits. React Context solves prop drilling, and Go context solves done drilling: both pull something that must cross many layers out of every signature in between. Two differences. React Context carries UI state that can trigger re-renders; Go context carries control signals and request metadata, not business state. React Context is implicit, available anywhere with a hook; Go context is explicit, passed as a first parameter, flowing one way down an immutable tree.

Production note. In a web service, each request derives its context from the HTTP request context, timeouts are set at layer boundaries, and server shutdown cancels in-flight request contexts. The context is what turns "the client went away" into work that actually stops.

关于这一条

sijie 的一条 wiki 条目。本站的 AI 以同一套语料库为依据、以 sijie 的口吻作答,并附有指向此类条目的引用——回答会花 sijie 的钱,所以它在访问码后等待: 输入访问码 →