1use std::time::Duration;
11
12#[derive(Debug, Clone, PartialEq)]
17pub struct BackoffConfig {
18 pub initial_delay: Duration,
19 pub multiplier: f64,
20 pub max_delay: Duration,
21}
22
23impl Default for BackoffConfig {
24 fn default() -> Self {
25 Self {
26 initial_delay: Duration::from_millis(100),
27 multiplier: 2.0,
28 max_delay: Duration::from_secs(30),
29 }
30 }
31}
32
33impl BackoffConfig {
34 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
35 let exp = (attempt as i32).min(63);
36 let exp_value = self.multiplier.powi(exp);
37 let millis = (self.initial_delay.as_millis() as f64 * exp_value) as u128;
38 let capped = millis.min(self.max_delay.as_millis());
39 Duration::from_millis(capped as u64)
40 }
41}
42
43#[derive(Debug, Clone)]
46pub struct BackoffState {
47 config: BackoffConfig,
48 current_delay: Duration,
49}
50
51impl BackoffState {
52 pub fn new(config: BackoffConfig) -> Self {
53 let current_delay = config.initial_delay;
54 Self {
55 config,
56 current_delay,
57 }
58 }
59
60 pub fn next_delay(&mut self) -> Duration {
61 let delay = self.current_delay;
62 let exp_value = self.config.multiplier;
63 let millis = (delay.as_millis() as f64 * exp_value) as u128;
64 let capped = millis.min(self.config.max_delay.as_millis());
65 self.current_delay = Duration::from_millis(capped as u64);
66 delay
67 }
68
69 pub fn reset(&mut self) {
70 self.current_delay = self.config.initial_delay;
71 }
72
73 pub fn current_delay(&self) -> Duration {
74 self.current_delay
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn backoff_default_config_values() {
84 let cfg = BackoffConfig::default();
85 assert_eq!(cfg.initial_delay, Duration::from_millis(100));
86 assert_eq!(cfg.multiplier, 2.0);
87 assert_eq!(cfg.max_delay, Duration::from_secs(30));
88 }
89
90 #[test]
91 fn backoff_state_exponential_growth() {
92 let mut state = BackoffState::new(BackoffConfig::default());
93 assert_eq!(state.next_delay(), Duration::from_millis(100));
94 assert_eq!(state.next_delay(), Duration::from_millis(200));
95 assert_eq!(state.next_delay(), Duration::from_millis(400));
96 assert_eq!(state.next_delay(), Duration::from_millis(800));
97 }
98
99 #[test]
100 fn backoff_state_caps_at_max() {
101 let cfg = BackoffConfig {
102 max_delay: Duration::from_millis(500),
103 ..Default::default()
104 };
105 let mut state = BackoffState::new(cfg);
106 assert_eq!(state.next_delay(), Duration::from_millis(100));
107 assert_eq!(state.next_delay(), Duration::from_millis(200));
108 assert_eq!(state.next_delay(), Duration::from_millis(400));
109 assert_eq!(state.next_delay(), Duration::from_millis(500));
110 assert_eq!(state.next_delay(), Duration::from_millis(500));
111 }
112
113 #[test]
114 fn backoff_state_reset() {
115 let mut state = BackoffState::new(BackoffConfig::default());
116 state.next_delay();
117 state.next_delay();
118 assert_eq!(state.current_delay(), Duration::from_millis(400));
119 state.reset();
120 assert_eq!(state.current_delay(), Duration::from_millis(100));
121 assert_eq!(state.next_delay(), Duration::from_millis(100));
122 }
123
124 #[test]
125 fn backoff_delay_for_attempt_stateless() {
126 let cfg = BackoffConfig::default();
127 assert_eq!(cfg.delay_for_attempt(0), Duration::from_millis(100));
128 assert_eq!(cfg.delay_for_attempt(1), Duration::from_millis(200));
129 assert_eq!(cfg.delay_for_attempt(2), Duration::from_millis(400));
130 }
131
132 #[test]
133 fn backoff_delay_for_attempt_caps() {
134 let cfg = BackoffConfig {
135 max_delay: Duration::from_secs(30),
136 ..Default::default()
137 };
138 let d = cfg.delay_for_attempt(100);
139 assert_eq!(d, Duration::from_secs(30));
140 }
141
142 #[test]
143 fn backoff_custom_multiplier() {
144 let cfg = BackoffConfig {
145 multiplier: 1.5,
146 initial_delay: Duration::from_millis(200),
147 max_delay: Duration::from_secs(60),
148 };
149 let mut state = BackoffState::new(cfg);
150 assert_eq!(state.next_delay(), Duration::from_millis(200));
151 assert_eq!(state.next_delay(), Duration::from_millis(300));
152 assert_eq!(state.next_delay(), Duration::from_millis(450));
153 }
154}