A gRPC request may pass through an endpoint, business logic, and a repository before it finishes. Pass its context.Context along that path so a deadline or cancellation can reach work started on the request’s behalf. Generated gRPC endpoint signatures already provide an incoming context; use it rather than starting over with a new one. section 10.3.3 section 10.6.2
context.Context is an interface with four methods: Deadline reports an expiration time, if set; Done returns a channel closed when the context ends; Err reports why it ended, or nil while it remains active; and Value retrieves an attached value. You normally use implementations supplied by Go’s context package, not implement the interface yourself. Check <-ctx.Done() to stop waiting, then use ctx.Err() to report the reason. section 10.6.1 section 10.6.3
Keep context ownership and propagation predictable:
- Create an application’s parent context with context.Background() in main. A function that receives a context should pass it onward or derive a child, not replace it with Background() or TODO(). section 10.6.2
- Accept context.Context as the first parameter, conventionally named ctx. Pass it explicitly instead of storing it in a struct. section 10.6.2
- Use context.WithDeadline for a specific expiration time or context.WithTimeout for a duration. Give separate remote calls their own child contexts when their time limits need separate control. section 10.6.2 section 10.6.3
- Reserve context values for noncritical identifiers, such as request or monitoring IDs. Pass business inputs as explicit parameters so a function’s requirements remain visible. section 10.6.1
Bound a repository call
After validating a habit, derive a context for its database operation: dbCtx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() err := db.Add(dbCtx, h) The repository receives the child context, not a fresh background context. It must observe cancellation for the limit to interrupt its work—for example, by selecting on <-dbCtx.Done() and returning dbCtx.Err(). Choose a timeout that accounts for the latency of the operation. section 10.6.3 section 10.3.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
},
A timeout test makes the mocked repository wait longer than the 100 ms limit, then expects context.DeadlineExceeded. When an exact child context is unpredictable, minimock.AnyContext can match the argument in ordinary mock expectations; that match alone does not test the timeout. section 10.6.3






