Skip to main content

error_forge/recovery/
circuit_breaker.rs

1use crate::recovery::RecoveryResult;
2use std::sync::{Arc, Mutex};
3use std::time::{Duration, Instant};
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        {
162            // Trip the circuit
163            inner.state = CircuitState::Open;
164            inner.last_state_change = now;
165        }
166    }
167
168    /// Update the circuit state based on timing
169    fn update_state(&self, inner: &mut CircuitBreakerInner) {
170        if inner.state == CircuitState::Open {
171            let now = Instant::now();
172            let elapsed = now.duration_since(inner.last_state_change);
173
174            if elapsed >= Duration::from_millis(inner.config.reset_timeout_ms) {
175                // Reset timeout has elapsed, try half-open state
176                inner.state = CircuitState::HalfOpen;
177                inner.last_state_change = now;
178            }
179        }
180    }
181}
182
183/// Error returned when circuit is open
184#[derive(Debug)]
185pub struct CircuitOpenError {
186    circuit_name: String,
187}
188
189impl CircuitOpenError {
190    fn new(circuit_name: &str) -> Self {
191        Self {
192            circuit_name: circuit_name.to_string(),
193        }
194    }
195}
196
197impl std::fmt::Display for CircuitOpenError {
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        write!(f, "Circuit '{}' is open, failing fast", self.circuit_name)
200    }
201}
202
203impl std::error::Error for CircuitOpenError {}