Back to series

Context and cancellation

Propagate timeouts and shutdown signals through every layer of a Go service.

Table of Contents

Goroutines without cancellation leak. Context is Go’s answer: a value you pass down the call chain that carries deadlines, cancel signals, and request-scoped data.

The pattern

func handler(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
    defer cancel()

    data, err := fetch(ctx, "https://example.com")
    if err != nil {
        http.Error(w, "upstream timeout", http.StatusGatewayTimeout)
        return
    }
    w.Write(data)
}

func fetch(ctx context.Context, url string) ([]byte, error) {
    req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}

Rules that keep you out of trouble

  1. ctx is the first parameter, named ctx, always
  2. Call cancel() — defer cancel() the moment you create a derived context
  3. Never store contexts in structs; pass them explicitly
  4. Select on ctx.Done() inside loops that can run long

Combined with bounded channels from part 1, this is the foundation of services that shut down cleanly.