Lesson 17 · Concurrency & context
context.Context
The first argument in every function you've seen — finally explained.
Your win: use context for cancellation, deadlines, and
request-scoped values; state the conventions; and explain the deliberate
Background() choice in our consumer retries.
One value, three jobs
A context.Context travels as the first argument through a request's call
tree and carries three things:1
Cancellation
A Done() channel that closes when work should stop. ctx.Err() says why.
Deadlines / timeouts
WithTimeout/WithDeadline auto-cancel after a duration/time.
Request-scoped values
WithValue carries identity, trace IDs — data that belongs to this request.
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel() // always — releases resources (Lesson 13)
select {
case <-ctx.Done(): return ctx.Err() // cancellation is COOPERATIVE — you must check
case r := <-work: return r
}
Done().
Your code must select on it (or pass ctx to functions that
do) to actually stop. A goroutine that ignores ctx runs to completion
regardless.
Conventions (interviewers check these)
- Pass
ctxas the first parameter; never store it in a struct. - Always
defer cancel()for a context you created — not calling it leaks resources. - Values are for request-scoped data (identity, trace) — not a back door for optional function parameters.
- A
Contextis safe for concurrent use by many goroutines.
Your codebase, from key to Kafka
Context values use private key types so packages can't collide, with typed getters/setters:
internal/golibs/interceptors/auth.go:346-477type UserIDKey int // unexported type → no cross-package key collision
ctx = interceptors.ContextWithJWTClaims(ctx, claims)
uid := interceptors.UserIDFromContext(ctx)
TraceCarrierAndClaimInfoFromMessageHeaders rebuilds them and
re-injects into a fresh ctx (consumer.go:114-131).
So a handler runs with the same identity the producer had — context propagation
across an async boundary.
context.WithTimeout(context.Background(), retryTimeout)
(consumer.go:112) — not from the incoming ctx.
Why? So an upstream cancellation can't kill an in-flight retry mid-work. The parent
consumer context still governs the retry loop; each attempt gets its own clean
deadline. Intentional, and a great "why did they do that?" talking point.
The Go Blog — "Go Concurrency Patterns: Context"
The canonical explanation of cancellation propagation and request-scoped values.
Then skim the context package docs for the API.
Check yourself (from memory)
Q1. context.Context's three jobs are…
Q2. Cancellation via context is…
Done(); nothing stops unless
code selects on it or passes ctx down. Ignore ctx → runs to completion.
Q3. Context values should carry…
context.Background() instead of the incoming ctx?WithTimeout(context.Background(), retryTimeout),
while the parent consumer context still governs the overall retry loop (via
try.DoWithCtx selecting on Done()). A deliberate choice to
make retries robust — and a strong "why did they do that?" interview answer.context.WithValue misused (the anti-pattern) vs. our clean
private-key approach side by side? Ask me.
1. The Go Blog — Go Concurrency Patterns: Context; context package docs.