1use std::collections::HashMap;
6
7#[allow(dead_code)]
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum FailureType {
11 NodeCrash,
12 NetworkPartition,
13 SlowNode,
14 MemoryExhausted,
15 DiskFull,
16}
17
18#[allow(dead_code)]
20#[derive(Debug, Clone)]
21pub struct NodeFailure {
22 pub node_id: u64,
23 pub failure_type: FailureType,
24 pub detected_at: u64,
25 pub recovered_at: Option<u64>,
26}
27
28#[allow(dead_code)]
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum CircuitState {
32 Closed,
34 Open,
36 HalfOpen,
38}
39
40#[allow(dead_code)]
42#[derive(Debug, Clone)]
43pub struct CircuitBreaker {
44 pub failure_count: u32,
45 pub threshold: u32,
46 pub state: CircuitState,
47 pub last_failure: u64,
48 pub reset_timeout_ms: u64,
49}
50
51impl CircuitBreaker {
52 #[must_use]
54 pub fn new(threshold: u32, reset_timeout_ms: u64) -> Self {
55 Self {
56 failure_count: 0,
57 threshold,
58 state: CircuitState::Closed,
59 last_failure: 0,
60 reset_timeout_ms,
61 }
62 }
63
64 pub fn call_succeeded(&mut self, _now: u64) {
66 self.failure_count = 0;
67 self.state = CircuitState::Closed;
68 }
69
70 pub fn call_failed(&mut self, now: u64) {
72 self.failure_count += 1;
73 self.last_failure = now;
74 if self.failure_count >= self.threshold {
75 self.state = CircuitState::Open;
76 }
77 }
78
79 pub fn is_allowed(&mut self, now: u64) -> bool {
86 match self.state {
87 CircuitState::Closed => true,
88 CircuitState::HalfOpen => true,
89 CircuitState::Open => {
90 if now.saturating_sub(self.last_failure) >= self.reset_timeout_ms {
91 self.state = CircuitState::HalfOpen;
92 true
93 } else {
94 false
95 }
96 }
97 }
98 }
99}
100
101#[allow(dead_code)]
103#[derive(Debug, Default)]
104pub struct FaultManager {
105 failures: Vec<NodeFailure>,
106 breakers: HashMap<u64, CircuitBreaker>,
107}
108
109impl FaultManager {
110 #[must_use]
112 pub fn new() -> Self {
113 Self {
114 failures: Vec::new(),
115 breakers: HashMap::new(),
116 }
117 }
118
119 pub fn report_failure(&mut self, node_id: u64, ftype: FailureType, now: u64) {
121 self.failures.push(NodeFailure {
122 node_id,
123 failure_type: ftype,
124 detected_at: now,
125 recovered_at: None,
126 });
127 let breaker = self
129 .breakers
130 .entry(node_id)
131 .or_insert_with(|| CircuitBreaker::new(3, 5000));
132 breaker.call_failed(now);
133 }
134
135 pub fn node_recovered(&mut self, node_id: u64, now: u64) {
137 for f in &mut self.failures {
138 if f.node_id == node_id && f.recovered_at.is_none() {
139 f.recovered_at = Some(now);
140 }
141 }
142 if let Some(b) = self.breakers.get_mut(&node_id) {
143 b.call_succeeded(now);
144 }
145 }
146
147 #[must_use]
149 pub fn active_failures(&self) -> Vec<&NodeFailure> {
150 self.failures
151 .iter()
152 .filter(|f| f.recovered_at.is_none())
153 .collect()
154 }
155
156 pub fn is_node_healthy(&mut self, node_id: u64, now: u64) -> bool {
159 match self.breakers.get_mut(&node_id) {
160 Some(b) => b.is_allowed(now),
161 None => true,
162 }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_circuit_breaker_initial_state() {
172 let cb = CircuitBreaker::new(3, 1000);
173 assert_eq!(cb.state, CircuitState::Closed);
174 assert_eq!(cb.failure_count, 0);
175 }
176
177 #[test]
178 fn test_circuit_breaker_trips_on_threshold() {
179 let mut cb = CircuitBreaker::new(3, 1000);
180 cb.call_failed(1);
181 cb.call_failed(2);
182 assert_eq!(cb.state, CircuitState::Closed);
183 cb.call_failed(3);
184 assert_eq!(cb.state, CircuitState::Open);
185 }
186
187 #[test]
188 fn test_circuit_breaker_blocked_when_open() {
189 let mut cb = CircuitBreaker::new(1, 5000);
190 cb.call_failed(100);
191 assert!(!cb.is_allowed(200)); }
193
194 #[test]
195 fn test_circuit_breaker_half_open_after_timeout() {
196 let mut cb = CircuitBreaker::new(1, 1000);
197 cb.call_failed(0);
198 assert!(cb.is_allowed(1001));
199 assert_eq!(cb.state, CircuitState::HalfOpen);
200 }
201
202 #[test]
203 fn test_circuit_breaker_resets_on_success() {
204 let mut cb = CircuitBreaker::new(1, 1000);
205 cb.call_failed(0);
206 cb.call_succeeded(2000);
207 assert_eq!(cb.state, CircuitState::Closed);
208 assert_eq!(cb.failure_count, 0);
209 }
210
211 #[test]
212 fn test_fault_manager_report_failure() {
213 let mut fm = FaultManager::new();
214 fm.report_failure(1, FailureType::NodeCrash, 100);
215 assert_eq!(fm.active_failures().len(), 1);
216 }
217
218 #[test]
219 fn test_fault_manager_node_recovered() {
220 let mut fm = FaultManager::new();
221 fm.report_failure(1, FailureType::NetworkPartition, 100);
222 fm.node_recovered(1, 200);
223 assert!(fm.active_failures().is_empty());
224 }
225
226 #[test]
227 fn test_fault_manager_active_failures_partial() {
228 let mut fm = FaultManager::new();
229 fm.report_failure(1, FailureType::SlowNode, 100);
230 fm.report_failure(2, FailureType::DiskFull, 110);
231 fm.node_recovered(1, 150);
232 let active = fm.active_failures();
233 assert_eq!(active.len(), 1);
234 assert_eq!(active[0].node_id, 2);
235 }
236
237 #[test]
238 fn test_fault_manager_is_node_healthy_unknown() {
239 let mut fm = FaultManager::new();
240 assert!(fm.is_node_healthy(99, 1000));
241 }
242
243 #[test]
244 fn test_fault_manager_is_node_unhealthy_after_failures() {
245 let mut fm = FaultManager::new();
246 fm.report_failure(5, FailureType::MemoryExhausted, 10);
248 fm.report_failure(5, FailureType::MemoryExhausted, 20);
249 fm.report_failure(5, FailureType::MemoryExhausted, 30);
250 assert!(!fm.is_node_healthy(5, 35));
251 }
252
253 #[test]
254 fn test_fault_manager_recovery_re_enables() {
255 let mut fm = FaultManager::new();
256 fm.report_failure(7, FailureType::NodeCrash, 10);
257 fm.report_failure(7, FailureType::NodeCrash, 20);
258 fm.report_failure(7, FailureType::NodeCrash, 30);
259 fm.node_recovered(7, 200);
260 assert!(fm.is_node_healthy(7, 200));
261 }
262
263 #[test]
264 fn test_all_failure_types() {
265 let mut fm = FaultManager::new();
266 let types = [
267 FailureType::NodeCrash,
268 FailureType::NetworkPartition,
269 FailureType::SlowNode,
270 FailureType::MemoryExhausted,
271 FailureType::DiskFull,
272 ];
273 for (i, ft) in types.into_iter().enumerate() {
274 fm.report_failure(i as u64, ft, i as u64 * 10);
275 }
276 assert_eq!(fm.active_failures().len(), 5);
277 }
278
279 #[test]
280 fn test_multiple_failures_same_node() {
281 let mut fm = FaultManager::new();
282 fm.report_failure(1, FailureType::SlowNode, 1);
283 fm.report_failure(1, FailureType::SlowNode, 2);
284 assert_eq!(fm.active_failures().len(), 2);
286 }
287}