Skip to main content

Timeout

The Timeout pattern bounds how long a caller waits on an operation, releasing threads and connections instead of blocking indefinitely on a hung dependency. Deadlines should be propagated through the call chain and tuned from observed latency percentiles.

Type
Resilience
When to Use
Remote Calls, Resource Protection, Latency Bounds, Slow Dependencies

An operation with no time limit can wait forever. A remote service that hangs, a lock that never releases, or a query that scans a huge table can hold a thread, a connection, and memory indefinitely. In a system serving concurrent requests, a handful of stuck calls can exhaust the thread pool and take down an otherwise healthy service. The Timeout pattern places an upper bound on how long any operation may run before it is abandoned and an error is returned.

How It Works

A timeout starts a clock when an operation begins. If the operation does not complete within the budget, it is cancelled and the caller receives a timeout error. The mechanics vary by platform:

  • Socket and HTTP timeouts: connect timeout, read/write timeout, and overall request timeout on the client.
  • Cooperative cancellation: a CancellationToken (.NET), context.Context deadline (Go), or AbortController signal (JavaScript) propagated through the call chain.
  • Deadline propagation: gRPC and many RPC frameworks pass a deadline downstream so each hop respects the remaining budget rather than restarting the clock.

Good designs distinguish connection timeouts from response timeouts and set both. They also propagate deadlines: if a request has 2 seconds left, downstream calls should not each be allowed 30.

When to Use It

Apply timeouts to every call that crosses a process or network boundary — HTTP, RPC, database, cache, and message broker calls. Apply them to lock acquisition and to any blocking wait. Effectively, any operation whose duration you do not fully control should have a timeout. Choose values from observed latency percentiles (e.g. a few multiples of p99), not guesses.

Trade-offs

A timeout set too low causes spurious failures on legitimately slow but successful operations, hurting availability and triggering needless retries. Set too high, it fails to protect resources. Cancellation is not always clean: the downstream work may continue running even after the caller gives up, wasting capacity and, for writes, leaving uncertain state. This makes timeouts and idempotency complementary.

Related Patterns

Timeouts are foundational to circuit-breaker (which counts timeouts as failures), retry-with-backoff (each attempt is bounded), bulkhead-pattern (isolating the resources a timeout protects), and fail-fast (returning errors quickly rather than blocking).

Example

Go with a context deadline:

ctx, cancel := context.WithTimeout(parent, 2*time.Second)
defer cancel()
resp, err := client.Do(req.WithContext(ctx))
if errors.Is(err, context.DeadlineExceeded) {
  return ErrUpstreamSlow
}

The deadline rides through the call chain, so a slow downstream cancels automatically.