Skip to main content

gix_utils/
backoff.rs

1use std::time::Duration;
2
3fn randomize(backoff_ms: usize) -> usize {
4    let new_value = (fastrand::usize(750..=1250) * backoff_ms) / 1000;
5    if new_value == 0 { backoff_ms } else { new_value }
6}
7
8/// A utility to calculate steps for quadratic backoff similar to how it's done in `git`.
9pub struct Quadratic<Fn> {
10    multiplier: usize,
11    max_multiplier: usize,
12    exponent: usize,
13    transform: Fn,
14}
15
16impl Default for Quadratic<fn(usize) -> usize> {
17    fn default() -> Self {
18        Quadratic {
19            multiplier: 1,
20            max_multiplier: 1000,
21            exponent: 1,
22            transform: std::convert::identity,
23        }
24    }
25}
26
27impl Quadratic<fn(usize) -> usize> {
28    /// Create a new quadratic backoff iterator that backs off in randomized, ever increasing steps.
29    pub fn default_with_random() -> Self {
30        Quadratic {
31            multiplier: 1,
32            max_multiplier: 1000,
33            exponent: 1,
34            transform: randomize,
35        }
36    }
37}
38
39impl<Transform> Quadratic<Transform>
40where
41    Transform: Fn(usize) -> usize,
42{
43    /// Return an iterator that yields `Duration` instances to sleep on until `time` is depleted.
44    pub fn until_no_remaining(&mut self, time: Duration) -> impl Iterator<Item = Duration> + '_ {
45        let mut elapsed = Duration::default();
46        let mut stop_next_iteration = false;
47        self.take_while(move |d| {
48            if stop_next_iteration {
49                false
50            } else {
51                elapsed += *d;
52                if elapsed > time {
53                    stop_next_iteration = true;
54                }
55                true
56            }
57        })
58    }
59}
60
61impl<Transform> Iterator for Quadratic<Transform>
62where
63    Transform: Fn(usize) -> usize,
64{
65    type Item = Duration;
66
67    fn next(&mut self) -> Option<Self::Item> {
68        let wait = Duration::from_millis((self.transform)(self.multiplier) as u64);
69
70        self.multiplier += 2 * self.exponent + 1;
71        if self.multiplier > self.max_multiplier {
72            self.multiplier = self.max_multiplier;
73        } else {
74            self.exponent += 1;
75        }
76        Some(wait)
77    }
78}