1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use std::time::Duration;
use recloser::{AsyncRecloser, Recloser};
#[derive(Clone)]
pub struct CircuitBreakerBuilder {
error_threshold: f32,
volume_threshold: usize,
reset_timeout: Duration,
half_open_attempts: usize,
}
impl Default for CircuitBreakerBuilder {
fn default() -> Self {
Self {
error_threshold: 0.5,
volume_threshold: 5,
reset_timeout: Duration::from_secs(30),
half_open_attempts: 10,
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum CircuitBreakerError {
#[error("Invalid error threshold: {0}. It must be between 0.0 and 1.0")]
InvalidErrorThreshold(f32),
}
impl CircuitBreakerBuilder {
/// Percentage after what the circuit breaker should kick in.
/// Default: .5
pub fn error_threshold(mut self, percentage: f32) -> Self {
self.error_threshold = percentage;
self
}
/// Size of the rolling sample used to decide whether the breaker
/// should open while closed. The breaker fills this sample with the
/// outcomes of the last `volume_threshold` requests; the next request
/// after the sample is full is the one whose result is evaluated
/// against `error_threshold`. In practice this means at least
/// `volume_threshold + 1` requests must be observed before the
/// breaker can trip.
/// Default: 5
pub fn volume_threshold(mut self, threshold: usize) -> Self {
self.volume_threshold = threshold;
self
}
/// How long the circuit breaker waits in the `Open` state before transitioning to `HalfOpen`
/// and retrying requests. Accepts a [`Duration`] (e.g. `Duration::from_secs(30)`).
/// Default: 30s
pub fn reset_timeout(mut self, timeout: Duration) -> Self {
self.reset_timeout = timeout;
self
}
/// Size of the rolling sample of probe requests collected while the
/// breaker is in the `HalfOpen` state after `reset_timeout` elapses.
/// The breaker fills this sample first; the next probe after the
/// sample is full is the one whose result is evaluated against
/// `error_threshold` to decide whether to transition back to `Closed`
/// or `Open`. In practice this means at least `half_open_attempts + 1`
/// probes pass through before the breaker can transition.
/// Default: 10
pub fn half_open_attempts(mut self, attempts: usize) -> Self {
self.half_open_attempts = attempts;
self
}
pub fn build_async(self) -> Result<AsyncRecloser, CircuitBreakerError> {
let recloser = self.build_sync()?;
Ok(AsyncRecloser::from(recloser))
}
pub fn build_sync(self) -> Result<Recloser, CircuitBreakerError> {
let error_threshold = if self.error_threshold < 0.0 || self.error_threshold > 1.0 {
return Err(CircuitBreakerError::InvalidErrorThreshold(
self.error_threshold,
));
} else {
self.error_threshold
};
let recloser = Recloser::custom()
.error_rate(error_threshold)
.closed_len(self.volume_threshold)
.half_open_len(self.half_open_attempts)
.open_wait(self.reset_timeout)
.build();
Ok(recloser)
}
}