backon/backoff/
fibonacci.rs1use core::time::Duration;
2
3use crate::backoff::BackoffBuilder;
4
5#[derive(Debug, Clone, Copy)]
37pub struct FibonacciBuilder {
38 jitter: bool,
39 seed: Option<u64>,
40 min_delay: Duration,
41 max_delay: Option<Duration>,
42 max_times: Option<usize>,
43}
44
45impl Default for FibonacciBuilder {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl FibonacciBuilder {
52 pub const fn new() -> Self {
54 Self {
55 jitter: false,
56 seed: None,
57 min_delay: Duration::from_secs(1),
58 max_delay: Some(Duration::from_secs(60)),
59 max_times: Some(3),
60 }
61 }
62
63 pub const fn with_jitter(mut self) -> Self {
67 self.jitter = true;
68 self
69 }
70
71 pub fn with_jitter_seed(mut self, seed: u64) -> Self {
73 self.seed = Some(seed);
74 self
75 }
76
77 pub const fn with_min_delay(mut self, min_delay: Duration) -> Self {
79 self.min_delay = min_delay;
80 self
81 }
82
83 pub const fn with_max_delay(mut self, max_delay: Duration) -> Self {
87 self.max_delay = Some(max_delay);
88 self
89 }
90
91 pub const fn without_max_delay(mut self) -> Self {
97 self.max_delay = None;
98 self
99 }
100
101 pub const fn with_max_times(mut self, max_times: usize) -> Self {
105 self.max_times = Some(max_times);
106 self
107 }
108
109 pub const fn without_max_times(mut self) -> Self {
115 self.max_times = None;
116 self
117 }
118}
119
120impl BackoffBuilder for FibonacciBuilder {
121 type Backoff = FibonacciBackoff;
122
123 fn build(self) -> Self::Backoff {
124 FibonacciBackoff {
125 jitter: self.jitter,
126 rng: if let Some(seed) = self.seed {
127 fastrand::Rng::with_seed(seed)
128 } else {
129 #[cfg(feature = "std")]
130 let rng = fastrand::Rng::new();
131
132 #[cfg(not(feature = "std"))]
133 let rng = fastrand::Rng::with_seed(super::RANDOM_SEED);
134
135 rng
136 },
137 min_delay: self.min_delay,
138 max_delay: self.max_delay,
139 max_times: self.max_times,
140
141 previous_delay: None,
142 current_delay: None,
143 attempts: 0,
144 }
145 }
146}
147
148impl BackoffBuilder for &FibonacciBuilder {
149 type Backoff = FibonacciBackoff;
150
151 fn build(self) -> Self::Backoff {
152 (*self).build()
153 }
154}
155
156#[doc(hidden)]
160#[derive(Debug)]
161pub struct FibonacciBackoff {
162 jitter: bool,
163 rng: fastrand::Rng,
164 min_delay: Duration,
165 max_delay: Option<Duration>,
166 max_times: Option<usize>,
167
168 previous_delay: Option<Duration>,
169 current_delay: Option<Duration>,
170 attempts: usize,
171}
172
173impl Iterator for FibonacciBackoff {
174 type Item = Duration;
175
176 fn next(&mut self) -> Option<Self::Item> {
177 if self.attempts >= self.max_times.unwrap_or(usize::MAX) {
178 return None;
179 }
180 self.attempts += 1;
181
182 match self.current_delay {
183 None => {
184 let mut next = self.min_delay;
186 self.current_delay = Some(next);
187
188 if self.jitter {
190 next += next.mul_f32(self.rng.f32());
191 }
192
193 Some(next)
194 }
195 Some(cur) => {
196 let mut next = cur;
197
198 if next < self.max_delay.unwrap_or(Duration::MAX) {
200 if let Some(prev) = self.previous_delay {
201 next = next.saturating_add(prev);
202 self.current_delay = Some(next);
203 }
204 self.previous_delay = Some(cur);
205 }
206
207 if self.jitter {
209 next += self.min_delay.mul_f32(self.rng.f32());
210 }
211
212 Some(next)
213 }
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use core::time::Duration;
221
222 #[cfg(target_arch = "wasm32")]
223 use wasm_bindgen_test::wasm_bindgen_test as test;
224
225 use super::*;
226
227 const TEST_BUILDER: FibonacciBuilder = FibonacciBuilder::new()
228 .with_jitter()
229 .with_min_delay(Duration::from_secs(2))
230 .with_max_delay(Duration::from_secs(30))
231 .with_max_times(5);
232
233 #[test]
234 fn test_fibonacci_default() {
235 let mut fib = FibonacciBuilder::default().build();
236
237 assert_eq!(Some(Duration::from_secs(1)), fib.next());
238 assert_eq!(Some(Duration::from_secs(1)), fib.next());
239 assert_eq!(Some(Duration::from_secs(2)), fib.next());
240 assert_eq!(None, fib.next());
241 }
242
243 #[test]
244 fn test_fibonacci_jitter() {
245 let mut fib = FibonacciBuilder::default().with_jitter().build();
246
247 let v = fib.next().expect("value must valid");
248 assert!(v >= Duration::from_secs(1), "current: {v:?}");
249 assert!(v < Duration::from_secs(2), "current: {v:?}");
250
251 let v = fib.next().expect("value must valid");
252 assert!(v >= Duration::from_secs(1), "current: {v:?}");
253 assert!(v < Duration::from_secs(2), "current: {v:?}");
254
255 let v = fib.next().expect("value must valid");
256 assert!(v >= Duration::from_secs(2), "current: {v:?}");
257 assert!(v < Duration::from_secs(3), "current: {v:?}");
258
259 assert_eq!(None, fib.next());
260 }
261
262 #[test]
263 fn test_fibonacci_min_delay() {
264 let mut fib = FibonacciBuilder::default()
265 .with_min_delay(Duration::from_millis(500))
266 .build();
267
268 assert_eq!(Some(Duration::from_millis(500)), fib.next());
269 assert_eq!(Some(Duration::from_millis(500)), fib.next());
270 assert_eq!(Some(Duration::from_secs(1)), fib.next());
271 assert_eq!(None, fib.next());
272 }
273
274 #[test]
275 fn test_fibonacci_max_delay() {
276 let mut fib = FibonacciBuilder::default()
277 .with_max_times(4)
278 .with_max_delay(Duration::from_secs(2))
279 .build();
280
281 assert_eq!(Some(Duration::from_secs(1)), fib.next());
282 assert_eq!(Some(Duration::from_secs(1)), fib.next());
283 assert_eq!(Some(Duration::from_secs(2)), fib.next());
284 assert_eq!(Some(Duration::from_secs(2)), fib.next());
285 assert_eq!(None, fib.next());
286 }
287
288 #[test]
289 fn test_fibonacci_no_max_delay() {
290 let mut fib = FibonacciBuilder::default()
291 .with_max_times(4)
292 .with_min_delay(Duration::from_secs(10_000_000_000_000_000_000))
293 .without_max_delay()
294 .build();
295
296 assert_eq!(
297 Some(Duration::from_secs(10_000_000_000_000_000_000)),
298 fib.next()
299 );
300 assert_eq!(
301 Some(Duration::from_secs(10_000_000_000_000_000_000)),
302 fib.next()
303 );
304 assert_eq!(Some(Duration::MAX), fib.next());
305 assert_eq!(Some(Duration::MAX), fib.next());
306 assert_eq!(None, fib.next());
307 }
308
309 #[test]
310 fn test_fibonacci_max_times() {
311 let mut fib = FibonacciBuilder::default().with_max_times(6).build();
312
313 assert_eq!(Some(Duration::from_secs(1)), fib.next());
314 assert_eq!(Some(Duration::from_secs(1)), fib.next());
315 assert_eq!(Some(Duration::from_secs(2)), fib.next());
316 assert_eq!(Some(Duration::from_secs(3)), fib.next());
317 assert_eq!(Some(Duration::from_secs(5)), fib.next());
318 assert_eq!(Some(Duration::from_secs(8)), fib.next());
319 assert_eq!(None, fib.next());
320 }
321
322 #[test]
323 fn test_fibonacci_no_max_times() {
324 let mut fib = FibonacciBuilder::default()
325 .with_min_delay(Duration::from_secs(0))
326 .without_max_times()
327 .build();
328
329 for _ in 0..10_000 {
332 assert_eq!(Some(Duration::from_secs(0)), fib.next());
333 }
334 }
335
336 #[allow(clippy::assertions_on_constants)]
338 #[test]
339 fn test_fibonacci_const_builder() {
340 assert!(TEST_BUILDER.jitter);
341 assert_eq!(TEST_BUILDER.min_delay, Duration::from_secs(2));
342 assert_eq!(TEST_BUILDER.max_delay, Some(Duration::from_secs(30)));
343 assert_eq!(TEST_BUILDER.max_times, Some(5));
344 }
345}