Week 11 - context.Context, Cancellation, errgroup, singleflight¶
11.1 Conceptual Core¶
context.Contextis the cancellation propagation primitive in Go. Every blocking operation that crosses an API boundary should accept acontext.Contextas 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.Contextas the first parameter, namedctx. - 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
cancelfunction returned byWithCancel/WithTimeout/WithDeadline, even on the success path. Otherwise the context's resources (timer, goroutine inpropagateCancel) leak. - Do not use
context.Valuefor 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 ofgo func() { <-ctx.Done(); cleanup() }().context.Cause(since Go 1.20): retrieve the cancellation reason, including custom errors viaWithCancelCause.
11.3 Lab-"Context Discipline"¶
- 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 requestctxand outlives the request. - Implement a parallel fan-out using
errgroupwith N=8 workers, all cancellable on first error. - Implement a cache stampede test: 1000 concurrent requests for the same uncached key. Without
singleflight, observe N upstream calls. Withsingleflight, observe 1. - Demonstrate
context.AfterFunccleanup: 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(forbidscontext.Background()outsidemain/tests),staticcheck SA1029(context.WithValuewith built-in key type-collision hazard).
11.5 Production Hardening Slice¶
- Wire
contextdeadlines 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'sRUNBOOK.md.