Cancellation and timeouts with context in Go
Tested with: Go 1.26
Contents
When an HTTP request is canceled, the database query and external service calls started for that request should stop too. In Go, the mechanism for this is context.Context.
Pass context to every call
The most common mistake I see is using context.Background() inside a handler:
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) { // Wrong: the query keeps running even if the client closes the connection. user, err := h.repo.Find(context.Background(), r.PathValue("id")) // ...}The right way is to use the request’s own context:
user, err := h.repo.Find(r.Context(), r.PathValue("id"))Let the caller set the timeout
Every call to an external service should have an upper bound. Defining that bound where the call is made lets the reader understand the behavior at a glance.
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)defer cancel()
resp, err := h.payments.Charge(ctx, req)if errors.Is(err, context.DeadlineExceeded) { http.Error(w, "payment service did not respond", http.StatusGatewayTimeout) return}Skipping the defer cancel() line means resources are not released until the timeout expires.
Tip
The lostcancel check in go vet catches cancel functions that are never called. It’s worth running in CI.
Listen on ctx.Done() inside goroutines
A background goroutineGoroutine A lightweight concurrent unit of execution managed by the Go runtime. should exit when the context is canceled:
for { select { case <-ctx.Done(): return ctx.Err() case msg := <-messages: process(msg) }}You can run the example below to see how the timeout works:
package main
import ( "context" "fmt" "time")
func slowQuery(ctx context.Context) error { select { case <-time.After(2 * time.Second): return nil case <-ctx.Done(): return ctx.Err() }}
func main() { ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel()
err := slowQuery(ctx) fmt.Println("result:", err) // result: context deadline exceeded}Using goleak in tests to catch leaks is a good habit: