context Package Best Practices
Team conventions for context keys, timeouts, and shutdown.
Apply these rules in code review, onboarding, and service templates so every handler, repository, and RPC client propagates cancellation consistently.
How to Use This List
- Tick rules as your service template adopts them.
- Encode Tier A items in golangci-lint custom checks or arch unit tests where possible.
- Document per-dependency timeout budgets in README or config next to the rule.
- Revisit quarterly when adding RPC clients or database drivers.
A - Function signatures and propagation
- Accept
ctx context.Contextas the first parameter on blocking APIs. Reviewers reject new I/O without context. - Name the parameter
ctx, notcorcontext. Matches Effective Go and staticcheck expectations. - Pass ctx down unchanged except for intentional child deadlines or values. No fresh
Background()mid-request. - Return
ctx.Err()when cancel ends work. Preserve with%wonly when adding layer context. - Replace
Query/Execwith*Contextvariants on request paths. Ban non-context SQL in handlers.
B - HTTP and RPC entry points
- Root handler context at
r.Context()or framework equivalent. chi/gin/echo wrappers still map to the request. - Attach metadata in middleware via
r.WithContext. Values and short timeouts wrap the incoming ctx. - Map
context.Canceledwithout noisy 500s when the response cannot flush. Log at debug for client aborts. - Use
http.NewRequestWithContextfor outbound HTTP. Tie dependency lifetime to the caller. - Pass ctx into gRPC stubs and honor deadlines server-side. Translate to
codes.DeadlineExceededwhen appropriate.
C - Timeouts and deadlines
- Publish a timeout table per dependency (DB, cache, auth). Shrink budgets at each hop from the edge SLO.
- Derive child timeouts from the parent, never from
Background(). Preserves user cancel and upstream clocks. - Always
defer cancel()afterWithCancel/WithTimeout/WithDeadline. Prevents timer leaks in tests and prod. - Cap retries with smaller per-attempt contexts. Stop when the parent ctx is already done.
- Use
Shutdown(ctx)with a bounded context on process exit. Pair with signal handling inmain.
D - Values and keys
- Define context keys as unexported types in the owning package. Avoid string keys in libraries.
- Limit values to cross-cutting metadata (request ID, principal, locale). Business filters belong in structs.
- Expose typed accessors (
User(ctx)) instead of rawValuecalls. Keeps key types private. - Do not store secrets or large blobs in context. Treat context as an in-process concern only.
- Document allowed keys in a package comment or ADR. Onboarding reads one list, not fifty middleware files.
E - Concurrency, testing, and ops
- Select on
ctx.Done()in long loops and streams. gRPCRecv/Sendloops check each iteration. - Use
context.WithoutCancelonly for documented post-response work. Billing and audit are common cases. - Table-test
CanceledandDeadlineExceededpaths. Short timeouts beattime.Sleepin CI. - Run
-raceon packages that spawn goroutines per request. Catches ignored cancel races. - Log remaining deadline budget at service boundaries during debug incidents. Speeds up tail-latency triage.
FAQs
Which rules are CI-blockers vs guidelines?
Tier A signature and SQL-context rules should fail CI or review bots.
Timeout tables and value ADRs are team policy enforced in review.
How do I introduce ctx to legacy packages?
Add ctx as first param on exported entry points, keep deprecated wrappers one release, then delete.
Track in a migration issue per package.
Should libraries enforce ctx on all helpers?
Exported blocking helpers yes; pure transforms no.
Document which functions are context-aware in godoc.
What default HTTP client timeout?
Set Transport limits and still pass per-request ctx.
Global Client.Timeout is a backstop, not a replacement for ctx.
How do kubebuilder controllers differ?
Reconcile context cancels on manager shutdown - pass it to client calls.
Do not store on the reconciler struct.
Can best practices differ per service tier?
Edge services use stricter outer deadlines; batch workers use longer budgets but still propagate shutdown ctx.
Write differences in the service timeout table.
Should we standardize one context key package?
A shared internal/ctxkeys package works for monorepos.
Libraries published externally should own their keys.
How do I review third-party code?
Wrap vendors that lack ctx with timeouts at the boundary.
Open upstream issues when blocking APIs have no context variant.
What about TinyGo or WASM builds?
Context behavior is the same; some drivers may lack cancel.
Verify board targets at build per stack footer note.
How do these rules interact with errgroup?
Use the group ctx inside goroutines and cancel on first error.
Document errgroup usage in the same concurrency ADR.
Related
- context.Context: Cancellation as a First-Class API - conceptual anchor
- context Basics - runnable starting examples
- Context Misuse Anti-Patterns - negative patterns to avoid
- Deadlines & Timeouts Across Service Boundaries - timeout table examples
- Testing Code That Accepts context.Context - verify cancel paths
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).