Saltar a contenido

Week 11 - context.Context, Cancellation, errgroup, singleflight

11.1 Conceptual Core

  • context.Context is the cancellation propagation primitive in Go. Every blocking operation that crosses an API boundary should accept a context.Context as its first parameter.
  • A context carries:
  • Deadline (or no deadline).
  • Cancellation channel (<-ctx.Done()) and reason (ctx.Err()).
  • Request-scoped values (ctx.Value(key))-sparingly.
  • Contexts are immutable trees: each derivation (WithCancel, WithTimeout, WithValue) produces a child. Cancelling a parent cancels all descendants.

11.2 Mechanical Detail

  • The cancellation rules:
  • Pass context.Context as the first parameter, named ctx.
  • Do not store context in struct fields except for short-lived adapters. (One narrow exception: long-running services that derive an internal context once from context.Background().)
  • Always call the cancel function returned by WithCancel/WithTimeout/WithDeadline, even on the success path. Otherwise the context's resources (timer, goroutine in propagateCancel) leak.
  • Do not use context.Value for required parameters. It is a request-scoped sidecar, not a function-call mechanism. Type-safe alternatives (function arguments, struct fields) are always better.
  • errgroup (golang.org/x/sync/errgroup): spawn N goroutines, propagate the first error, cancel siblings, wait for all. The standard pattern for parallel sub-tasks. Read the source-it is ~120 lines.
  • singleflight (golang.org/x/sync/singleflight): deduplicate concurrent identical requests. The classic cache-stampede mitigator. Use for expensive lookups (DB, RPC) where a thundering herd is plausible.
  • context.AfterFunc (since Go 1.21): register a callback to fire when a context is cancelled. Replaces the boilerplate of go func() { <-ctx.Done(); cleanup() }().
  • context.Cause (since Go 1.20): retrieve the cancellation reason, including custom errors via WithCancelCause.

11.3 Lab-"Context Discipline"

  1. Take a small HTTP service. Audit every blocking operation (DB query, downstream RPC, Redis call). Each should accept and propagate ctx. Fail any goroutine that captures a request ctx and outlives the request.
  2. Implement a parallel fan-out using errgroup with N=8 workers, all cancellable on first error.
  3. Implement a cache stampede test: 1000 concurrent requests for the same uncached key. Without singleflight, observe N upstream calls. With singleflight, observe 1.
  4. Demonstrate context.AfterFunc cleanup: register a release-resource callback on cancellation; verify it fires under both timeout and explicit cancel.

11.4 Idiomatic & golangci-lint Drill

  • contextcheck (verifies context propagation), noctx (forbids context.Background() outside main/tests), staticcheck SA1029 (context.WithValue with built-in key type-collision hazard).

11.5 Production Hardening Slice

  • Wire context deadlines to your gRPC server's per-RPC timeouts. The pattern: take the incoming RPC deadline, optionally tighten it for downstream calls, and propagate. Document the deadline-budget calculation in your service's RUNBOOK.md.

Comments