context Package (Cancel, Deadline, Values)
Context: The Lifecycle Backbone of Go Services
The context package is not optional in production Go services. It defines request lifetimes, cancellation propagation, and timeout boundaries. Misusing context leads to hung goroutines, stuck database calls, and cascading latency failures.
Context governs:
- Request-scoped cancellation
- Timeout enforcement
- Resource cleanup
- Graceful shutdown
- Cross-service propagation
Real Production Failure: The Never-Ending Query
A service handled HTTP requests and made database calls without passing request context. When clients disconnected, handlers returned — but database queries continued running. Under traffic spikes, thousands of abandoned queries accumulated, exhausting the connection pool.
The service became unresponsive, even though CPU usage remained moderate.
Root cause: context was ignored at the repository layer.
Context Tree Model
Contexts form a tree:
- Background() at root
- Derived contexts via WithCancel / WithTimeout / WithDeadline
- Cancellation propagates downward
Create Root Context
ctx := context.Background()
Derived Context With Timeout
ctx, cancel := context.WithTimeout(ctx, 2*time.Second) defer cancel()
Always call cancel to release resources.
Propagating Context Across Layers
Every boundary must accept context explicitly.
func (s *Service) GetUser(ctx context.Context, id int) (*User, error)
Never create a new background context inside lower layers.
Incorrect Pattern
func (r *Repo) Find(id int) {
ctx := context.Background()
}
This disconnects cancellation propagation.
HTTP Integration
HTTP handlers automatically receive request context.
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
...
}
If the client disconnects, r.Context() is canceled.
Database Integration
db.QueryContext(ctx, query)
Always use Context-aware DB calls.
Without it, cancellation does not stop the query.
Timeout Strategy
Not every function needs its own timeout. Prefer timeouts at boundaries:
- HTTP layer
- External API calls
- Database queries
Example: External API Call
ctx, cancel := context.WithTimeout(parent, 3*time.Second) defer cancel() req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
Context Cancellation in Goroutines
go func() {
select {
case <-ctx.Done():
return
case result := <-work:
handle(result)
}
}()
Ignoring ctx.Done leads to goroutine leaks.
Context Value Misuse
Context values are for request-scoped metadata only.
Allowed
- Request ID
- User ID
- Tracing span
Not Allowed
- Database handles
- Large structs
- Optional configuration
Define Typed Keys
type ctxKey string const requestIDKey ctxKey = "requestID"
Avoid using raw strings as keys.
Leaked Contexts
Forgetting cancel leaks timers and memory.
Incorrect
ctx, cancel := context.WithTimeout(parent, time.Second) process(ctx)
Missing cancel() call.
Correct
ctx, cancel := context.WithTimeout(parent, time.Second) defer cancel() process(ctx)
Deadline vs Timeout
- WithTimeout: relative duration
- WithDeadline: absolute timestamp
Use deadline when aligning across services.
Cross-Service Propagation
Forward deadlines downstream.
deadline, ok := ctx.Deadline()
Use this to adjust sub-call timeouts.
Detecting Context-Related Issues
- Requests hang after client disconnect
- Database queries continue after timeout
- Goroutine count grows
- Connection pool exhaustion
Debug With pprof
curl http://localhost:6060/debug/pprof/goroutine?debug=2
Look for goroutines blocked without ctx.Done handling.
Graceful Shutdown With Context
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() <-ctx.Done()
This integrates OS signals with context cancellation.
Testing Cancellation
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := service.Do(ctx)
if !errors.Is(err, context.Canceled) {
t.Fatal("expected cancellation")
}
Test cancellation paths explicitly.
Anti-Patterns
- Creating new background context in lower layers
- Storing business data in context
- Ignoring ctx.Done in loops
- Forgetting cancel()
- Adding arbitrary data without type-safe keys
Operational Checklist
- Context passed explicitly across layers
- DB and HTTP calls use Context variants
- Cancellation handled in goroutines
- Timeouts defined at boundaries
- cancel() always deferred
- Context values limited to metadata
Final Perspective
Context is the contract of time and cancellation in Go systems. It defines how work begins and ends. In production environments, correct context propagation prevents leaks, enforces deadlines, and enables graceful degradation. Treat context as a first-class design concern — not an afterthought.