What values can ctx.Err() return, and how should you check them?
Question 264MediumGo 1.22 to 1.25
Err() returns nil while Done() is still open. Once it is closed, Err() returns exactly one of two sentinels:
context.Canceled: someone called a cancel function, or a parent was canceled.context.DeadlineExceeded: a deadline or timeout expired, here or in an ancestor.
Libraries usually wrap these (for example *url.Error from http.Client, or driver errors), so check with errors.Is, not ==. DeadlineExceeded also has a Timeout() bool method that returns true, so code that checks net.Error-style timeouts treats it as a timeout.
resp, err := client.Do(req)
switch {
case errors.Is(err, context.DeadlineExceeded):
return fmt.Errorf("upstream too slow: %w", err) // e.g. 504
case errors.Is(err, context.Canceled):
return err // caller went away; usually don't log as an error
case err != nil:
return fmt.Errorf("upstream: %w", err)
}
defer resp.Body.Close()
Gotcha: tell "client disconnected" (Canceled) apart from "we were too slow" (DeadlineExceeded) in metrics and alerts. Logging every canceled request as a 500 creates noise during deploys and when mobile clients drop.
More on Context
- Q262WithTimeout vs WithDeadline: what is the difference, and what happens when a child asks for a later deadline than its parent?
- Q263What does this print?
- Q265What is WithCancelCause, and how does ctx.Err() differ from context.Cause(ctx)?
- Q266What does this print? (WithTimeoutCause and its cancel function)
- Q267Why should context keys use an unexported custom type instead of a string? Show the idiomatic pattern.
- Q268What does this print? (context value keys)