Day 075 — context Cancellation & Propagation¶
Month 3 · Week 3 · ⬅ Day 074 · Day 076 ➡ · Journal index
🎯 Learning Objective¶
Use context.Context to carry a cancellation signal and deadline down a call tree, so cancelling a parent stops all derived work, and learn the conventions that keep it leak-free.
📚 Topics¶
WithCancel,WithTimeout,WithDeadline;Done(),Err()- Propagation: parent cancel → every child context cancels
- Conventions: ctx is the first param; always
defer cancel()
📖 Reading / Sources¶
📝 Notes¶
- A
Contextcarries a cancellation signal, an optional deadline, and request-scoped values across API boundaries and goroutines → [[context]]. - Derive children with
context.WithCancel/WithTimeout/WithDeadline(parent). Each returns acancelfunction — alwaysdefer cancel(), even on the timeout variants, to release the context's resources (a timer, a goroutine) immediately → [[context-cancellation]]. - Propagation is one-way and downward: cancelling a parent cancels every context derived from it; cancelling a child does not affect the parent. Building a tree means one
cancel()can tear down a whole subtree. ctx.Done()returns a channel closed on cancellation;selecton it.ctx.Err()then explains why:context.Canceled(explicit cancel) orcontext.DeadlineExceeded(timeout/deadline) → [[ctx-err]].- Conventions (enforced by
go vet/lint): passctxas the first parameter, namedctx; never store aContextin a struct field for later; never pass anilContext — usecontext.TODO()if you truly don't have one yet → [[context-first-param]]. WithTimeout(parent, d)is justWithDeadline(parent, time.Now().Add(d)). The context cancels itself when the deadline passes — no goroutine needed to fire it.context.Valueis for request-scoped data (trace IDs, auth) crossing API boundaries — not for passing optional function parameters. Overuse makes data flow invisible → [[context-values]].- Cancellation is cooperative: a context can't forcibly stop a goroutine. The goroutine must check
ctx.Done()(or passctxto a blocking call that does). Ignored contexts = leaks → [[goroutine-leak]].
💻 Code Examples¶
// Race the work against the context: a cancelled/expired ctx returns its error
// immediately instead of blocking for the full duration.
func slowWork(ctx context.Context, d time.Duration) error {
select {
case <-time.After(d):
return nil
case <-ctx.Done():
return ctx.Err() // context.Canceled or context.DeadlineExceeded
}
}
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel() // release the timer even if slowWork returns early
err := slowWork(ctx, 2*time.Second)
fmt.Println(errors.Is(err, context.DeadlineExceeded)) // true
Full code:
examples/month-03/context-cancel/main.go· Run:go run ./examples/month-03/context-cancel
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Semaphore.AcquireCtx (cancellable acquire) |
✅ | exercises/month-03/week-3/semaphore/ |
Bounded Map returning ctx.Err() on cancel |
✅ | exercises/month-03/week-3/boundedmap/ |
🐛 Mistakes Made¶
- Created a
WithTimeoutcontext and forgotdefer cancel()→go vetflagged "the cancel function is not used"; left a timer running until the deadline. Added the defer. - Compared
err == context.DeadlineExceededdirectly; fine here, but switched toerrors.Isso wrapped errors still match → [[error-wrapping]].
❓ Open Questions¶
- How do I cancel a group of goroutines and collect the first error in one shot? (That's
errgroup— Day 076.)
🧠 Active Recall (answer without looking)¶
-
Q: What's the difference between
context.Canceledandcontext.DeadlineExceeded, and how do you read which occurred?
A
`context.Canceled` means someone called the `cancel` func; `context.DeadlineExceeded` means the deadline/timeout passed. After `<-ctx.Done()`, call `ctx.Err()` (compare with `errors.Is`) to see which. -
Q: Why must you call
cancel()even when you usedWithTimeout?
A
The context holds resources (a timer and an internal goroutine) until either the deadline fires or `cancel` is called. `defer cancel()` releases them promptly instead of leaking until the deadline.
🪶 Feynman Reflection¶
A context is a "stop work" walkie-talkie handed down a chain of command. The boss can press cancel (or set a timer that presses it); every subordinate holding the same channel hears the click via ctx.Done() and stops. But it's cooperative — a worker wearing headphones (never checking Done()) keeps going, which is exactly how goroutine leaks happen.
🕳️ Knowledge Gaps¶
- Wiring
contextthrough real network calls (HTTP/gRPC) with deadlines — comes up in the API month.
✅ Summary¶
I can create and propagate contexts, distinguish Canceled vs DeadlineExceeded, follow the ctx-first/defer cancel() conventions, and remember cancellation is cooperative — workers must check Done().
⏭️ Next Steps / Prep for Tomorrow¶
- Day 076:
errgroupfor grouped cancellation + first-error, and rate limiting.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): context cancellation, deadlines & propagation (day 075)