Context
The context package in Go provides a way to manage request-scoped data, deadlines, and cancellation signals across API boundaries and between processes.
It is a must if you want to build robust, production-ready applications in Go, particularly for handling concurrent workloads like HTTP servers, database queries, and background tasks.
Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel
, WithDeadline
, WithTimeout
, or WithValue
. When a Context is canceled, all Contexts derived from it are also canceled.
Context
context.Context
The base interface that provides methods to retrieve deadlines, cancellation signals, and values.
Done() <-chan struct{}
: Returns a channel that closes when the context is canceled or times out.Err() error
: Returns the reason for cancellation, if any.Deadline() (time.Time, bool)
: Returns the deadline and whether a deadline is set.Value(key any) any
: Retrieves request-scoped values.
Functions in context
Package
context.WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
Returns a copy of parent context that can be manually canceled by calling the returned cancel function.
context.WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
Returns a copy of parent context that automatically cancel after deadline exceeded.
context.WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
Returns a copy of parent context that automatically cancels after a specified duration. WithTimeout
returns WithDeadline(parent, time.Now().Add(timeout))
.
context.WithValue
func WithValue(parent Context, key, val any) Context
Returns a copy of parent context that carries request-scoped values.
Best Practices
-
Pass Context Explicitly:
- Always pass
context.Context
as the first argument to functions handling operations that may require cancellation or deadlines. - Use context to propagate request metadata (e.g., trace IDs) across HTTP handlers or gRPC interceptors.
- Combine contexts appropriately to propagate cancellations or deadlines.
- Always pass
-
Respect Cancellation:
-
Periodically check ctx.Done() in long-running operations and exit promptly when canceled.
select {
case <-ctx.Done():
return ctx.Err()
default:
// Continue processing
}
-
-
Use Timeouts Sparingly:
- Avoid overusing
context.WithTimeout
in nested operations to prevent conflicting deadlines. Set timeouts at higher levels and propagate them down.
- Avoid overusing
-
Avoids:
- Using context.WithValue for Critical Data: Use
WithValue
only for request-scoped data. Avoid storing critical or large data; instead, pass such data explicitly. - Using
context.Background
Everywhere: Usecontext.Background
only for root-level operations (e.g., in main or tests). - Avoid Mutating Values in Context: Contexts are immutable. Avoid attempts to modify or replace values in context.
- Using context.WithValue for Critical Data: Use
Example
package main
import (
"context"
"net/http"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
err := doWork(ctx)
if err != nil {
http.Error(w, "Request timed out", http.StatusRequestTimeout)
return
}
w.Write([]byte("Success"))
})
http.ListenAndServe(":8080", nil)
}
func doWork(ctx context.Context) error {
select {
case <-time.After(3 * time.Second): // Simulate long work
return nil
case <-ctx.Done():
return ctx.Err()
}
}
- In above example we try to simulate long work and it should response with error
Request timed out
. - Try to make the timeout longer and see what the result is.