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
JsonRpcResponsewith 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.
| Model | Trip condition |
|---|---|
FailureModel::SlidingWindow | failure_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(), andmetrics() - Metrics integration via
metricsfeature - Tracing support via
tracingfeature
§Feature Flags
metrics: enables metrics collection using themetricscratetracing: enables logging and tracing using thetracingcrateserde: enablesSerializeforCircuitStateandCircuitMetrics
§Examples
See the examples/ directory for complete working examples:
circuitbreaker_example.rs- Basic usage with state transitionscircuitbreaker_fallback.rs- Fallback strategies for graceful degradationcircuitbreaker_health_check.rs- Health check endpoints and monitoring
Modules§
- classifier
- Custom failure classifiers for circuit breaker evaluation. Failure classification for circuit breaker decisions.
Structs§
- Circuit
Breaker - A Tower Service that applies circuit breaker logic to an inner service.
- Circuit
Breaker Config - Configuration for the circuit breaker pattern.
- Circuit
Breaker Config Builder - Builder for configuring and constructing a circuit breaker.
- Circuit
Breaker Handle - A read-only handle for observing circuit breaker state.
- Circuit
Breaker Layer - Circuit
Breaker With Fallback - A circuit breaker with a configured fallback handler.
- Circuit
Metrics - Snapshot of circuit breaker metrics for observability.
- Default
Classifier - Default failure classifier that treats all errors as failures.
- FnClassifier
- A failure classifier backed by a closure.
Enums§
- Circuit
Breaker Error - Errors returned by the
CircuitBreakerservice. - Circuit
Breaker Event - Events emitted by the circuit breaker pattern.
- Circuit
State - Represents the state of the circuit breaker.
- Failure
Model - Trip-condition model used by the circuit breaker.
- Sliding
Window Type - Type of sliding window used for tracking calls.
Traits§
- Failure
Classifier - Trait for classifying whether a result represents a failure.
Functions§
- circuit_
breaker_ builder - Returns a new builder for a
CircuitBreakerLayer.