Day 040 — context: Timeouts & Cancellation¶
Month 2 · Week 2 · ⬅ Day 039 · Day 041 ➡ · Journal index
🎯 Learning Objective¶
Propagate deadlines and cancellation across API boundaries with context.Context: create derived contexts, honor cancellation in blocking code, and detect why a context ended.
📚 Topics¶
context.Background/TODO· the derivation treeWithCancel/WithTimeout/WithDeadline·cancel()disciplinectx.Done(),ctx.Err(),context.Causeerrors.Is(err, context.DeadlineExceeded / Canceled)WithValue(sparingly) · context-first convention
📖 Reading / Sources¶
-
contextpackage docs - The Go Blog — Go Concurrency Patterns: Context
- Go wiki — contexts and cancellation
📝 Notes¶
- A
Contextcarries a deadline, a cancellation signal, and request-scoped values across API boundaries and goroutines. By convention it is the first parameter, namedctx, and is never stored in a struct (pass it explicitly) → [[context-first-param]]. - Start from a root:
context.Background()(main/top of request) orcontext.TODO()(placeholder while plumbing). Derive children: WithCancel(parent)→ manualcancel().WithTimeout(parent, d)→ auto-cancels afterd.WithDeadline(parent, t)→ auto-cancels at wall timet.- Always call
cancel(usuallydefer cancel()), even on the timeout/deadline forms — it releases the timer and child resources immediately. Leakingcancelis a context (and goroutine) leak vet will warn about → [[always-call-cancel]]. - Cancellation propagates down the tree: cancelling a parent cancels all descendants; cancelling a child doesn't touch the parent.
- A context does not stop code by itself — blocking work must select on
ctx.Done()(or passctxto a context-aware API likehttp.NewRequestWithContextordb.QueryContext). A baretime.Sleepignores cancellation → [[honor-ctx-done]]. - After
Done()is closed,ctx.Err()tells you why:context.Canceledorcontext.DeadlineExceeded. Check witherrors.Is(err, context.DeadlineExceeded)since wrappers nest it.context.Cause(ctx)(Go 1.20+) returns a richer cause when set viaWithCancelCause. context.WithValuecarries request-scoped data (request ID, auth) only — not optional function params. Use an unexported key type to avoid collisions; never pass config through it → [[context-value-sparingly]].
💻 Code Examples¶
// Per-call deadline that auto-cancels; defer cancel() always.
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
select {
case res := <-slowWork(ctx):
use(res)
case <-ctx.Done():
// ctx.Err() is context.DeadlineExceeded or context.Canceled
return ctx.Err()
}
Full code:
examples/month-02/http-context/main.go· Run:go run ./examples/month-02/http-context
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Do(ctx, attempts, delay, fn) — cancellable retry/backoff |
✅ | exercises/month-02/week-2/retry |
🐛 Mistakes Made¶
- Created a
WithTimeoutcontext but forgotdefer cancel()→go vetflagged "lost cancel"; the timer lingered. - Expected
WithTimeoutto kill atime.Sleep— it doesn't; I had toselectonctx.Done()instead. - Compared
err == context.DeadlineExceededafter wrapping with%wand it failed — switched toerrors.Is.
❓ Open Questions¶
- When is
WithCancelCause/context.Causeworth the extra plumbing over a plainWithCancel?
🧠 Active Recall (answer without looking)¶
-
Q: Does passing a
ctxwith a 50 ms timeout to a function automatically stop its work after 50 ms?
A
No. A context only *signals*; it can't preempt code. The function must select on `ctx.Done()` or call a context-aware API. Otherwise it runs to completion regardless of the deadline. -
Q: Why call
cancel()even when you usedWithTimeout(which auto-cancels)?
A
To release the context's timer and child resources *immediately* when the work finishes early, instead of waiting for the deadline. Skipping it leaks resources until timeout — `defer cancel()` is the idiom.
🪶 Feynman Reflection¶
A context is a "stop" signal you thread through every call in a request. Parents hand it to children; if a parent calls "stop" (or its timer runs out), everyone downstream hears it on ctx.Done(). But hearing isn't obeying — each piece of blocking work has to actually listen for that signal, otherwise it keeps going. And you always tidy up with cancel() so the alarm clock isn't left ticking.
🕳️ Knowledge Gaps¶
WithCancelCause/Causepatterns and when to surface causes to callers.- Propagating context cleanly through deeply layered libraries.
✅ Summary¶
I can derive timeout/cancel contexts, always defer cancel(), make blocking code honor ctx.Done(), and distinguish cancellation from deadline with errors.Is.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 041:
log/slogstructured logging — and attaching request context to log lines.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦🟦⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(exercises): context timeouts and cancellation (day 040)