1use std::time::Duration;
4
5#[derive(Debug, Clone)]
7pub struct RetryConfig {
8 pub max_retries: u32,
10
11 pub initial_delay: Duration,
13
14 pub max_delay: Duration,
16
17 pub backoff_multiplier: f64,
19
20 pub jitter: bool,
22}
23
24impl Default for RetryConfig {
25 fn default() -> Self {
26 Self {
27 max_retries: 3,
28 initial_delay: Duration::from_millis(100),
29 max_delay: Duration::from_secs(5),
30 backoff_multiplier: 2.0,
31 jitter: true,
32 }
33 }
34}
35
36impl RetryConfig {
37 pub fn no_retries() -> Self {
39 Self {
40 max_retries: 0,
41 ..Default::default()
42 }
43 }
44
45 pub fn aggressive() -> Self {
47 Self {
48 max_retries: 10,
49 initial_delay: Duration::from_millis(50),
50 max_delay: Duration::from_secs(30),
51 backoff_multiplier: 2.0,
52 jitter: true,
53 }
54 }
55
56 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
58 let base = self.initial_delay.as_millis() as f64;
59 let delay_ms = base * self.backoff_multiplier.powi(attempt as i32);
60 let capped_ms = delay_ms.min(self.max_delay.as_millis() as f64);
61
62 if self.jitter {
63 let jitter_range = capped_ms * 0.1; let jitter = rand::random::<f64>() * jitter_range;
65 Duration::from_millis((capped_ms + jitter) as u64)
66 } else {
67 Duration::from_millis(capped_ms as u64)
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn default_config() {
78 let c = RetryConfig::default();
79 assert_eq!(c.max_retries, 3);
80 assert_eq!(c.initial_delay, Duration::from_millis(100));
81 }
82
83 #[test]
84 fn delay_increases_exponentially() {
85 let c = RetryConfig {
86 jitter: false,
87 ..Default::default()
88 };
89 let d0 = c.delay_for_attempt(0);
90 let d1 = c.delay_for_attempt(1);
91 let d2 = c.delay_for_attempt(2);
92 assert!(d1 > d0);
93 assert!(d2 > d1);
94 }
95
96 #[test]
97 fn delay_respects_max() {
98 let c = RetryConfig {
99 max_delay: Duration::from_millis(200),
100 jitter: false,
101 ..Default::default()
102 };
103 let d = c.delay_for_attempt(100);
104 assert!(d <= Duration::from_millis(200));
105 }
106}