circuitbreaker_rs/
policy.rs1use crate::metrics::{BreakerStats, EMAWindow, FixedWindow};
4use std::time::Duration;
5
6pub trait BreakerPolicy: Send + Sync + 'static {
8 fn should_trip(&self, stats: &BreakerStats) -> bool;
10
11 fn should_reset(&self, stats: &BreakerStats) -> bool;
13}
14
15pub struct DefaultPolicy {
17 failure_threshold: f64,
18 min_throughput: u64,
19 consecutive_failures_threshold: u64,
20 consecutive_successes_threshold: u64,
21}
22
23impl DefaultPolicy {
24 pub fn new(
26 failure_threshold: f64,
27 min_throughput: u64,
28 consecutive_failures_threshold: u64,
29 consecutive_successes_threshold: u64,
30 ) -> Self {
31 Self {
32 failure_threshold,
33 min_throughput,
34 consecutive_failures_threshold,
35 consecutive_successes_threshold,
36 }
37 }
38}
39
40impl BreakerPolicy for DefaultPolicy {
41 fn should_trip(&self, stats: &BreakerStats) -> bool {
42 let error_rate = stats.error_rate();
44 let total_calls = stats.get_total_calls();
45
46 if total_calls >= self.min_throughput && error_rate >= self.failure_threshold {
47 return true;
48 }
49
50 stats.consecutive_failures() >= self.consecutive_failures_threshold
52 }
53
54 fn should_reset(&self, stats: &BreakerStats) -> bool {
55 stats.consecutive_successes() >= self.consecutive_successes_threshold
56 }
57}
58
59pub struct TimeBasedPolicy {
61 window: FixedWindow,
62 failure_threshold: f64,
63 min_call_count: u64,
64 min_recovery_time: Duration,
65 consecutive_successes_threshold: u64,
66}
67
68impl TimeBasedPolicy {
69 pub fn new(
71 window_size: Duration,
72 bucket_count: usize,
73 failure_threshold: f64,
74 min_call_count: u64,
75 min_recovery_time: Duration,
76 consecutive_successes_threshold: u64,
77 ) -> Self {
78 Self {
79 window: FixedWindow::new(window_size, bucket_count),
80 failure_threshold,
81 min_call_count,
82 min_recovery_time,
83 consecutive_successes_threshold,
84 }
85 }
86
87 pub fn record_success(&self) {
89 self.window.record_success();
90 }
91
92 pub fn record_failure(&self) {
94 self.window.record_failure();
95 }
96}
97
98impl BreakerPolicy for TimeBasedPolicy {
99 fn should_trip(&self, stats: &BreakerStats) -> bool {
100 let window_error_rate = self.window.error_rate();
101 let total_calls = stats.get_total_calls();
102
103 window_error_rate >= self.failure_threshold && total_calls >= self.min_call_count
104 }
105
106 fn should_reset(&self, stats: &BreakerStats) -> bool {
107 let last_failure = stats.get_last_failure_time();
108
109 if let Some(time) = last_failure {
110 if time.elapsed() < self.min_recovery_time {
111 return false;
112 }
113 }
114
115 stats.consecutive_successes() >= self.consecutive_successes_threshold
116 }
117}
118
119pub struct ThroughputAwarePolicy {
121 ema_window: EMAWindow,
122 failure_threshold: f64,
123 min_throughput_per_second: f64,
124 throughput_window: Duration,
125 recovery_threshold: f64,
126}
127
128impl ThroughputAwarePolicy {
129 pub fn new(
131 alpha: f64,
132 calls_required: u64,
133 failure_threshold: f64,
134 min_throughput_per_second: f64,
135 throughput_window: Duration,
136 recovery_threshold: f64,
137 ) -> Self {
138 Self {
139 ema_window: EMAWindow::new(alpha, calls_required),
140 failure_threshold,
141 min_throughput_per_second,
142 throughput_window,
143 recovery_threshold,
144 }
145 }
146
147 pub fn record_success(&self) {
149 self.ema_window.record_success();
150 }
151
152 pub fn record_failure(&self) {
154 self.ema_window.record_failure();
155 }
156
157 fn calculate_throughput(&self, stats: &BreakerStats) -> f64 {
158 let total_calls = stats.get_total_calls();
159
160 let window_secs = self.throughput_window.as_secs_f64();
161 if window_secs <= 0.0 {
162 return 0.0;
163 }
164
165 total_calls as f64 / window_secs
166 }
167}
168
169impl BreakerPolicy for ThroughputAwarePolicy {
170 fn should_trip(&self, stats: &BreakerStats) -> bool {
171 let error_rate = self.ema_window.error_rate();
172 let throughput = self.calculate_throughput(stats);
173
174 error_rate >= self.failure_threshold && throughput >= self.min_throughput_per_second
175 }
176
177 fn should_reset(&self, _stats: &BreakerStats) -> bool {
178 let error_rate = self.ema_window.error_rate();
180 error_rate <= self.recovery_threshold
181 }
182}