Module lessons (5/5)
`context.Context`: cancellation and deadlines
The context package is the de-facto standard for propagating
cancellation, deadlines and scope-bound values along the call chain,
across multiple goroutines. All standard "networking" packages
(net/http, database/sql, os/exec) accept a context.Context as
their first parameter.
The interface
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}Done(): channel that is closed when the context is cancelled.Err(): reason for cancellation (context.Canceledorcontext.DeadlineExceeded).Deadline(): optional deadline.Value(): lookup of scope-bound values (use sparingly).
The four basic functions
ctx := context.Background() // radice: usalo in main, init, test
ctx := context.TODO() // "non so ancora": stub esplicito
ctx, cancel := context.WithCancel(parent) // cancel manuale
ctx, cancel := context.WithTimeout(parent, d) // scadenza relativa
ctx, cancel := context.WithDeadline(parent, t) // scadenza assoluta
ctx := context.WithValue(parent, key, value) // valore scope-boundAll derived forms return a cancel function: always call it
(ideally with defer) to release internal resources, even if the
context has already expired.
Timeout pattern
func fetchUser(id string) (*User, error) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
return queryUser(ctx, id) // queryUser deve rispettare ctx
}If queryUser takes more than 2 seconds, ctx.Done() is closed and
the function must abort the operation.
Respecting the context: select on Done()
func slowWork(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err() // canceled o deadline exceeded
case <-time.After(100 * time.Millisecond):
// un tick di lavoro
}
}
}The rule: anywhere your function might block for a long time, it must handle ctx.Done(). Otherwise the context is "powered on" for nothing.
First-parameter convention
func Fetch(ctx context.Context, url string) ([]byte, error) { ... }
// ^^^^^^^^^^^^^^^^^^^
// SEMPRE primo, mai dentro a una struct, mai opzionalectxALWAYS as the first parameter, namedctx.- Never put
Contextas a field of a struct (very rare exceptions). - Never pass
nil: usecontext.TODO()as a stub.
context.WithValue: sparingly
type userKey struct{}
ctx := context.WithValue(ctx, userKey{}, currentUser)
// più sotto:
if u, ok := ctx.Value(userKey{}).(*User); ok { ... }Guidelines:
- ONLY for request-scoped data (request ID, trace ID, user identity).
- NEVER for function parameters: pass them normally.
- Keys of a custom non-exported type to avoid collisions.
Propagation along the chain
func handleHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // context legato al ciclo di vita della richiesta
user, err := fetchUser(ctx, r.URL.Query().Get("id"))
if err != nil { ... }
// ...
}net/http automatically cancels r.Context() when the client
disconnects or when the handler returns. By propagating it to all
downstream calls, the whole tree of operations is cancelled in
cascade: no wasted work after disconnect.
Try it
Create a context with a 1-second timeout derived from context.Background() and remember to call cancel with defer.
Show hint
`context.WithTimeout(parent, d)` returns `(ctx, cancel)`; always call cancel.
Solution available after 3 attempts
Use <-ctx.Done() in a select to wait for the context to be cancelled.
Show hint
`ctx.Done()` is a channel that closes when the context is cancelled.
Solution available after 3 attempts
By convention, where does the ctx parameter go in a function signature?
func Fetch(???, url string) ([]byte, error)Recap
context.Contextpropagates cancellation, deadline and scope-bound values.Background()/TODO()as the root;WithCancel/WithTimeout/WithDeadlineto derive.- Always
defer cancel()to release resources. - "Long" functions must
selectonctx.Done()or pass ctx to standard packages. - Convention:
ctxas the FIRST parameter, never inside a struct. WithValueonly for request-scoped data (request ID, trace ID).- In
net/http,r.Context()is cancelled on client disconnect.