Concept · Go
Context and cancellation
One value that carries a deadline and a cancel signal through every call a request makes.
A request handler might query a database, call two services and start a goroutine. If the client disconnects or the deadline passes, all of that work should stop. Go's answer is context.Context: a value passed as the first argument of every function that does I/O. It carries:
- a cancel signal, through
ctx.Done(), a channel that closes when the work should stop; - a deadline, which fires that signal automatically;
- the reason, from
ctx.Err(), which iscontext.Canceledorcontext.DeadlineExceeded; - a few request-scoped values, like a trace ID. Use these sparingly.
Deriving and passing
Contexts form a tree. A child gets cancelled when its parent does, and it can only tighten the deadline, never extend it.
func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) // cancelled if client leaves, or after 2s
defer cancel() // always: releases the timer
order, err := h.store.GetOrder(ctx, id) // database/sql and pgx honour ctx
if errors.Is(err, context.DeadlineExceeded) { /* 504 */ }
// ...
}The standard library and good clients (net/http, database/sql, pgx, gRPC) watch the context and abort when it's done. Your own long loops and goroutines have to check it themselves:
for job := range jobs {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
process(job)
}Rules
- Pass it along explicitly; don't store it in a struct. Whatever a function does on the request's behalf should use the request's context.
- Always
defer cancel(). Skipping it leaks the context's resources until the parent is cancelled. - Never pass
nil. Usecontext.Background()at the top ofmainor a background job, andcontext.TODO()while you're refactoring. - A goroutine that ignores its context leaks. If the handler returns and nobody reads the goroutine's channel or cancels its work, it lives forever. Give every goroutine a way to stop.
- Use
context.WithoutCancel(ctx)(Go 1.21+) for work that must finish even if the request is cancelled, such as writing an audit record. It keeps the values but drops the cancellation.
This is how Go implements deadline propagation, and the same signal drives graceful shutdown.