Context.Err reports why a Context ended: it returns an error after Done closes and nil while the Context remains active. Typical callers wait for cancellation with Done, then return ctx.Err(). section 10.6.1 section 10.6.3
Return a timeout error
A backend call can return whichever event happens first: select { case response := <-responseChan: return response, nil case <-ctx.Done(): return nil, ctx.Err() } When the Context reaches its deadline, the second case runs and Err supplies the cancellation reason, commonly context.DeadlineExceeded. section 10.6.3
Use Err with these practical rules:
- Check Done before calling Err; Err is nil until the Context has ended. section 10.6.1
- Test timeout behavior by making a mock wait on ctx.Done and return ctx.Err(). section 10.6.3
- Prefer context.Cause(ctx) when a Go 1.23 Context carries a specific cancellation cause, because Err can be ambiguous. section 10.6.3
select {
// Read from channel used by backend to communicate response
case response := <-responseChan:
return response, nil
// Check for deadline
case <-ctx.Done():
return nil, ctx.Err()
}
Inspect the compact select pattern that returns ctx.Err when a response does not arrive before cancellation. section 10.6.3
Listing 10.32 create_test.go: Testing the timeout
func TestCreate(t *testing.T) {
// ...
"db timeout": {
db: func(ctl *minimock.Controller) *mocks.HabitCreatorMock {
db := mocks.NewHabitCreatorMock(ctl)
db.AddMock.Set(
func(ctx context.Context, habit habit.Habit) error {
select {
// This tick is longer than a database call
case <-time.Tick(2 * time.Second):
return nil
case <-ctx.Done():
return ctx.Err()
}
})
return db
},
expectedErr: context.DeadlineExceeded, #1
},
Inspect a timeout test that overrides a mock database call and verifies context.DeadlineExceeded. section 10.6.3
