nautilus_network/
backoff.rs1use std::time::Duration;
25
26use rand::Rng;
27
28#[derive(Clone, Debug)]
29pub struct ExponentialBackoff {
30 delay_initial: Duration,
32 delay_max: Duration,
34 delay_current: Duration,
36 factor: f64,
38 jitter_ms: u64,
40 immediate_first: bool,
42}
43
44impl ExponentialBackoff {
52 pub fn new(
54 delay_initial: Duration,
55 delay_max: Duration,
56 factor: f64,
57 jitter_ms: u64,
58 immediate_first: bool,
59 ) -> Self {
60 Self {
61 delay_initial,
62 delay_max,
63 delay_current: delay_initial,
64 factor,
65 jitter_ms,
66 immediate_first,
67 }
68 }
69
70 pub fn next_duration(&mut self) -> Duration {
76 if self.immediate_first && self.delay_current == self.delay_initial {
77 self.immediate_first = false;
78 return Duration::ZERO;
79 }
80
81 let jitter = rand::thread_rng().gen_range(0..=self.jitter_ms);
83 let delay_with_jitter = self.delay_current + Duration::from_millis(jitter);
84
85 let current_nanos = self.delay_current.as_nanos();
87 let max_nanos = self.delay_max.as_nanos() as u64;
88 let next_nanos = (current_nanos as f64 * self.factor) as u64;
89 self.delay_current = Duration::from_nanos(std::cmp::min(next_nanos, max_nanos));
90
91 delay_with_jitter
92 }
93
94 pub fn reset(&mut self) {
96 self.delay_current = self.delay_initial;
97 }
98
99 pub fn current_delay(&self) -> Duration {
103 self.delay_current
104 }
105}
106
107#[cfg(test)]
111mod tests {
112 use std::time::Duration;
113
114 use rstest::rstest;
115
116 use super::*;
117
118 #[rstest]
119 fn test_no_jitter_exponential_growth() {
120 let initial = Duration::from_millis(100);
121 let max = Duration::from_millis(1600);
122 let factor = 2.0;
123 let jitter = 0;
124 let mut backoff = ExponentialBackoff::new(initial, max, factor, jitter, false);
125
126 let d1 = backoff.next_duration();
128 assert_eq!(d1, Duration::from_millis(100));
129
130 let d2 = backoff.next_duration();
132 assert_eq!(d2, Duration::from_millis(200));
133
134 let d3 = backoff.next_duration();
136 assert_eq!(d3, Duration::from_millis(400));
137
138 let d4 = backoff.next_duration();
140 assert_eq!(d4, Duration::from_millis(800));
141
142 let d5 = backoff.next_duration();
144 assert_eq!(d5, Duration::from_millis(1600));
145
146 let d6 = backoff.next_duration();
148 assert_eq!(d6, Duration::from_millis(1600));
149 }
150
151 #[rstest]
152 fn test_reset() {
153 let initial = Duration::from_millis(100);
154 let max = Duration::from_millis(1600);
155 let factor = 2.0;
156 let jitter = 0;
157 let mut backoff = ExponentialBackoff::new(initial, max, factor, jitter, false);
158
159 let _ = backoff.next_duration(); backoff.reset();
162 let d = backoff.next_duration();
163 assert_eq!(d, Duration::from_millis(100));
165 }
166
167 #[rstest]
168 fn test_jitter_within_bounds() {
169 let initial = Duration::from_millis(100);
170 let max = Duration::from_millis(1000);
171 let factor = 2.0;
172 let jitter = 50;
173 for _ in 0..10 {
175 let mut backoff = ExponentialBackoff::new(initial, max, factor, jitter, false);
176 let base = backoff.delay_current;
178 let delay = backoff.next_duration();
179 let min_expected = base;
181 let max_expected = base + Duration::from_millis(jitter);
182 assert!(
183 delay >= min_expected,
184 "Delay {:?} is less than expected minimum {:?}",
185 delay,
186 min_expected
187 );
188 assert!(
189 delay <= max_expected,
190 "Delay {:?} exceeds expected maximum {:?}",
191 delay,
192 max_expected
193 );
194 }
195 }
196
197 #[rstest]
198 fn test_factor_less_than_two() {
199 let initial = Duration::from_millis(100);
200 let max = Duration::from_millis(200);
201 let factor = 1.5;
202 let jitter = 0;
203 let mut backoff = ExponentialBackoff::new(initial, max, factor, jitter, false);
204
205 let d1 = backoff.next_duration();
207 assert_eq!(d1, Duration::from_millis(100));
208
209 let d2 = backoff.next_duration();
211 assert_eq!(d2, Duration::from_millis(150));
212
213 let d3 = backoff.next_duration();
215 assert_eq!(d3, Duration::from_millis(200));
216
217 let d4 = backoff.next_duration();
219 assert_eq!(d4, Duration::from_millis(200));
220 }
221
222 #[rstest]
223 fn test_max_delay_is_respected() {
224 let initial = Duration::from_millis(500);
225 let max = Duration::from_millis(1000);
226 let factor = 3.0;
227 let jitter = 0;
228 let mut backoff = ExponentialBackoff::new(initial, max, factor, jitter, false);
229
230 let d1 = backoff.next_duration();
232 assert_eq!(d1, Duration::from_millis(500));
233
234 let d2 = backoff.next_duration();
236 assert_eq!(d2, Duration::from_millis(1000));
237
238 let d3 = backoff.next_duration();
240 assert_eq!(d3, Duration::from_millis(1000));
241 }
242
243 #[rstest]
244 fn test_current_delay_getter() {
245 let initial = Duration::from_millis(100);
246 let max = Duration::from_millis(1600);
247 let factor = 2.0;
248 let jitter = 0;
249 let mut backoff = ExponentialBackoff::new(initial, max, factor, jitter, false);
250
251 assert_eq!(backoff.current_delay(), initial);
252
253 let _ = backoff.next_duration();
254 assert_eq!(backoff.current_delay(), Duration::from_millis(200));
255
256 let _ = backoff.next_duration();
257 assert_eq!(backoff.current_delay(), Duration::from_millis(400));
258
259 backoff.reset();
260 assert_eq!(backoff.current_delay(), initial);
261 }
262
263 #[rstest]
264 fn test_immediate_first() {
265 let initial = Duration::from_millis(100);
266 let max = Duration::from_millis(1600);
267 let factor = 2.0;
268 let jitter = 0;
269 let mut backoff = ExponentialBackoff::new(initial, max, factor, jitter, true);
270
271 let d1 = backoff.next_duration();
273 assert_eq!(
274 d1,
275 Duration::ZERO,
276 "Expected immediate reconnect (zero delay) on first call"
277 );
278
279 let d2 = backoff.next_duration();
281 assert_eq!(
282 d2, initial,
283 "Expected the delay to be the initial delay after immediate reconnect"
284 );
285
286 let d3 = backoff.next_duration();
288 let expected = initial * 2; assert_eq!(
290 d3, expected,
291 "Expected exponential growth from the initial delay"
292 );
293 }
294}