Skip to main content

Crate tower_resilience_circuitbreaker

Crate tower_resilience_circuitbreaker 

Source
Expand description

Circuit breaker pattern for Tower services.

A circuit breaker prevents cascading failures by monitoring service calls and temporarily blocking requests when the failure rate exceeds a threshold.

§States

  • Closed: Normal operation, all requests pass through
  • Open: Circuit is tripped, requests are rejected immediately
  • Half-Open: Testing if service has recovered, limited requests allowed

§Usage

§Basic Usage with ServiceBuilder

The circuit breaker layer can be used directly with ServiceBuilder without any type parameters:

use tower_resilience_circuitbreaker::CircuitBreakerLayer;
use tower::{ServiceBuilder, service_fn};

// No type parameters needed!
let circuit_breaker = CircuitBreakerLayer::builder()
    .failure_rate_threshold(0.5)
    .sliding_window_size(100)
    .build();

let service = ServiceBuilder::new()
    .layer(circuit_breaker)
    .service(service_fn(|req: String| async move {
        Ok::<String, std::io::Error>(req)
    }));

§With Fallback Handler

Use layer_fn() to get access to the CircuitBreaker service for setting a fallback:

use tower_resilience_circuitbreaker::CircuitBreakerLayer;
use tower::service_fn;
use futures::future::BoxFuture;

let layer = CircuitBreakerLayer::builder()
    .failure_rate_threshold(0.5)
    .build();

let svc = service_fn(|req: String| async move {
    Ok::<String, ()>(req)
});

let mut service = layer.layer_fn(svc)
    .with_fallback(|_req: String| -> BoxFuture<'static, Result<String, ()>> {
        Box::pin(async { Ok("fallback".to_string()) })
    });

§Custom Failure Classification

By default, all errors are counted as failures. You can customize this:

use tower_resilience_circuitbreaker::CircuitBreakerLayer;
use tower::{ServiceBuilder, service_fn};
use std::io::{Error, ErrorKind};

let layer = CircuitBreakerLayer::builder()
    .failure_classifier(|result: &Result<String, Error>| {
        match result {
            Ok(_) => false,
            // Don't count timeouts as failures
            Err(e) if e.kind() == ErrorKind::TimedOut => false,
            Err(_) => true,
        }
    })
    .build();

let service = ServiceBuilder::new()
    .layer(layer)
    .service(service_fn(|req: String| async move { Ok::<_, Error>(req) }));

§Services with Error = Infallible

Many services encode errors in the response body rather than returning Err:

  • HTTP services returning error status codes as Ok(Response)
  • gRPC services with status codes in the response
  • MCP servers returning JsonRpcResponse with error fields

Use classify_response() for these services:

use tower_resilience_circuitbreaker::CircuitBreakerLayer;


// Classify failures based on response content
let layer = CircuitBreakerLayer::builder()
    .classify_response(|response: &Response| response.status() >= 500)
    .build();

This is simpler than failure_classifier() because you don’t need to handle the Err case (which can never occur with Error = Infallible).

§Failure Models

The trip condition is selectable via FailureModel. The default is FailureModel::SlidingWindow (failure rate over a count- or time-based window). The alternative FailureModel::ConsecutiveFailures trips after k failures in a row with no intervening success – this matches the default behavior of the Elixir ex_resilience library and is common in agent / LLM-retry contexts.

ModelTrip condition
FailureModel::SlidingWindowfailure_count / total_count >= failure_rate_threshold over the configured window (the default)
FailureModel::ConsecutiveFailures { k }k classified failures in a row with no intervening success

Slow-call detection (slow_call_duration_threshold / slow_call_rate_threshold) continues to evaluate against the sliding-window stats in both models, and can open the circuit independently of the failure model.

use tower_resilience_circuitbreaker::{CircuitBreakerLayer, FailureModel};

// Trip after 5 consecutive failures.
let layer = CircuitBreakerLayer::builder()
    .consecutive_failures(5)
    .build();

// Equivalent via the general setter.
let layer = CircuitBreakerLayer::builder()
    .failure_model(FailureModel::ConsecutiveFailures { k: 5 })
    .build();

§Time-Based Sliding Window

Use time-based windows instead of count-based:

use tower_resilience_circuitbreaker::{CircuitBreakerLayer, SlidingWindowType};
use tower::{ServiceBuilder, service_fn};
use std::time::Duration;

let layer = CircuitBreakerLayer::builder()
    .failure_rate_threshold(0.5)
    .sliding_window_type(SlidingWindowType::TimeBased)
    .sliding_window_duration(Duration::from_secs(60))  // Last 60 seconds
    .minimum_number_of_calls(10)
    .build();

let service = ServiceBuilder::new()
    .layer(layer)
    .service(service_fn(|req: String| async move { Ok::<_, ()>(req) }));

§Slow Call Detection

Open circuit based on slow calls:

use tower_resilience_circuitbreaker::CircuitBreakerLayer;
use tower::{ServiceBuilder, service_fn};
use std::time::Duration;

let layer = CircuitBreakerLayer::builder()
    .failure_rate_threshold(1.0)  // Don't open on failures
    .slow_call_duration_threshold(Duration::from_secs(2))
    .slow_call_rate_threshold(0.5)  // Open at 50% slow calls
    .sliding_window_size(100)
    .build();

let service = ServiceBuilder::new()
    .layer(layer)
    .service(service_fn(|req: String| async move { Ok::<_, ()>(req) }));

§Event Listeners

Monitor circuit breaker behavior:

use tower_resilience_circuitbreaker::CircuitBreakerLayer;
use tower::{ServiceBuilder, service_fn};

let layer = CircuitBreakerLayer::builder()
    .failure_rate_threshold(0.5)
    .sliding_window_size(100)
    .on_state_transition(|from, to| {
        println!("Circuit breaker: {:?} -> {:?}", from, to);
    })
    .on_call_permitted(|state| {
        println!("Call permitted in state: {:?}", state);
    })
    .on_call_rejected(|| {
        println!("Call rejected - circuit open");
    })
    .on_slow_call(|duration| {
        println!("Slow call detected: {:?}", duration);
    })
    .build();

let service = ServiceBuilder::new()
    .layer(layer)
    .service(service_fn(|req: String| async move { Ok::<_, ()>(req) }));

§Error Handling

use tower_resilience_circuitbreaker::{CircuitBreakerLayer, CircuitBreakerError};
use tower::{Service, ServiceBuilder, service_fn};

let layer = CircuitBreakerLayer::builder().build();
let mut service = ServiceBuilder::new()
    .layer(layer)
    .service(service_fn(|req: String| async move { Ok::<_, ()>(req) }));

match service.call("request".to_string()).await {
    Ok(response) => println!("Success: {}", response),
    Err(CircuitBreakerError::OpenCircuit) => {
        eprintln!("Circuit breaker is open");
    }
    Err(CircuitBreakerError::Inner(e)) => {
        eprintln!("Service error: {:?}", e);
    }
}

§State Inspection and Observability

The circuit breaker provides both synchronous and asynchronous methods for inspecting state and metrics:

use tower_resilience_circuitbreaker::{CircuitBreakerLayer, CircuitState};
use tower::service_fn;

let layer = CircuitBreakerLayer::builder().build();
let svc = service_fn(|req: String| async move { Ok::<String, ()>(req) });
let breaker = layer.layer_fn(svc);

// Sync state inspection (no await needed, lock-free)
match breaker.state_sync() {
    CircuitState::Closed => println!("Healthy"),
    CircuitState::Open => println!("Circuit open - return 503"),
    CircuitState::HalfOpen => println!("Recovering"),
}

// Convenience method
if breaker.is_open() {
    // Return error response
}

// Detailed metrics (async, requires lock)
let metrics = breaker.metrics().await;
println!("Failure rate: {:.1}%", metrics.failure_rate * 100.0);
println!("Total calls: {}", metrics.total_calls);

§Health Check Integration

use tower_resilience_circuitbreaker::CircuitBreakerLayer;
use tower::service_fn;

let layer = CircuitBreakerLayer::builder().build();
let svc = service_fn(|req: String| async move { Ok::<String, ()>(req) });
let breaker = layer.layer_fn(svc);

// Simple health status
let status = breaker.health_status(); // "healthy", "degraded", or "unhealthy"

// HTTP status code for health endpoints
let http_status = breaker.http_status(); // 200 or 503

§Features

  • Count-based and time-based sliding windows
  • Configurable failure rate threshold
  • Slow call detection and rate threshold
  • Half-open state for gradual recovery
  • Event system for observability
  • Optional fallback handling
  • Manual state control (force_open, force_closed, reset)
  • Sync state inspection with state_sync(), is_open(), and metrics()
  • Metrics integration via metrics feature
  • Tracing support via tracing feature

§Feature Flags

  • metrics: enables metrics collection using the metrics crate
  • tracing: enables logging and tracing using the tracing crate
  • serde: enables Serialize for CircuitState and CircuitMetrics

§Examples

See the examples/ directory for complete working examples:

  • circuitbreaker_example.rs - Basic usage with state transitions
  • circuitbreaker_fallback.rs - Fallback strategies for graceful degradation
  • circuitbreaker_health_check.rs - Health check endpoints and monitoring

Modules§

classifier
Custom failure classifiers for circuit breaker evaluation. Failure classification for circuit breaker decisions.

Structs§

CircuitBreaker
A Tower Service that applies circuit breaker logic to an inner service.
CircuitBreakerConfig
Configuration for the circuit breaker pattern.
CircuitBreakerConfigBuilder
Builder for configuring and constructing a circuit breaker.
CircuitBreakerHandle
A read-only handle for observing circuit breaker state.
CircuitBreakerLayer
CircuitBreakerWithFallback
A circuit breaker with a configured fallback handler.
CircuitMetrics
Snapshot of circuit breaker metrics for observability.
DefaultClassifier
Default failure classifier that treats all errors as failures.
FnClassifier
A failure classifier backed by a closure.

Enums§

CircuitBreakerError
Errors returned by the CircuitBreaker service.
CircuitBreakerEvent
Events emitted by the circuit breaker pattern.
CircuitState
Represents the state of the circuit breaker.
FailureModel
Trip-condition model used by the circuit breaker.
SlidingWindowType
Type of sliding window used for tracking calls.

Traits§

FailureClassifier
Trait for classifying whether a result represents a failure.

Functions§

circuit_breaker_builder
Returns a new builder for a CircuitBreakerLayer.