1use std::time::Duration;
7
8#[derive(Debug, Clone)]
10pub struct BackoffConfig {
11 pub base_interval_ms: u64,
13 pub max_backoff_ms: u64,
15 pub max_exponent: u32,
17}
18
19impl Default for BackoffConfig {
20 fn default() -> Self {
21 Self {
22 base_interval_ms: 5000, max_backoff_ms: 60000, max_exponent: 4, }
26 }
27}
28
29impl BackoffConfig {
30 pub fn new(base_interval_ms: u64, max_backoff_ms: u64, max_exponent: u32) -> Self {
32 Self {
33 base_interval_ms,
34 max_backoff_ms,
35 max_exponent,
36 }
37 }
38
39 pub fn fast() -> Self {
41 Self {
42 base_interval_ms: 1000, max_backoff_ms: 10000, max_exponent: 3, }
46 }
47
48 pub fn aggressive() -> Self {
50 Self {
51 base_interval_ms: 10000, max_backoff_ms: 300000, max_exponent: 5, }
55 }
56}
57
58pub fn calculate_backoff(config: &BackoffConfig, consecutive_failures: u32) -> Duration {
91 if consecutive_failures == 0 {
92 return Duration::from_millis(config.base_interval_ms);
93 }
94
95 let exponent = (consecutive_failures.saturating_sub(1)).min(config.max_exponent);
97 let multiplier = 2u64.saturating_pow(exponent);
98 let delay_ms = config
99 .base_interval_ms
100 .saturating_mul(multiplier)
101 .min(config.max_backoff_ms);
102
103 Duration::from_millis(delay_ms)
104}
105
106pub fn calculate_backoff_ms(
108 base_interval_ms: u64,
109 max_backoff_ms: u64,
110 consecutive_failures: u32,
111) -> u64 {
112 let config = BackoffConfig {
113 base_interval_ms,
114 max_backoff_ms,
115 max_exponent: 4, };
117 calculate_backoff(&config, consecutive_failures).as_millis() as u64
118}
119
120#[derive(Debug, Clone)]
122pub enum PollResult<T> {
123 Continue,
125 Terminal(T),
127 Error(String),
129}
130
131#[derive(Debug, Clone)]
133pub struct PollConfig {
134 pub backoff: BackoffConfig,
136 pub max_consecutive_errors: u32,
138 pub timeout_secs: u64,
140 pub poll_interval_ms: u64,
142}
143
144impl Default for PollConfig {
145 fn default() -> Self {
146 Self {
147 backoff: BackoffConfig::default(),
148 max_consecutive_errors: 5,
149 timeout_secs: 0, poll_interval_ms: 5000, }
152 }
153}
154
155#[derive(Debug)]
157pub struct PollState {
158 pub consecutive_errors: u32,
160 pub total_attempts: u32,
162 start_time: std::time::Instant,
164 config: PollConfig,
166}
167
168impl PollState {
169 pub fn new(config: PollConfig) -> Self {
171 Self {
172 consecutive_errors: 0,
173 total_attempts: 0,
174 start_time: std::time::Instant::now(),
175 config,
176 }
177 }
178
179 pub fn record_success(&mut self) {
181 self.consecutive_errors = 0;
182 self.total_attempts += 1;
183 }
184
185 pub fn record_error(&mut self) {
187 self.consecutive_errors += 1;
188 self.total_attempts += 1;
189 }
190
191 pub fn should_give_up(&self) -> bool {
193 self.consecutive_errors >= self.config.max_consecutive_errors
194 }
195
196 pub fn is_timed_out(&self) -> bool {
198 if self.config.timeout_secs == 0 {
199 return false;
200 }
201 self.start_time.elapsed().as_secs() >= self.config.timeout_secs
202 }
203
204 pub fn next_delay(&self) -> Duration {
206 if self.consecutive_errors > 0 {
207 calculate_backoff(&self.config.backoff, self.consecutive_errors)
208 } else {
209 Duration::from_millis(self.config.poll_interval_ms)
210 }
211 }
212
213 pub fn elapsed(&self) -> Duration {
215 self.start_time.elapsed()
216 }
217}
218
219#[derive(Debug, Clone)]
233pub struct RetryConfig {
234 pub max_attempts: u32,
236 pub initial_delay_ms: u64,
238 pub backoff_multiplier: f64,
240 pub max_delay_ms: u64,
242}
243
244impl Default for RetryConfig {
245 fn default() -> Self {
246 Self {
247 max_attempts: 3,
248 initial_delay_ms: 1000,
249 backoff_multiplier: 2.0,
250 max_delay_ms: 30000,
251 }
252 }
253}
254
255impl RetryConfig {
256 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
258 if attempt == 0 {
259 return Duration::from_millis(self.initial_delay_ms);
260 }
261
262 let multiplier = self.backoff_multiplier.powi(attempt as i32);
263 let delay_ms = (self.initial_delay_ms as f64 * multiplier) as u64;
264 Duration::from_millis(delay_ms.min(self.max_delay_ms))
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn test_calculate_backoff_default() {
274 let config = BackoffConfig::default();
275
276 assert_eq!(calculate_backoff(&config, 1).as_millis(), 5000);
278
279 assert_eq!(calculate_backoff(&config, 2).as_millis(), 10000);
281
282 assert_eq!(calculate_backoff(&config, 3).as_millis(), 20000);
284
285 assert_eq!(calculate_backoff(&config, 4).as_millis(), 40000);
287
288 assert_eq!(calculate_backoff(&config, 5).as_millis(), 60000);
290
291 assert_eq!(calculate_backoff(&config, 6).as_millis(), 60000);
293 assert_eq!(calculate_backoff(&config, 10).as_millis(), 60000);
294 }
295
296 #[test]
297 fn test_calculate_backoff_zero_failures() {
298 let config = BackoffConfig::default();
299 assert_eq!(calculate_backoff(&config, 0).as_millis(), 5000);
300 }
301
302 #[test]
303 fn test_calculate_backoff_fast() {
304 let config = BackoffConfig::fast();
305 assert_eq!(calculate_backoff(&config, 1).as_millis(), 1000); assert_eq!(calculate_backoff(&config, 2).as_millis(), 2000); assert_eq!(calculate_backoff(&config, 3).as_millis(), 4000); assert_eq!(calculate_backoff(&config, 4).as_millis(), 8000); assert_eq!(calculate_backoff(&config, 5).as_millis(), 8000); assert_eq!(calculate_backoff(&config, 10).as_millis(), 8000); }
314
315 #[test]
316 fn test_poll_state() {
317 let config = PollConfig {
318 max_consecutive_errors: 3,
319 timeout_secs: 0,
320 ..Default::default()
321 };
322
323 let mut state = PollState::new(config);
324
325 assert!(!state.should_give_up());
327 assert_eq!(state.consecutive_errors, 0);
328
329 state.record_error();
331 assert!(!state.should_give_up());
332 state.record_error();
333 assert!(!state.should_give_up());
334 state.record_error();
335 assert!(state.should_give_up());
336
337 state.record_success();
339 assert!(!state.should_give_up());
340 assert_eq!(state.consecutive_errors, 0);
341 }
342
343 #[test]
344 fn test_retry_delay() {
345 let config = RetryConfig {
346 max_attempts: 5,
347 initial_delay_ms: 1000,
348 backoff_multiplier: 2.0,
349 max_delay_ms: 10000,
350 };
351
352 assert_eq!(config.delay_for_attempt(0).as_millis(), 1000);
353 assert_eq!(config.delay_for_attempt(1).as_millis(), 2000);
354 assert_eq!(config.delay_for_attempt(2).as_millis(), 4000);
355 assert_eq!(config.delay_for_attempt(3).as_millis(), 8000);
356 assert_eq!(config.delay_for_attempt(4).as_millis(), 10000);
358 }
359}