Go cookbook · Intermediate

Use context deadlines for outbound calls

A slow dependency makes requests pile up because calls never time out.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Derive a context with a deadline from the incoming request's context for every outbound call, so calls stop when the client disconnects or the budget runs out. Configure the HTTP client's transport timeouts too.

Solution

package pricing

import (
	"context"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"time"
)

var client = &http.Client{
	Transport: &http.Transport{
		DialContext:           (&net.Dialer{Timeout: 2 * time.Second}).DialContext,
		TLSHandshakeTimeout:   3 * time.Second,
		ResponseHeaderTimeout: 5 * time.Second,
		MaxIdleConnsPerHost:   20,
	},
}

type Quote struct {
	PriceCents int `json:"price_cents"`
}

func FetchQuote(ctx context.Context, sku string) (Quote, error) {
	ctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://pricing.internal/quotes/"+sku, nil)
	if err != nil {
		return Quote{}, err
	}
	resp, err := client.Do(req)
	if err != nil {
		return Quote{}, fmt.Errorf("fetch quote %s: %w", sku, err) // includes context.DeadlineExceeded
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return Quote{}, fmt.Errorf("fetch quote %s: status %d", sku, resp.StatusCode)
	}
	var q Quote
	return q, json.NewDecoder(resp.Body).Decode(&q)
}

// In a handler: quote, err := FetchQuote(r.Context(), sku)

Pitfalls

  • Using context.Background() inside handlers, so work continues after the client has gone.
  • Forgetting defer cancel(), leaking timers until the parent context ends.
  • http.DefaultClient in production: no timeouts at all.

Variations

  • errors.Is(err, context.DeadlineExceeded) distinguishes timeouts for metrics and fallbacks.
  • Use context.WithoutCancel(ctx) for work that must finish after the request (audit logs).

Further reading: context package

Reference: https://pkg.go.dev/context

More Go recipes

Was this page helpful?