Context Package
The context
package in Go is used to carry deadlines, cancellation signals, and other request-scoped values across API boundaries and between goroutines. It provides a way to control the lifetime of processes, manage resource cleanup, and prevent goroutine leaks in concurrent applications. Below is an in-depth explanation of how the context
package works, along with several illustrative examples.
Key Concepts
Context
Interfacetype Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }Deadline()
: Returns the time when work should be canceled, and a boolean indicating if a deadline is set.Done()
: Returns a channel that is closed when work should be canceled.Err()
: Returns a non-nil error if the context is canceled or has expired.Value()
: Retrieves any stored data associated with the context.
Context Types and Functions
context.Background()
: Returns an empty context. It’s the root context usually used at the start of main functions or servers.context.TODO()
: Returns an empty context when you are not sure which context to use or planning to add it later.context.WithCancel(parent)
: Returns a copy of the parent context with a newDone()
channel that can be closed by calling the cancel function.context.WithDeadline(parent, deadlineTime)
: Similar toWithCancel
, but automatically closes theDone()
channel at a specified time.context.WithTimeout(parent, duration)
: A convenience wrapper overWithDeadline
that ends the context after a duration.context.WithValue(parent, key, val)
: Returns a copy of the parent context with an associated key-value pair. Good for passing request-scoped data down a call chain.
1. Using context.WithCancel
WithCancel
creates a context that can be canceled manually. When you call the cancel function, the Done
channel in the context is closed, signaling any goroutines using that context to stop work if they wish to respect cancellation.
Example: Manual Cancellation
Explanation 1
We create a root context with
context.Background()
.context.WithCancel(ctx)
returns a derived context and acancel
function.The goroutine continuously checks for cancellation via
select { case <-ctx.Done(): ... }
.When
cancel()
is called,ctx.Done()
is closed, signaling the goroutine to stop.
Sample Output (truncated):
2. Using context.WithTimeout
and context.WithDeadline
WithTimeout
automatically cancels the context after a specified duration, whereas WithDeadline
cancels the context at a specific time.
Example: Automatic Cancellation with Timeout
Explanation 2
context.WithTimeout
sets up a 2-second limit for the derived context.If the work (
time.After(3 * time.Second)
) takes longer than 2 seconds, the context will expire, andctx.Err()
will becontext.DeadlineExceeded
.If the work finishes before 2 seconds, the context wouldn’t be canceled early, but you still call
cancel()
in adefer
to release any resources.
Sample Output (if it times out):
3. Passing Values in Context
Although the context
package allows adding and retrieving values with WithValue
, it is intended for request-scoped data (like user IDs, request IDs, or authentication tokens) and not for passing large data structures.
Example: Adding and Retrieving Values
Explanation 3
We define a custom type
key string
to avoid collisions in case other packages also store strings in context.WithValue(ctx, userIDKey, "12345")
returns a new context with user ID ="12345"
.We retrieve it via
ctx.Value(userIDKey)
, and if it matches typestring
, we use it.
Sample Output:
4. Cancelling Multiple Goroutines
Multiple goroutines can share the same context to signal a coordinated cancellation. When cancel()
is called or a timeout is reached, all goroutines that derive from that context see ctx.Done()
close.
Example: Coordinating Cancellation
Explanation 4
We create a single context with a 3-second timeout and spawn three goroutines.
Each goroutine checks for
ctx.Done()
. After 3 seconds,ctx.Err()
becomescontext.DeadlineExceeded
, causing all workers to exit.
Sample Output (truncated):
5. Nested Contexts
You can derive multiple child contexts from a parent, each potentially with its own deadline or cancellation rule. Canceling or timing out the parent automatically cancels all children.
Example: Nested Contexts
Explanation 5
The child context will finish in 2 seconds, triggering
childCtx.Err() = context.DeadlineExceeded
.The parent context will finish in 3 seconds.
If the parent finishes earlier, it cancels the child as well.
Sample Output (order may vary):
6. Best Practices
Pass
context.Context
as the first parameterConventional signature:
func DoSomething(ctx context.Context, arg1 Type1, ...)
.
Do Not Store Contexts in Struct Fields
Contexts are request-scoped or operation-scoped; storing them in a struct can lead to confusion about their lifetime.
Use
context.TODO()
Sparinglycontext.TODO()
is a placeholder. Replace it with a proper context once you know the lifetime or cancellation needs.
Avoid Putting Large Objects in Context
Use
context.WithValue
for request-scoped data like request IDs, not for large data structures.
Always Call
cancel()
If you create a context with
WithCancel
,WithTimeout
, orWithDeadline
, call the cancel function to release resources (especially in library code).
Check
ctx.Err()
If you’re performing expensive operations or waiting for I/O, periodically check
ctx.Err()
orctx.Done()
to handle cancellations promptly.
Conclusion
The context
package is a vital tool in Go for managing cancellations, timeouts, and request-scoped data in concurrent applications. By following Go’s conventions—such as making context.Context
the first parameter in functions, respecting cancellation signals, and passing only small amounts of data—you can write robust, leak-free, and well-structured code. Whether you’re building microservices, CLI tools, or background workers, using context effectively ensures that your goroutines do not outlive their purpose and that your code handles timeouts and cancellations gracefully.