sz_orm_pool/
circuit_breaker.rs1use std::time::{Duration, Instant};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum CircuitState {
30 Closed,
32 Open,
34 HalfOpen,
36}
37
38pub trait CircuitBreaker: Send + Sync {
47 fn state(&self) -> CircuitState;
49
50 fn record_success(&mut self);
54
55 fn record_failure(&mut self);
59
60 fn can_execute(&mut self) -> bool;
64
65 fn reset(&mut self) -> bool;
72}
73
74#[derive(Debug)]
100pub struct DefaultCircuitBreaker {
101 failure_threshold: usize,
103 reset_timeout: Duration,
105 state: CircuitState,
107 consecutive_failures: usize,
109 last_failure_at: Option<Instant>,
111}
112
113impl DefaultCircuitBreaker {
114 pub fn new(failure_threshold: usize, reset_timeout: Duration) -> Self {
119 Self {
120 failure_threshold,
121 reset_timeout,
122 state: CircuitState::Closed,
123 consecutive_failures: 0,
124 last_failure_at: None,
125 }
126 }
127
128 pub fn consecutive_failures(&self) -> usize {
130 self.consecutive_failures
131 }
132
133 pub fn failure_threshold(&self) -> usize {
135 self.failure_threshold
136 }
137
138 pub fn reset_timeout(&self) -> Duration {
140 self.reset_timeout
141 }
142}
143
144impl Default for DefaultCircuitBreaker {
145 fn default() -> Self {
147 Self::new(5, Duration::from_secs(30))
148 }
149}
150
151impl CircuitBreaker for DefaultCircuitBreaker {
152 fn state(&self) -> CircuitState {
153 self.state
154 }
155
156 fn record_success(&mut self) {
157 self.consecutive_failures = 0;
158 self.state = CircuitState::Closed;
159 self.last_failure_at = None;
160 }
161
162 fn record_failure(&mut self) {
163 self.consecutive_failures += 1;
164 self.last_failure_at = Some(Instant::now());
165 if self.consecutive_failures >= self.failure_threshold {
166 self.state = CircuitState::Open;
167 }
168 }
169
170 fn can_execute(&mut self) -> bool {
171 match self.state {
172 CircuitState::Closed => true,
173 CircuitState::HalfOpen => true,
174 CircuitState::Open => {
175 let elapsed = self
176 .last_failure_at
177 .map(|t| t.elapsed())
178 .unwrap_or_else(|| Duration::ZERO);
179 if elapsed >= self.reset_timeout {
180 self.state = CircuitState::HalfOpen;
181 true
182 } else {
183 false
184 }
185 }
186 }
187 }
188
189 fn reset(&mut self) -> bool {
190 let changed = self.state != CircuitState::Closed || self.consecutive_failures != 0;
191 self.state = CircuitState::Closed;
192 self.consecutive_failures = 0;
193 self.last_failure_at = None;
194 changed
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn test_circuit_breaker_starts_closed() {
204 let mut cb = DefaultCircuitBreaker::new(3, Duration::from_millis(100));
205 assert_eq!(cb.state(), CircuitState::Closed);
206 assert!(cb.can_execute());
207 }
208
209 #[test]
210 fn test_circuit_breaker_trips_after_threshold() {
211 let mut cb = DefaultCircuitBreaker::new(3, Duration::from_secs(60));
212 assert!(cb.can_execute());
213 cb.record_failure();
214 cb.record_failure();
215 assert_eq!(cb.state(), CircuitState::Closed);
216 cb.record_failure();
217 assert_eq!(cb.state(), CircuitState::Open);
218 assert!(!cb.can_execute());
219 }
220
221 #[test]
222 fn test_circuit_breaker_success_resets() {
223 let mut cb = DefaultCircuitBreaker::new(3, Duration::from_secs(60));
224 cb.record_failure();
225 cb.record_failure();
226 cb.record_success();
227 assert_eq!(cb.state(), CircuitState::Closed);
228 cb.record_failure();
230 cb.record_failure();
231 assert_eq!(cb.state(), CircuitState::Closed);
232 }
233
234 #[test]
235 fn test_circuit_breaker_half_open_after_timeout() {
236 let mut cb = DefaultCircuitBreaker::new(1, Duration::from_millis(10));
237 cb.record_failure();
238 assert_eq!(cb.state(), CircuitState::Open);
239 assert!(!cb.can_execute());
241 std::thread::sleep(Duration::from_millis(30));
243 assert!(cb.can_execute());
244 assert_eq!(cb.state(), CircuitState::HalfOpen);
245 }
246
247 #[test]
248 fn test_circuit_breaker_half_open_success_closes() {
249 let mut cb = DefaultCircuitBreaker::new(1, Duration::from_millis(10));
250 cb.record_failure();
251 std::thread::sleep(Duration::from_millis(20));
252 assert!(cb.can_execute());
253 assert_eq!(cb.state(), CircuitState::HalfOpen);
254 cb.record_success();
255 assert_eq!(cb.state(), CircuitState::Closed);
256 }
257
258 #[test]
259 fn test_circuit_breaker_half_open_failure_reopens() {
260 let mut cb = DefaultCircuitBreaker::new(1, Duration::from_millis(10));
261 cb.record_failure();
262 std::thread::sleep(Duration::from_millis(20));
263 assert!(cb.can_execute());
264 assert_eq!(cb.state(), CircuitState::HalfOpen);
265 cb.record_failure();
266 assert_eq!(cb.state(), CircuitState::Open);
267 }
268
269 #[test]
270 fn test_circuit_breaker_boundary_exactly_threshold() {
271 let mut cb = DefaultCircuitBreaker::new(3, Duration::from_secs(60));
273 cb.record_failure();
274 cb.record_failure();
275 assert_eq!(cb.state(), CircuitState::Closed);
276 cb.record_failure();
277 assert_eq!(cb.state(), CircuitState::Open);
278 }
279
280 #[test]
281 fn test_circuit_breaker_reset_from_open() {
282 let mut cb = DefaultCircuitBreaker::new(2, Duration::from_secs(60));
283 cb.record_failure();
284 cb.record_failure();
285 assert_eq!(cb.state(), CircuitState::Open);
286 assert!(cb.reset());
287 assert_eq!(cb.state(), CircuitState::Closed);
288 assert_eq!(cb.consecutive_failures(), 0);
289 assert!(cb.can_execute());
291 cb.record_failure();
293 assert_eq!(cb.state(), CircuitState::Closed);
294 }
295
296 #[test]
297 fn test_circuit_breaker_reset_from_half_open() {
298 let mut cb = DefaultCircuitBreaker::new(1, Duration::from_millis(10));
299 cb.record_failure();
300 assert_eq!(cb.state(), CircuitState::Open);
301 std::thread::sleep(Duration::from_millis(20));
302 assert!(cb.can_execute());
303 assert_eq!(cb.state(), CircuitState::HalfOpen);
304 assert!(cb.reset());
306 assert_eq!(cb.state(), CircuitState::Closed);
307 }
308
309 #[test]
310 fn test_circuit_breaker_reset_idempotent_when_closed() {
311 let mut cb = DefaultCircuitBreaker::new(3, Duration::from_secs(60));
312 assert!(!cb.reset());
313 assert_eq!(cb.state(), CircuitState::Closed);
314 cb.record_failure();
316 cb.record_failure();
317 assert!(cb.reset());
318 assert_eq!(cb.consecutive_failures(), 0);
319 assert!(!cb.reset());
321 }
322
323 #[test]
324 fn test_circuit_state_variants_distinct() {
325 assert_ne!(CircuitState::Closed, CircuitState::Open);
326 assert_ne!(CircuitState::Open, CircuitState::HalfOpen);
327 assert_ne!(CircuitState::Closed, CircuitState::HalfOpen);
328 }
329
330 #[test]
331 fn test_default_circuit_breaker_default_config() {
332 let cb = DefaultCircuitBreaker::default();
333 assert_eq!(cb.failure_threshold(), 5);
334 assert_eq!(cb.reset_timeout(), Duration::from_secs(30));
335 assert_eq!(cb.state(), CircuitState::Closed);
336 }
337
338 #[test]
339 fn test_circuit_breaker_send_sync() {
340 fn assert_send_sync<T: Send + Sync>() {}
341 assert_send_sync::<DefaultCircuitBreaker>();
342 assert_send_sync::<CircuitState>();
343 }
344
345 #[test]
347 fn test_circuit_breaker_via_trait_object() {
348 let cb: Box<dyn CircuitBreaker> =
349 Box::new(DefaultCircuitBreaker::new(2, Duration::from_secs(60)));
350 assert_eq!(cb.state(), CircuitState::Closed);
352 }
353}