breaker-machines
High-performance circuit breaker implementation with state machine-based lifecycle management.
This crate provides a complete, standalone circuit breaker that can be used independently or as a performance backend for the breaker_machines Ruby gem.
Features
- State Machine: Built on state-machines with dynamic mode for runtime state transitions
- Thread-safe Storage: Sliding window event tracking with
RwLockfor concurrent access - Monotonic Time: Uses
Instantto prevent NTP clock skew issues - Builder API: Ergonomic fluent configuration interface
- Callbacks: Type-safe hooks for state transitions (
on_open,on_close,on_half_open) - Fallback Support: Return default values when circuit is open
- Rate-based Thresholds: Trip circuit based on failure percentage, not just absolute counts
- Exception Filtering: Classify which errors should trip the circuit using custom predicates
- Bulkheading: Limit concurrent operations to prevent resource exhaustion
- Jitter Support: Configurable jitter using chrono-machines to prevent thundering herd
- Storage Abstraction: Pluggable backends via
StorageBackendtrait - Zero-cost: Optimized for high-performance applications
Performance
Approximately 65x faster than Ruby-based storage for sliding window calculations (10,000 operations: 0.011s vs 0.735s).
Usage
Basic Example
use CircuitBreaker;
let mut circuit = builder
.failure_threshold
.failure_window_secs
.half_open_timeout_secs
.success_threshold
.on_open
.build;
// Execute with circuit protection
let result = circuit.call;
match result
// Check circuit state
if circuit.is_open
With Callbacks
use CircuitBreaker;
let mut circuit = builder
.failure_threshold
.on_open
.on_close
.build;
circuit.call?;
With Jitter (Thundering Herd Prevention)
use CircuitBreaker;
let mut circuit = builder
.failure_threshold
.half_open_timeout_secs
.jitter_factor // 10% jitter = 90-100% of timeout
.on_open
.build;
// With jitter, multiple circuits won't retry simultaneously
// Prevents thundering herd problem in distributed systems
circuit.call?;
With Fallback (v0.2.0+)
use ;
let mut circuit = builder
.failure_threshold
.build;
// Provide a fallback when circuit is open
let result = circuit.call;
// Fallback is only called when circuit is Open
// Normal calls work as before: circuit.call(|| api_request())
Rate-based Thresholds (v0.2.0+)
use CircuitBreaker;
// Trip circuit when 50% of calls fail (modern approach)
let mut circuit = builder
.failure_rate // 50% failure rate threshold
.minimum_calls // Need at least 10 calls before evaluating rate
.disable_failure_threshold // Don't use absolute count
.build;
// Or combine both: whichever threshold is hit first opens the circuit
let mut circuit = builder
.failure_threshold // Absolute: 100 failures
.failure_rate // OR 30% failure rate
.minimum_calls // (after at least 20 calls)
.build;
Exception Filtering (v0.3.0+)
use ;
use Arc;
// Only trip circuit on server errors (5xx), ignore client errors (4xx)
let classifier = new;
let mut circuit = builder
.failure_threshold
.failure_classifier
.build;
// Client errors don't trip the circuit
circuit.call?;
assert!;
// Server errors do trip the circuit
for _ in 0..5
assert!;
Bulkheading (v0.3.0+)
use ;
// Limit concurrent operations to prevent resource exhaustion
let mut circuit = builder
.max_concurrency // Max 10 concurrent DB connections
.failure_threshold
.build;
// Up to 10 calls can run concurrently
let result = circuit.call;
match result
Bulkheading is especially useful for:
- Database connection pools: Prevent connection exhaustion
- API rate limiting: Stay within provider limits
- Thread pool protection: Avoid starvation in executors
- Memory-intensive operations: Limit parallel processing
Custom Storage Backend
use ;
use Arc;
// Default in-memory storage with event tracking
let storage = new;
let mut circuit = builder
.storage
.build;
NullStorage for Testing/Benchmarking
use ;
use Arc;
// No-op storage: discards all events, returns zero counts
// Useful for testing state machine logic without storage overhead
let storage = new;
let mut circuit = builder
.storage
.build;
// Circuit will never open (no failure tracking)
circuit.call?;
assert!; // Still closed
State Machine
The circuit breaker implements a state machine with three states:
Closed → Open → HalfOpen → Closed
↑ ↓
└─────────────────┘
- Closed: Normal operation, tracking failures
- Open: Circuit tripped, rejecting calls immediately
- HalfOpen: Testing recovery with limited requests
Transitions are guarded by configurable thresholds and timeouts.
Architecture
- Dynamic Mode: Uses runtime state dispatch via
state-machinescrate - Guards: Validate transitions based on failure counts and timeouts
- State Data: Tracks
opened_attimestamps and success counters - Context: Shared circuit name, config, and storage across states
Ruby FFI Integration
This crate can be used as a high-performance backend for Ruby applications via Magnus FFI bindings. See the parent breaker_machines gem for Ruby usage.
Examples
See examples/ directory for more usage patterns:
basic.rs- Simple circuit with builder API and callbacks
Run examples with:
Testing
All tests use the dynamic state machine with proper guard validation.
License
MIT