context Package (Cancel, Deadline, Values)

The context package controls cancellation, deadlines, and request lifecycles in Go services. This lesson covers propagation patterns, timeout strategy, misuse pitfalls, and production-safe context design.

On this page

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.