Go Package context
External
Internal
Overview
The context
package provides two, somewhat unrelated, features.
One is the capability to preempt, or cancel, blocking code executed by goroutines running downstream in the call graph. The context
package provides an API for cancelling branches of the call graph. Cancellation can be programmatic, on timeout or on deadline. This is an idiomatic preemption pattern, equivalent, but preferred to the Done channel pattern. It is preferred because its implementation is available in the standard library, making it a standard Go idiom to consider when working with concurrent code. The implementation is also more expressive.
The second feature is the capability to store request-scoped data and propagate this state through the call graph.
Explicit context propagation, as an argument of functions implementing the pattern, is a key difference between Go and other languages.
Concepts
Context
context.Context
is an interface exposed by the context
package. The Context
instances flow across API boundaries and through the system in the same way "done" channels do. The role of the Context instances is to carry cancellation signals, deadlines and request-scoped values. To implement the pattern, each function downstream from the top level concurrent call must take a Context as parameter. Conventionally, is the first parameter of the function. For more rules of using Contexts, see Programming Model. The implementations must ensure that they are concurrent-safe, so their methods may be called by multiple goroutines simultaneously.
type Context interface {
// Deadline returns the time when work done on behalf of this context should be canceled.
Deadline() (deadline time.Time, ok bool)
// Done returns a channel that's closed when work done on behalf of this context should be canceled.
Done() <-chan struct{}
// If Done is not yet closed, Err returns nil. If Done is closed, Err returns a non-nil error
// explaining why: Canceled if the context was canceled or DeadlineExceeded if the context's deadline passed.
Err() error
// Value returns the value associated with this context for key, or nil if no value is associated with key.
Value(key any) any
}
The Context interface does not expose any method that allows cancelling the Context. This protects functions up the call stack from children cancelling the context - only the parent can cancel the children functions, as shown in the Programmatic Preemption (Cancellation) section.
The Context interface does not expose any method that can mutate the state of the underlying structure, either. The context is immutable, so when new values are stored, the context is actually cloned, with extra metadata.
Err()
ctx.Err()
returns a standard response, as follows:
nil
is the Done channel is not closed yet.- the
context.Canceled
error, if the context was canceled. - the
context.DeadlineExceeded
error, if the context's deadline has passed.
The Background Context
The background Context is a non-null, empty, no-deadline and no-values Context returned by the context.Background()
function. This context is never canceled, and it is typically used by the main function, initialization and tests, and as the top-level Context for incoming requests.
Derived Context and the Context Hierarchy
Context hierarchy.
Preemption (Cancellation)
Preemption, or cancellation of a function executing a blocking operation on a channel, or any other blocking operation (disk, network IO, user), consists in the immediate return from the blocking operation, and usually return from the function with a cancellation error. Programmatic cancelation is useful in stopping processing or handling application shutdown.
Cancellation is initiated by a parent goroutine in the call graph.
Cancellation Signal
The "cancellation signal" carried by the context is a Done channel that is closed when the function associated with the context needs to be prepped. The channel is returned by the context's Done()
method.
Cancel Function
Deadline
A deadline is communicated to the user by the result of the Context's Deadline()
method. The method returns the time when work done on behalf of this Context should be cancelled, or a false
value if no deadline is set.
Timeout
Call Graph
Requests and Request-Scoped Data
One of the primary use of goroutines is to service requests. Usually, in these programs, request-specific information needs to be. passed along in addition to information about preemption. The Context exposes the Value()
method, which returns a request-scoped value for a given key.
Using the value propagation feature of the Context is dubious. Don't use Context values for stuff that should be regular dependencies between components. Use Context values for data that can't be passed to your program in any other way: only data that is request-scoped, stuff that is created at the beginning of a request lifecycle, like request ID, etc. If the information is available when the program starts or at any point prior to when the request starts, do not use Context values. This is the case for database handles, loggers, etc.
Programming Model
- Each function downstream from the top level concurrent call must take a Context as the first parameter.
- Context instances cannot be mutated or cancelled directly, as explained above.
- To allow a parent goroutine to cancel its children, create a new Context instance with one of the
WithCancel()
,WithTimeout()
andWithDeadline()
methods and pass that instance to the function executing on the child goroutine.
Programmatic Preemption (Cancellation)
This section documents the idiomatic pattern to preempt, or cancel, a blocking function.
It consists in a someFunc()
function that gets a Context instance as its first argument. The function will select
reading the Done channel returned by the Context's Done()
method. When the context is externally cancelled, the Done channel is closed, reading operation on the Done channel unblocks and allows the function to return. The recommended return value in this case is the error returned by ctx.Err()
.
// someFunc will return a context.Canceled error if the context was externally canceled
func someFunc(ctx context.Context, c <-chan interface{}) error {
for {
select {
case <-ctx.Done():
return ctx.Err() // context.Canceled is the context was externally canceled
case <-c: // this will ensure the goroutine will block, as we will never write on that channel
}
}
}
To externally cancel the context, and implicitly all functions listening on its Done channel, invoke the cancel function returned by the context.WithCancel()
function:
ctx, cancel := context.WithCancel(context.Background())
// spin off someFunc() on its own thread and get it to block by reading on the channel we will never write on
go func() {
err := someFunc(ctx, make(chan interface{}))
fmt.Printf("someFunc() errored out because %v\n", err)
}()
// spin off the anonymous function that will cancel someFunc() after 5 seconds
go func() {
time.Sleep(5 * time.Second)
cancel() // Calling the cancel() function cancels the context
}()