1use std::collections::HashMap;
4use std::sync::Mutex;
5use std::time::{Duration, Instant};
6
7#[derive(Debug, Clone)]
8pub struct BreakerConfig {
9 pub failure_threshold: u32,
10 pub open_for: Duration,
11}
12
13impl Default for BreakerConfig {
14 fn default() -> Self {
15 Self {
16 failure_threshold: 5,
17 open_for: Duration::from_secs(30),
18 }
19 }
20}
21
22#[derive(Debug, Default)]
23struct HostState {
24 failures: u32,
25 open_until: Option<Instant>,
26}
27
28#[derive(Debug, Default)]
29pub struct CircuitBreaker {
30 inner: Mutex<HashMap<String, HostState>>,
31 cfg: BreakerConfig,
32}
33
34impl CircuitBreaker {
35 pub fn new(cfg: BreakerConfig) -> Self {
36 Self {
37 inner: Mutex::new(HashMap::new()),
38 cfg,
39 }
40 }
41
42 pub fn guard(&self, host: &str) -> bool {
43 let mut g = self.inner.lock().unwrap();
44 let st = g.entry(host.to_string()).or_default();
45 if let Some(until) = st.open_until {
46 if Instant::now() < until {
47 return false;
48 }
49 st.open_until = None;
50 st.failures = 0;
51 }
52 true
53 }
54
55 pub fn record_success(&self, host: &str) {
56 let mut g = self.inner.lock().unwrap();
57 if let Some(st) = g.get_mut(host) {
58 st.failures = 0;
59 st.open_until = None;
60 }
61 }
62
63 pub fn record_failure(&self, host: &str) {
64 let mut g = self.inner.lock().unwrap();
65 let st = g.entry(host.to_string()).or_default();
66 st.failures = st.failures.saturating_add(1);
67 if st.failures >= self.cfg.failure_threshold {
68 st.open_until = Some(Instant::now() + self.cfg.open_for);
69 }
70 }
71}