Skip to main content

faucet_core/resilience/
breaker.rs

1//! Consecutive-failure circuit breaker. Pure (no clock): counts consecutive
2//! page-level write failures and reports when the threshold is crossed.
3
4/// Tracks consecutive failures; opens when `threshold` is reached.
5#[derive(Debug)]
6pub struct CircuitBreaker {
7    threshold: u32,
8    consecutive: u32,
9}
10
11impl CircuitBreaker {
12    /// New breaker that opens after `threshold` consecutive failures.
13    pub fn new(threshold: u32) -> Self {
14        Self {
15            threshold: threshold.max(1),
16            consecutive: 0,
17        }
18    }
19
20    /// Record a success; resets the consecutive counter.
21    pub fn record_success(&mut self) {
22        self.consecutive = 0;
23    }
24
25    /// Record a failure; returns `true` if the circuit is now open.
26    pub fn record_failure(&mut self) -> bool {
27        self.consecutive = self.consecutive.saturating_add(1);
28        self.consecutive >= self.threshold
29    }
30
31    /// Current consecutive-failure count.
32    pub fn consecutive(&self) -> u32 {
33        self.consecutive
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn opens_after_threshold_consecutive_failures() {
43        let mut b = CircuitBreaker::new(3);
44        assert!(!b.record_failure());
45        assert!(!b.record_failure());
46        assert!(b.record_failure(), "third consecutive failure should open");
47        assert_eq!(b.consecutive(), 3);
48    }
49
50    #[test]
51    fn success_resets_the_counter() {
52        let mut b = CircuitBreaker::new(3);
53        b.record_failure();
54        b.record_failure();
55        b.record_success();
56        assert_eq!(b.consecutive(), 0);
57        assert!(!b.record_failure());
58    }
59
60    #[test]
61    fn threshold_one_opens_immediately() {
62        let mut b = CircuitBreaker::new(1);
63        assert!(b.record_failure());
64    }
65}