How it works
The explanation, with examples you can run.
Deriving and cancelling
package main import ( "context" "errors" "fmt" "time") func fetch(ctx context.Context) (string, error) { select { case <-time.After(200 * time.Millisecond): // pretend this is a slow call return "data", nil case <-ctx.Done(): return "", ctx.Err() }} func main() { ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() // always release the timer and children _, err := fetch(ctx) fmt.Println(err) fmt.Println(errors.Is(err, context.DeadlineExceeded)) parent, stop := context.WithCancel(context.Background()) child, cancelChild := context.WithTimeout(parent, time.Hour) defer cancelChild() stop() // cancelling the parent cancels every child <-child.Done() fmt.Println(child.Err())}It prints context deadline exceeded, true and context canceled.
context.Background()is the root, formain, tests and start-up. In a server, user.Context()from the request instead;net/httpcancels it when the client disconnects or the handler returns.- A child can only shorten its parent's deadline, never extend it. A one-hour timeout under a parent with 50 ms left still ends in 50 ms.
Done()returns a channel that is closed on cancellation, so aselectcan wait on it next to real work. After it is closed,Err()returnscontext.Canceledorcontext.DeadlineExceeded.- Calling the cancel function releases resources. Forgetting it leaks the child until the parent ends;
go vet'slostcancelcheck catches most cases.
Using it well
- Take
ctx context.Contextas the first parameter, and don't store it in a struct: a context belongs to one operation. - Pass it to everything that can block:
http.NewRequestWithContext,db.QueryContext, gRPC calls. Those libraries close connections or abort queries when it is cancelled. - Set one deadline at the edge (when the request arrives, or before a batch job's call) and let it flow down. Per-call timeouts deeper in should only make it shorter.
- In a loop that does work in steps, check
ctx.Err()between steps, orselectonctx.Done()next to each blocking send, so a cancelled job stops within one step rather than running to the end. errgroup.WithContext(ingolang.org/x/sync/errgroup) runs several goroutines and cancels their shared context as soon as one returns an error: the usual way to fan out calls and fail fast.context.WithValueis for request-scoped data that crosses API boundaries, such as a trace ID. It is not a way to pass optional parameters.
Newer helpers: WithCancelCause and context.Cause (Go 1.20) record why something was cancelled; WithoutCancel (Go 1.21) keeps values but drops cancellation, useful for clean-up that must finish after the request ends; AfterFunc (Go 1.21) runs a function when a context is done.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.