Recipe: Resilient Circuit Breaker & Exponential Backoff
Unreliable remote HTTP endpoints and database calls can cascade failures throughout your application. This recipe demonstrates how to combine the Circuit Breaker and Retry with Exponential Backoff patterns using xstate-patterns.
Statechart Topology
stateDiagram-v2
[*] --> Closed
state Closed {
[*] --> Idle
Idle --> Executing: CALL
Executing --> Idle: SUCCESS
Executing --> Tripping: FAILURE [failures >= threshold]
}
Closed --> Open: TRIP
state Open {
[*] --> Sleeping
Sleeping --> HalfOpen: TIMEOUT (after cooldown)
}
state HalfOpen {
[*] --> Probing
Probing --> Closed: PROBE_SUCCESS
Probing --> Open: PROBE_FAILURE
}
Rust Implementation
use std::time::Duration;
use xstate_patterns::{CircuitBreakerOptions, RetryOptions, backoff, circuit_breaker};
use xstate_statemachine::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Configure the Circuit Breaker pattern
let cb_options = CircuitBreakerOptions {
failure_threshold: 3,
recovery_timeout: Duration::from_millis(500),
success_threshold: 2,
};
let breaker_machine = circuit_breaker::build_machine(&cb_options)?;
// 2. Configure Retry with Exponential Backoff and Decorrelated Jitter
let retry_options = RetryOptions {
max_attempts: 4,
initial_interval: Duration::from_millis(50),
max_interval: Duration::from_millis(1000),
multiplier: 2.0,
randomization_factor: 0.1,
};
let retry_machine = backoff::build_retry_machine(&retry_options)?;
println!("Circuit breaker and retry statecharts assembled successfully.");
// Drive the circuit breaker interpreter
let mut cb_interp = SyncInterpreter::new(breaker_machine, MachineLogic::dynamic());
cb_interp.start()?;
assert_eq!(cb_interp.state_ids(), ["circuit_breaker.closed"]);
// Simulate 3 consecutive failures to trip the breaker
cb_interp.send("FAILURE")?;
cb_interp.send("FAILURE")?;
cb_interp.send("FAILURE")?;
assert_eq!(cb_interp.state_ids(), ["circuit_breaker.open"]);
// In 'open' state, calls fail fast immediately without hitting downstream servers
let fast_fail_receipt = cb_interp.send("CALL")?;
assert!(fast_fail_receipt.unhandled || cb_interp.matches("circuit_breaker.open"));
Ok(())
}
Production Characteristics
- Fail Fast: When the circuit is
Open, incoming requests are refused immediately (under 100 ns), shielding fragile backends from brownouts. - Probe Verification: In
HalfOpen, only limited test traffic passes through. If the probe succeeds twice (success_threshold: 2), the circuit automatically heals back toClosed. - Zero Allocations: Both pattern statecharts operate with pre-compiled static structures and bitset states.