1use std::time::{Duration, Instant};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum CircuitState {
14 Closed,
16 Open,
18 HalfOpen,
20}
21
22pub struct CircuitBreaker {
40 state: CircuitState,
42 failure_count: usize,
44 failure_threshold: usize,
46 opened_at: Option<Instant>,
48 open_duration: Duration,
50 half_open_successes: usize,
52 half_open_threshold: usize,
54}
55
56impl CircuitBreaker {
57 pub fn new(failure_threshold: usize, open_duration: Duration) -> Self {
59 Self {
60 state: CircuitState::Closed,
61 failure_count: 0,
62 failure_threshold,
63 opened_at: None,
64 open_duration,
65 half_open_successes: 0,
66 half_open_threshold: 1,
67 }
68 }
69
70 #[must_use]
72 pub fn state(&self) -> CircuitState {
73 self.state
74 }
75
76 #[must_use]
78 pub fn allow_request(&mut self) -> bool {
79 match self.state {
80 CircuitState::Closed => true,
81 CircuitState::Open => {
82 if let Some(opened_at) = self.opened_at {
84 if opened_at.elapsed() >= self.open_duration {
85 self.state = CircuitState::HalfOpen;
86 self.half_open_successes = 0;
87 return true; }
89 }
90 false
91 }
92 CircuitState::HalfOpen => true, }
94 }
95
96 pub fn record_success(&mut self) {
98 match self.state {
99 CircuitState::Closed => {
100 self.failure_count = 0;
102 }
103 CircuitState::HalfOpen => {
104 self.half_open_successes += 1;
105 if self.half_open_successes >= self.half_open_threshold {
106 self.state = CircuitState::Closed;
108 self.failure_count = 0;
109 self.opened_at = None;
110 }
111 }
112 CircuitState::Open => {}
113 }
114 }
115
116 pub fn record_failure(&mut self) {
118 match self.state {
119 CircuitState::Closed => {
120 self.failure_count += 1;
121 if self.failure_count >= self.failure_threshold {
122 self.state = CircuitState::Open;
124 self.opened_at = Some(Instant::now());
125 }
126 }
127 CircuitState::HalfOpen => {
128 self.state = CircuitState::Open;
130 self.opened_at = Some(Instant::now());
131 }
132 CircuitState::Open => {}
133 }
134 }
135
136 pub fn reset(&mut self) {
138 self.state = CircuitState::Closed;
139 self.failure_count = 0;
140 self.opened_at = None;
141 self.half_open_successes = 0;
142 }
143}
144
145impl Default for CircuitBreaker {
146 fn default() -> Self {
147 Self::new(5, Duration::from_secs(30))
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_circuit_state_eq() {
157 assert_eq!(CircuitState::Closed, CircuitState::Closed);
158 assert_eq!(CircuitState::Open, CircuitState::Open);
159 assert_eq!(CircuitState::HalfOpen, CircuitState::HalfOpen);
160 assert_ne!(CircuitState::Closed, CircuitState::Open);
161 }
162
163 #[test]
164 fn test_circuit_breaker_new() {
165 let cb = CircuitBreaker::new(5, Duration::from_secs(30));
166 assert_eq!(cb.state(), CircuitState::Closed);
167 assert_eq!(cb.failure_count, 0);
168 assert_eq!(cb.failure_threshold, 5);
169 }
170
171 #[test]
172 fn test_circuit_breaker_default() {
173 let cb = CircuitBreaker::default();
174 assert_eq!(cb.state(), CircuitState::Closed);
175 assert_eq!(cb.failure_threshold, 5);
176 assert_eq!(cb.open_duration, Duration::from_secs(30));
177 }
178
179 #[test]
180 fn test_circuit_breaker_closed_allows_requests() {
181 let mut cb = CircuitBreaker::new(3, Duration::from_secs(30));
182 assert!(cb.allow_request());
183 assert!(cb.allow_request());
184 assert!(cb.allow_request());
185 }
186
187 #[test]
188 fn test_circuit_breaker_trips_on_failures() {
189 let mut cb = CircuitBreaker::new(3, Duration::from_secs(30));
190
191 cb.record_failure();
192 assert_eq!(cb.state(), CircuitState::Closed);
193
194 cb.record_failure();
195 assert_eq!(cb.state(), CircuitState::Closed);
196
197 cb.record_failure();
198 assert_eq!(cb.state(), CircuitState::Open);
199 }
200
201 #[test]
202 fn test_circuit_breaker_open_blocks_requests() {
203 let mut cb = CircuitBreaker::new(2, Duration::from_secs(30));
204
205 cb.record_failure();
206 cb.record_failure();
207 assert_eq!(cb.state(), CircuitState::Open);
208
209 assert!(!cb.allow_request());
210 }
211
212 #[test]
213 fn test_circuit_breaker_success_resets_failures() {
214 let mut cb = CircuitBreaker::new(3, Duration::from_secs(30));
215
216 cb.record_failure();
217 cb.record_failure();
218 assert_eq!(cb.failure_count, 2);
219
220 cb.record_success();
221 assert_eq!(cb.failure_count, 0);
222 }
223
224 #[test]
225 fn test_circuit_breaker_half_open_transition() {
226 let mut cb = CircuitBreaker::new(2, Duration::from_millis(10));
227
228 cb.record_failure();
230 cb.record_failure();
231 assert_eq!(cb.state(), CircuitState::Open);
232
233 std::thread::sleep(Duration::from_millis(15));
235
236 assert!(cb.allow_request());
238 assert_eq!(cb.state(), CircuitState::HalfOpen);
239 }
240
241 #[test]
242 fn test_circuit_breaker_half_open_success_closes() {
243 let mut cb = CircuitBreaker::new(2, Duration::from_millis(10));
244
245 cb.record_failure();
247 cb.record_failure();
248 std::thread::sleep(Duration::from_millis(15));
249 let _ = cb.allow_request(); assert_eq!(cb.state(), CircuitState::HalfOpen);
252
253 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 = CircuitBreaker::new(2, Duration::from_millis(10));
261
262 cb.record_failure();
264 cb.record_failure();
265 std::thread::sleep(Duration::from_millis(15));
266 let _ = cb.allow_request(); assert_eq!(cb.state(), CircuitState::HalfOpen);
269
270 cb.record_failure();
272 assert_eq!(cb.state(), CircuitState::Open);
273 }
274
275 #[test]
276 fn test_circuit_breaker_reset() {
277 let mut cb = CircuitBreaker::new(2, Duration::from_secs(30));
278
279 cb.record_failure();
280 cb.record_failure();
281 assert_eq!(cb.state(), CircuitState::Open);
282
283 cb.reset();
284 assert_eq!(cb.state(), CircuitState::Closed);
285 assert_eq!(cb.failure_count, 0);
286 }
287
288 #[test]
293 fn test_falsify_exact_threshold_trip() {
294 for threshold in 1..=10 {
295 let mut cb = CircuitBreaker::new(threshold, Duration::from_secs(30));
296
297 for i in 0..(threshold - 1) {
299 cb.record_failure();
300 assert_eq!(
301 cb.state(),
302 CircuitState::Closed,
303 "FALSIFICATION FAILED: Circuit tripped after {} failures (threshold={})",
304 i + 1,
305 threshold
306 );
307 }
308
309 cb.record_failure();
311 assert_eq!(
312 cb.state(),
313 CircuitState::Open,
314 "FALSIFICATION FAILED: Circuit did NOT trip at exact threshold={}",
315 threshold
316 );
317 }
318 }
319
320 #[test]
325 fn test_falsify_open_blocks_all() {
326 let mut cb = CircuitBreaker::new(1, Duration::from_secs(30));
327
328 cb.record_failure();
330 assert_eq!(cb.state(), CircuitState::Open);
331
332 let mut allowed = 0;
334 for _ in 0..1000 {
335 if cb.allow_request() {
336 allowed += 1;
337 }
338 }
339
340 assert_eq!(
341 allowed, 0,
342 "FALSIFICATION FAILED: {} requests leaked through open circuit",
343 allowed
344 );
345 }
346
347 #[test]
352 fn test_falsify_half_open_single_failure_reopens() {
353 let mut cb = CircuitBreaker::new(1, Duration::from_millis(5));
354
355 cb.record_failure();
357 std::thread::sleep(Duration::from_millis(10));
358 let _ = cb.allow_request(); assert_eq!(cb.state(), CircuitState::HalfOpen);
361
362 cb.record_failure();
364
365 assert_eq!(
366 cb.state(),
367 CircuitState::Open,
368 "FALSIFICATION FAILED: Half-open did not reopen after failure"
369 );
370
371 assert!(cb.opened_at.is_some(), "FALSIFICATION FAILED: opened_at not set after reopen");
373 }
374
375 #[test]
380 fn test_falsify_state_machine_determinism() {
381 for _ in 0..10 {
383 let mut cb = CircuitBreaker::new(3, Duration::from_millis(5));
384
385 cb.record_failure();
387 assert_eq!(cb.state(), CircuitState::Closed);
388
389 cb.record_success();
390 assert_eq!(cb.failure_count, 0); cb.record_failure();
393 cb.record_failure();
394 cb.record_failure();
395 assert_eq!(cb.state(), CircuitState::Open);
396
397 std::thread::sleep(Duration::from_millis(10));
398 let _ = cb.allow_request();
399 assert_eq!(cb.state(), CircuitState::HalfOpen);
400
401 cb.record_success();
402 assert_eq!(cb.state(), CircuitState::Closed);
403 }
404 }
405
406 #[test]
411 fn test_falsify_open_ignores_failures() {
412 let mut cb = CircuitBreaker::new(2, Duration::from_secs(30));
413
414 cb.record_failure();
416 cb.record_failure();
417 let opened_at = cb.opened_at;
418
419 for _ in 0..100 {
421 cb.record_failure();
422 }
423
424 assert_eq!(cb.state(), CircuitState::Open);
426 assert_eq!(
427 cb.opened_at, opened_at,
428 "FALSIFICATION FAILED: opened_at changed while in Open state"
429 );
430 }
431}