Go

Explain context errors: context.Canceled, DeadlineExceeded, and context.Cause.

Question 320HardGo 1.22 to 1.25

ctx.Err() returns nil while the context is active. After it ends, it returns context.Canceled (from an explicit cancel) or context.DeadlineExceeded (from a timeout). DeadlineExceeded implements Timeout() bool returning true, so it looks like a net.Error timeout. Operations such as http.Client.Do and db.QueryContext wrap these errors, so check them with errors.Is.

Go 1.20 added WithCancelCause and context.Cause, and Go 1.21 added WithTimeoutCause and WithDeadlineCause, so you can record why a context was cancelled while Err() keeps returning the standard values:

var ErrShutdown = errors.New("server shutting down")

ctx, cancel := context.WithCancelCause(context.Background())
cancel(ErrShutdown)

fmt.Println(ctx.Err())                             // context canceled
fmt.Println(context.Cause(ctx))                    // server shutting down
fmt.Println(errors.Is(ctx.Err(), context.Canceled)) // true

tctx, stop := context.WithTimeoutCause(context.Background(),
	50*time.Millisecond, errors.New("payment API too slow"))
defer stop()
<-tctx.Done()
fmt.Println(tctx.Err(), "|", context.Cause(tctx))
// context deadline exceeded | payment API too slow

In practice, don't log a client-cancelled request (context.Canceled) as a server error, and map DeadlineExceeded to 504 or retry logic. context.AfterFunc (Go 1.21) runs cleanup when a context ends.

More on Error Handling & panics

All 37 Error Handling & panics questions