Go

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

All 35 Context questions