Skip to main content

retry_block/delay/
mod.rs

1//! Different types of delay for retryable operations.
2
3use std::time::Duration;
4
5mod random;
6
7pub use random::{jitter, jitter_rng, Range};
8
9/// The sum of cumulative retry delays is bounded by some finite amount.
10#[derive(Debug)]
11pub struct Bounded<T> {
12    inner: T,
13    acc: Duration,
14    max: Duration,
15}
16
17impl<T> Bounded<T>
18where
19    T: Iterator<Item = Duration>,
20{
21    pub fn new<U>(inner: U, max: Duration) -> Self
22    where
23        U: IntoIterator<Item = Duration, IntoIter = T>,
24    {
25        Self {
26            inner: inner.into_iter(),
27            max,
28            acc: Default::default(),
29        }
30    }
31}
32
33impl<T> Iterator for Bounded<T>
34where
35    T: Iterator<Item = Duration>,
36{
37    type Item = Duration;
38
39    fn next(&mut self) -> Option<Duration> {
40        self.inner.next().filter(|next| {
41            if let Some(acc) = self.acc.checked_add(*next) {
42                self.acc = acc;
43                self.acc <= self.max
44            } else {
45                false
46            }
47        })
48    }
49}
50
51/// Each retry increases the delay since the last exponentially.
52#[derive(Debug, Clone)]
53pub struct Exponential {
54    current: Duration,
55    factor: f64,
56}
57
58impl Exponential {
59    /// Creates a new `Exponential` using a random proportion of the given
60    /// duration as the initial delay.
61    pub fn new(duration: Duration) -> Self {
62        Self::with_factor(duration, duration.as_millis() as f64)
63    }
64
65    /// Creates a new `Exponential` using a random proportion of the given
66    /// duration as the initial delay and a variable multiplication factor.
67    pub fn with_factor(base: Duration, factor: f64) -> Self {
68        Self {
69            current: jitter(base),
70            factor,
71        }
72    }
73
74    /// Creates a new `Exponential` using the given duration as the initial
75    /// delay.
76    pub fn exact(duration: Duration) -> Self {
77        Self::exact_with_factor(duration, duration.as_millis() as f64)
78    }
79
80    /// Creates a new `Exponential` using the given duration as the initial
81    /// delay and a variable multiplication factor.
82    pub fn exact_with_factor(base: Duration, factor: f64) -> Self {
83        Self {
84            current: base,
85            factor,
86        }
87    }
88
89    /// Applies an upper bound of `max` to this exponential delay generator.
90    pub fn bounded(self, max: Duration) -> Bounded<Self> {
91        Bounded::new(self, max)
92    }
93}
94
95impl Iterator for Exponential {
96    type Item = Duration;
97
98    fn next(&mut self) -> Option<Duration> {
99        fn try_from_secs_f64(secs: f64) -> Option<Duration> {
100            const NANOS_PER_SEC: u32 = 1_000_000_000;
101            const MAX_NANOS_F64: f64 = ((u64::MAX as u128 + 1) * (NANOS_PER_SEC as u128)) as f64;
102            let nanos = secs * (NANOS_PER_SEC as f64);
103            if !nanos.is_finite() || nanos >= MAX_NANOS_F64 || nanos < 0.0 {
104                None
105            } else {
106                Some(Duration::from_secs_f64(secs))
107            }
108        }
109
110        let duration = self.current;
111
112        let next_secs = self.current.as_secs_f64() * self.factor;
113        self.current = try_from_secs_f64(next_secs).unwrap_or(self.current);
114
115        Some(duration)
116    }
117}
118
119impl From<Duration> for Exponential {
120    fn from(duration: Duration) -> Self {
121        Self::new(duration)
122    }
123}
124
125#[test]
126fn exponential_with_factor() {
127    let mut iter = Exponential::exact_with_factor(Duration::from_secs(1), 2.0);
128    assert_eq!(iter.next(), Some(Duration::from_secs(1)));
129    assert_eq!(iter.next(), Some(Duration::from_secs(2)));
130    assert_eq!(iter.next(), Some(Duration::from_secs(4)));
131    assert_eq!(iter.next(), Some(Duration::from_secs(8)));
132    assert_eq!(iter.next(), Some(Duration::from_secs(16)));
133    assert_eq!(iter.next(), Some(Duration::from_secs(32)));
134}
135
136#[test]
137fn exponential_overflow() {
138    let mut iter = Exponential::exact(Duration::MAX);
139    assert_eq!(iter.next(), Some(Duration::MAX));
140    assert_eq!(iter.next(), Some(Duration::MAX));
141}
142
143#[test]
144fn exponential_with_upper_bound() {
145    let mut iter =
146        Exponential::exact_with_factor(Duration::from_secs(1), 2.0).bounded(Duration::from_secs(4));
147    assert_eq!(iter.next(), Some(Duration::from_secs(1)));
148    assert_eq!(iter.next(), Some(Duration::from_secs(2)));
149    // 1 + 2 + 4 > 5 => upper bound would be reached
150    assert_eq!(iter.next(), None);
151}
152
153/// Each retry uses a delay which is the sum of the two previous delays.
154///
155/// Depending on the problem at hand, a fibonacci delay strategy might
156/// perform better and lead to better throughput than the `Exponential`
157/// strategy.
158///
159/// See ["A Performance Comparison of Different Backoff Algorithms under Different Rebroadcast Probabilities for MANETs."](http://www.comp.leeds.ac.uk/ukpew09/papers/12.pdf)
160/// for more details.
161#[derive(Debug, Clone)]
162pub struct Fibonacci {
163    curr: Duration,
164    next: Duration,
165}
166
167impl Fibonacci {
168    /// Creates a new `Fibonacci` using a random proportion of the given duration.
169    pub fn new(duration: Duration) -> Fibonacci {
170        let duration = jitter(duration);
171        Fibonacci {
172            curr: duration,
173            next: duration,
174        }
175    }
176    /// Creates a new `Fibonacci` using the given duration.
177    pub fn exact(duration: Duration) -> Fibonacci {
178        Fibonacci {
179            curr: duration,
180            next: duration,
181        }
182    }
183}
184
185impl Iterator for Fibonacci {
186    type Item = Duration;
187
188    fn next(&mut self) -> Option<Duration> {
189        let duration = self.curr;
190
191        let next = self.curr.saturating_add(self.next);
192        self.curr = self.next;
193        self.next = next;
194
195        Some(duration)
196    }
197}
198
199impl From<Duration> for Fibonacci {
200    fn from(duration: Duration) -> Self {
201        Self::new(duration)
202    }
203}
204
205#[test]
206fn fibonacci() {
207    let mut iter = Fibonacci::exact(Duration::from_millis(10));
208    assert_eq!(iter.next(), Some(Duration::from_millis(10)));
209    assert_eq!(iter.next(), Some(Duration::from_millis(10)));
210    assert_eq!(iter.next(), Some(Duration::from_millis(20)));
211    assert_eq!(iter.next(), Some(Duration::from_millis(30)));
212    assert_eq!(iter.next(), Some(Duration::from_millis(50)));
213    assert_eq!(iter.next(), Some(Duration::from_millis(80)));
214}
215
216#[test]
217fn fibonacci_saturated() {
218    let mut iter = Fibonacci::exact(Duration::MAX);
219    assert_eq!(iter.next(), Some(Duration::MAX));
220    assert_eq!(iter.next(), Some(Duration::MAX));
221}
222
223/// Each retry uses a fixed delay.
224#[derive(Debug, Clone)]
225pub struct Fixed {
226    duration: Duration,
227}
228
229impl Fixed {
230    /// Creates a new `Fixed` using a random proportion of the given duration in milliseconds.
231    pub fn new(duration: Duration) -> Self {
232        Fixed {
233            duration: jitter(duration),
234        }
235    }
236
237    /// Creates a new `Fixed` using the given duration in milliseconds.
238    pub fn exact(duration: Duration) -> Self {
239        Fixed { duration }
240    }
241}
242
243impl Iterator for Fixed {
244    type Item = Duration;
245
246    fn next(&mut self) -> Option<Duration> {
247        Some(self.duration)
248    }
249}
250
251impl From<Duration> for Fixed {
252    fn from(duration: Duration) -> Self {
253        Self { duration }
254    }
255}
256
257/// Each retry happens immediately without any delay.
258#[derive(Debug, Clone)]
259pub struct NoDelay;
260
261impl Iterator for NoDelay {
262    type Item = Duration;
263
264    fn next(&mut self) -> Option<Duration> {
265        Some(Duration::default())
266    }
267}
268
269#[cfg(test)]
270mod test {
271    use crate::delay::Exponential;
272    use std::time::Duration;
273
274    #[test]
275    fn test_bounded_overflow() {
276        let mut delays = Exponential::exact_with_factor(Duration::MAX, 1.0).bounded(Duration::MAX);
277
278        assert_eq!(delays.next(), Some(Duration::MAX));
279
280        assert_eq!(delays.next(), None);
281    }
282}