Skip to content

Add circuit breaker middleware / policy #2986

@Mattbusel

Description

@Mattbusel

The gap

reqwest added retry policies in #2763, which is great. The next layer up — circuit breaking — still doesn't exist. The two patterns are complementary but distinct:

  • Retry: "this request failed, try again"
  • Circuit breaker: "this backend has been failing for 30 seconds, stop sending requests entirely until it recovers"

Without a circuit breaker, retry policies can make outages worse: every client keeps hammering a downed backend, retry storms pile up, and connection pools exhaust. The circuit breaker cuts this off at the source.

What this would look like

A ClientBuilder::circuit_breaker() method accepting a CircuitBreakerConfig, similar to how .retry() works:

let client = reqwest::Client::builder()
    .retry(RetryPolicy::default())
    .circuit_breaker(
        CircuitBreakerConfig::new()
            .failure_threshold(5)           // open after 5 failures
            .success_threshold(0.8)         // close when 80 % of probes succeed
            .timeout(Duration::from_secs(30)) // probe after 30 s
    )
    .build()?;

The three-state machine (Closed → Open → HalfOpen) is well understood. The key implementation choices:

  • Scope: per-Client instance (same base URL shares one breaker) or per-hostname? Per-hostname is more useful for multi-host clients.
  • Error classification: HTTP 5xx and connection errors trip the breaker; 4xx do not.
  • Retry-After header (issue Support Retry-After header #925): circuit breaker timeout could respect this header automatically.

Prior art in the Tokio ecosystem

I recently submitted tower-rs/tower#855 adding a CircuitBreaker Tower middleware. The implementation there is a Service<Request> wrapper — the same pattern could be adapted as a reqwest middleware layer or built into the client directly.

The full implementation with tests is available at that PR if it's useful as a reference.

Happy to implement this for reqwest if there's appetite — wanted to check the preferred API shape first.

— Matthew Busel

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions