error_forge/recovery/
circuit_breaker.rs

1use std::sync::{Arc, Mutex};
2use std::time::{Duration, Instant};
3use crate::recovery::RecoveryResult;
4
5/// Represents the current state of a circuit breaker
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum CircuitState {
8    /// Circuit is closed and operations are allowed to execute
9    Closed,
10    
11    /// Circuit is open and operations will fail fast
12    Open,
13    
14    /// Circuit is partially open, allowing a test request
15    HalfOpen,
16}
17
18/// Configuration for a circuit breaker
19#[derive(Clone)]
20pub struct CircuitBreakerConfig {
21    /// Number of failures required to open the circuit
22    pub failure_threshold: usize,
23    
24    /// Time window in milliseconds to count failures
25    pub failure_window_ms: u64,
26    
27    /// Time in milliseconds that the circuit stays open before trying again
28    pub reset_timeout_ms: u64,
29}
30
31impl Default for CircuitBreakerConfig {
32    fn default() -> Self {
33        Self {
34            failure_threshold: 5,
35            failure_window_ms: 60000, // 1 minute
36            reset_timeout_ms: 30000,  // 30 seconds
37        }
38    }
39}
40
41struct CircuitBreakerInner {
42    config: CircuitBreakerConfig,
43    state: CircuitState,
44    failures: Vec<Instant>,
45    last_state_change: Instant,
46}
47
48/// Circuit breaker implementation to prevent cascading failures
49///
50/// The circuit breaker tracks failures and "trips" after a threshold is reached,
51/// preventing further calls and allowing the system to recover.
52pub struct CircuitBreaker {
53    name: String,
54    inner: Arc<Mutex<CircuitBreakerInner>>,
55}
56
57impl CircuitBreaker {
58    /// Create a new circuit breaker with the given name and default configuration
59    pub fn new(name: impl Into<String>) -> Self {
60        Self::with_config(name, CircuitBreakerConfig::default())
61    }
62    
63    /// Create a new circuit breaker with custom configuration
64    pub fn with_config(name: impl Into<String>, config: CircuitBreakerConfig) -> Self {
65        Self {
66            name: name.into(),
67            inner: Arc::new(Mutex::new(CircuitBreakerInner {
68                config,
69                state: CircuitState::Closed,
70                failures: Vec::new(),
71                last_state_change: Instant::now(),
72            })),
73        }
74    }
75    
76    /// Get the current state of the circuit breaker
77    pub fn state(&self) -> CircuitState {
78        let inner = self.inner.lock().unwrap();
79        inner.state
80    }
81    
82    /// Get the name of the circuit breaker
83    pub fn name(&self) -> &str {
84        &self.name
85    }
86    
87    /// Execute a function protected by the circuit breaker
88    pub fn execute<F, T, E>(&self, f: F) -> RecoveryResult<T>
89    where
90        F: FnOnce() -> Result<T, E>,
91        E: std::error::Error + Send + Sync + 'static,
92    {
93        // First check if we can proceed with the call
94        let can_proceed = {
95            let mut inner = self.inner.lock().unwrap();
96            self.update_state(&mut inner);
97            inner.state != CircuitState::Open
98        };
99        
100        // If circuit is open, fail fast
101        if !can_proceed {
102            return Err(Box::new(CircuitOpenError::new(&self.name)));
103        }
104        
105        // Execute the function
106        match f() {
107            Ok(value) => {
108                // Success, potentially reset circuit breaker
109                self.on_success();
110                Ok(value)
111            }
112            Err(err) => {
113                // Failure, record it and potentially trip circuit
114                self.on_failure();
115                Err(Box::new(err))
116            }
117        }
118    }
119    
120    /// Manually reset the circuit breaker to closed state
121    pub fn reset(&self) {
122        let mut inner = self.inner.lock().unwrap();
123        inner.state = CircuitState::Closed;
124        inner.failures.clear();
125        inner.last_state_change = Instant::now();
126    }
127    
128    /// Called when an operation succeeds
129    fn on_success(&self) {
130        let mut inner = self.inner.lock().unwrap();
131        if inner.state == CircuitState::HalfOpen {
132            // Successful test request, close the circuit
133            inner.state = CircuitState::Closed;
134            inner.failures.clear();
135            inner.last_state_change = Instant::now();
136        }
137    }
138    
139    /// Called when an operation fails
140    fn on_failure(&self) {
141        let mut inner = self.inner.lock().unwrap();
142        
143        if inner.state == CircuitState::HalfOpen {
144            // Failed during test request, reopen the circuit
145            inner.state = CircuitState::Open;
146            inner.last_state_change = Instant::now();
147            return;
148        }
149        
150        // Add the failure
151        let now = Instant::now();
152        inner.failures.push(now);
153        
154        // Remove old failures outside the window
155        let window_start = now - Duration::from_millis(inner.config.failure_window_ms);
156        inner.failures.retain(|&time| time >= window_start);
157        
158        // Check if threshold is reached
159        if inner.state == CircuitState::Closed && 
160           inner.failures.len() >= inner.config.failure_threshold {
161            // Trip the circuit
162            inner.state = CircuitState::Open;
163            inner.last_state_change = now;
164        }
165    }
166    
167    /// Update the circuit state based on timing
168    fn update_state(&self, inner: &mut CircuitBreakerInner) {
169        if inner.state == CircuitState::Open {
170            let now = Instant::now();
171            let elapsed = now.duration_since(inner.last_state_change);
172            
173            if elapsed >= Duration::from_millis(inner.config.reset_timeout_ms) {
174                // Reset timeout has elapsed, try half-open state
175                inner.state = CircuitState::HalfOpen;
176                inner.last_state_change = now;
177            }
178        }
179    }
180}
181
182/// Error returned when circuit is open
183#[derive(Debug)]
184pub struct CircuitOpenError {
185    circuit_name: String,
186}
187
188impl CircuitOpenError {
189    fn new(circuit_name: &str) -> Self {
190        Self {
191            circuit_name: circuit_name.to_string(),
192        }
193    }
194}
195
196impl std::fmt::Display for CircuitOpenError {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        write!(f, "Circuit '{}' is open, failing fast", self.circuit_name)
199    }
200}
201
202impl std::error::Error for CircuitOpenError {}