chie_shared/utils/
circuit_breaker.rs1use std::time::{Duration, Instant};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum CircuitState {
11 Closed,
13 Open,
15 HalfOpen,
17}
18
19#[derive(Debug, Clone)]
44pub struct CircuitBreaker {
45 state: CircuitState,
47 failure_count: u32,
49 failure_threshold: u32,
51 timeout_ms: u64,
53 #[allow(dead_code)]
55 half_open_timeout_ms: u64,
56 opened_at: Option<Instant>,
58 half_open_successes: u32,
60 half_open_max_requests: u32,
62}
63
64impl CircuitBreaker {
65 #[must_use]
73 pub fn new(failure_threshold: u32, timeout_ms: u64, half_open_timeout_ms: u64) -> Self {
74 Self {
75 state: CircuitState::Closed,
76 failure_count: 0,
77 failure_threshold,
78 timeout_ms,
79 half_open_timeout_ms,
80 opened_at: None,
81 half_open_successes: 0,
82 half_open_max_requests: 3,
83 }
84 }
85
86 #[must_use]
90 pub fn with_defaults() -> Self {
91 Self::new(5, 60_000, 30_000)
92 }
93
94 #[must_use]
96 pub fn state(&self) -> CircuitState {
97 self.state
98 }
99
100 #[must_use]
102 pub fn is_closed(&self) -> bool {
103 self.check_state_transition();
104 self.state == CircuitState::Closed
105 }
106
107 #[must_use]
109 pub fn is_open(&self) -> bool {
110 self.check_state_transition();
111 self.state == CircuitState::Open
112 }
113
114 #[must_use]
116 pub fn is_half_open(&self) -> bool {
117 self.check_state_transition();
118 self.state == CircuitState::HalfOpen
119 }
120
121 #[must_use]
123 pub fn allow_request(&self) -> bool {
124 match self.state {
125 CircuitState::Closed => true,
126 CircuitState::Open => {
127 if let Some(opened_at) = self.opened_at {
129 opened_at.elapsed() >= Duration::from_millis(self.timeout_ms)
130 } else {
131 false
132 }
133 }
134 CircuitState::HalfOpen => {
135 self.half_open_successes < self.half_open_max_requests
137 }
138 }
139 }
140
141 pub fn record_success(&mut self) {
143 match self.state {
144 CircuitState::Closed => {
145 self.failure_count = 0;
147 }
148 CircuitState::HalfOpen => {
149 self.half_open_successes += 1;
150 if self.half_open_successes >= self.half_open_max_requests {
152 self.transition_to_closed();
153 }
154 }
155 CircuitState::Open => {
156 self.transition_to_half_open();
159 }
160 }
161 }
162
163 pub fn record_failure(&mut self) {
165 match self.state {
166 CircuitState::Closed => {
167 self.failure_count += 1;
168 if self.failure_count >= self.failure_threshold {
169 self.transition_to_open();
170 }
171 }
172 CircuitState::HalfOpen => {
173 self.transition_to_open();
175 }
176 CircuitState::Open => {
177 self.opened_at = Some(Instant::now());
179 }
180 }
181 }
182
183 #[must_use]
185 pub fn failure_count(&self) -> u32 {
186 self.failure_count
187 }
188
189 #[must_use]
191 pub fn time_until_half_open(&self) -> Option<Duration> {
192 if self.state != CircuitState::Open {
193 return None;
194 }
195
196 self.opened_at.and_then(|opened_at| {
197 let elapsed = opened_at.elapsed();
198 let timeout = Duration::from_millis(self.timeout_ms);
199 timeout.checked_sub(elapsed)
200 })
201 }
202
203 pub fn reset(&mut self) {
205 self.transition_to_closed();
206 }
207
208 pub fn force_open(&mut self) {
210 self.transition_to_open();
211 }
212
213 fn check_state_transition(&self) {
216 }
218
219 fn transition_to_closed(&mut self) {
220 self.state = CircuitState::Closed;
221 self.failure_count = 0;
222 self.opened_at = None;
223 self.half_open_successes = 0;
224 }
225
226 fn transition_to_open(&mut self) {
227 self.state = CircuitState::Open;
228 self.opened_at = Some(Instant::now());
229 self.half_open_successes = 0;
230 }
231
232 fn transition_to_half_open(&mut self) {
233 self.state = CircuitState::HalfOpen;
234 self.half_open_successes = 0;
235 }
236}
237
238impl Default for CircuitBreaker {
239 fn default() -> Self {
240 Self::with_defaults()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use std::thread::sleep;
248
249 #[test]
250 fn test_circuit_starts_closed() {
251 let breaker = CircuitBreaker::new(3, 1000, 500);
252 assert_eq!(breaker.state(), CircuitState::Closed);
253 assert!(breaker.is_closed());
254 assert!(!breaker.is_open());
255 }
256
257 #[test]
258 fn test_circuit_opens_after_threshold() {
259 let mut breaker = CircuitBreaker::new(3, 1000, 500);
260
261 breaker.record_failure();
263 assert!(breaker.is_closed());
264
265 breaker.record_failure();
266 assert!(breaker.is_closed());
267
268 breaker.record_failure();
269 assert!(breaker.is_open());
270 }
271
272 #[test]
273 fn test_success_resets_failure_count() {
274 let mut breaker = CircuitBreaker::new(3, 1000, 500);
275
276 breaker.record_failure();
277 breaker.record_failure();
278 assert_eq!(breaker.failure_count(), 2);
279
280 breaker.record_success();
281 assert_eq!(breaker.failure_count(), 0);
282 assert!(breaker.is_closed());
283 }
284
285 #[test]
286 fn test_allow_request_closed() {
287 let breaker = CircuitBreaker::new(3, 1000, 500);
288 assert!(breaker.allow_request());
289 }
290
291 #[test]
292 fn test_allow_request_open() {
293 let mut breaker = CircuitBreaker::new(3, 100, 50); for _ in 0..3 {
297 breaker.record_failure();
298 }
299 assert!(breaker.is_open());
300 assert!(!breaker.allow_request());
301
302 sleep(Duration::from_millis(150));
304 assert!(breaker.allow_request()); }
306
307 #[test]
308 fn test_half_open_recovery() {
309 let mut breaker = CircuitBreaker::new(3, 100, 50);
310
311 for _ in 0..3 {
313 breaker.record_failure();
314 }
315 assert!(breaker.is_open());
316
317 sleep(Duration::from_millis(150));
319 assert!(breaker.allow_request());
320
321 breaker.transition_to_half_open();
323 assert!(breaker.is_half_open());
324
325 breaker.record_success();
327 breaker.record_success();
328 breaker.record_success();
329
330 assert!(breaker.is_closed());
332 }
333
334 #[test]
335 fn test_half_open_failure_reopens() {
336 let mut breaker = CircuitBreaker::new(3, 100, 50);
337
338 for _ in 0..3 {
340 breaker.record_failure();
341 }
342
343 breaker.transition_to_half_open();
345 assert!(breaker.is_half_open());
346
347 breaker.record_failure();
349 assert!(breaker.is_open());
350 }
351
352 #[test]
353 fn test_reset() {
354 let mut breaker = CircuitBreaker::new(3, 1000, 500);
355
356 for _ in 0..3 {
358 breaker.record_failure();
359 }
360 assert!(breaker.is_open());
361
362 breaker.reset();
364 assert!(breaker.is_closed());
365 assert_eq!(breaker.failure_count(), 0);
366 }
367
368 #[test]
369 fn test_force_open() {
370 let mut breaker = CircuitBreaker::new(3, 1000, 500);
371 assert!(breaker.is_closed());
372
373 breaker.force_open();
374 assert!(breaker.is_open());
375 }
376
377 #[test]
378 fn test_time_until_half_open() {
379 let mut breaker = CircuitBreaker::new(3, 1000, 500);
380
381 assert!(breaker.time_until_half_open().is_none());
383
384 for _ in 0..3 {
386 breaker.record_failure();
387 }
388
389 let remaining = breaker.time_until_half_open();
391 assert!(remaining.is_some());
392 assert!(remaining.unwrap().as_millis() <= 1000);
393 }
394
395 #[test]
396 fn test_default_constructor() {
397 let breaker = CircuitBreaker::default();
398 assert_eq!(breaker.state(), CircuitState::Closed);
399 assert_eq!(breaker.failure_count(), 0);
400 }
401}