retry_strategy/strategy/
mod.rs

1//! Different types of strategy for retryable operations.
2
3use std::time::Duration;
4use std::u64::MAX as U64_MAX;
5
6#[cfg(feature = "random")]
7mod random;
8
9#[cfg(feature = "random")]
10pub use random::{jitter, Range};
11
12/// Each retry increases the delay since the last exponentially.
13#[derive(Debug)]
14pub struct Exponential {
15    current: u64,
16    factor: f64,
17}
18
19impl Exponential {
20    /// Create a new [`Exponential`] using the given millisecond duration as the initial delay and
21    /// an exponential backoff factor of `2.0`.
22    pub fn from_millis(base: u64) -> Self {
23        Exponential {
24            current: base,
25            factor: 2.0,
26        }
27    }
28
29    /// Create a new [`Exponential`] using the given millisecond duration as the initial delay and
30    /// the same duration as the exponential backoff factor. This was the behavior of
31    /// [`Exponential::from_millis`] prior to version 2.0.
32    pub fn from_millis_with_base_factor(base: u64) -> Self {
33        Exponential {
34            current: base,
35            factor: base as f64,
36        }
37    }
38
39    /// Create a new [`Exponential`] using the given millisecond duration as the initial delay and
40    /// the given exponential backoff factor.
41    pub fn from_millis_with_factor(base: u64, factor: f64) -> Self {
42        Exponential {
43            current: base,
44            factor,
45        }
46    }
47}
48
49impl Iterator for Exponential {
50    type Item = Duration;
51
52    fn next(&mut self) -> Option<Duration> {
53        let duration = Duration::from_millis(self.current);
54
55        let next = (self.current as f64) * self.factor;
56        self.current = if next > (U64_MAX as f64) {
57            U64_MAX
58        } else {
59            next as u64
60        };
61
62        Some(duration)
63    }
64}
65
66impl From<Duration> for Exponential {
67    fn from(duration: Duration) -> Self {
68        Self::from_millis(duration.as_millis() as u64)
69    }
70}
71
72/// Each retry uses a delay which is the sum of the two previous delays.
73///
74/// Depending on the problem at hand, a fibonacci delay strategy might perform better and lead to
75/// better throughput than the [`Exponential`] strategy.
76///
77/// See ["A Performance Comparison of Different Backoff Algorithms under Different Rebroadcast
78/// Probabilities for MANETs"](https://www.researchgate.net/publication/255672213_A_Performance_Comparison_of_Different_Backoff_Algorithms_under_Different_Rebroadcast_Probabilities_for_MANET's)
79/// for more details.
80#[derive(Debug)]
81pub struct Fibonacci {
82    curr: u64,
83    next: u64,
84}
85
86impl Fibonacci {
87    /// Create a new [`Fibonacci`] using the given duration in milliseconds.
88    pub fn from_millis(millis: u64) -> Fibonacci {
89        Fibonacci {
90            curr: millis,
91            next: millis,
92        }
93    }
94}
95
96impl Iterator for Fibonacci {
97    type Item = Duration;
98
99    fn next(&mut self) -> Option<Duration> {
100        let duration = Duration::from_millis(self.curr);
101
102        if let Some(next_next) = self.curr.checked_add(self.next) {
103            self.curr = self.next;
104            self.next = next_next;
105        } else {
106            self.curr = self.next;
107            self.next = U64_MAX;
108        }
109
110        Some(duration)
111    }
112}
113
114impl From<Duration> for Fibonacci {
115    fn from(duration: Duration) -> Self {
116        Self::from_millis(duration.as_millis() as u64)
117    }
118}
119
120/// Each retry uses a fixed delay.
121#[derive(Debug)]
122pub struct Fixed {
123    duration: Duration,
124}
125
126impl Fixed {
127    /// Create a new [`Fixed`] using the given duration in milliseconds.
128    pub fn from_millis(millis: u64) -> Self {
129        Fixed {
130            duration: Duration::from_millis(millis),
131        }
132    }
133}
134
135impl Iterator for Fixed {
136    type Item = Duration;
137
138    fn next(&mut self) -> Option<Duration> {
139        Some(self.duration)
140    }
141}
142
143impl From<Duration> for Fixed {
144    fn from(delay: Duration) -> Self {
145        Self { duration: delay }
146    }
147}
148
149/// Each retry happens immediately without any delay.
150#[derive(Debug)]
151pub struct NoDelay;
152
153impl Iterator for NoDelay {
154    type Item = Duration;
155
156    fn next(&mut self) -> Option<Duration> {
157        Some(Duration::default())
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::{Exponential, Fibonacci};
164    use crate::ToDuration;
165    use std::time::Duration;
166    use std::u64::MAX as U64_MAX;
167
168    #[test]
169    fn test_opportunity() {
170        let mut iter = vec![100.ms(), 200.ms(), 300.ms()].into_iter();
171        assert_eq!(iter.next(), Some(Duration::from_millis(100)));
172        assert_eq!(iter.next(), Some(Duration::from_millis(200)));
173        assert_eq!(iter.next(), Some(Duration::from_millis(300)));
174        assert_eq!(iter.next(), None);
175    }
176
177    #[test]
178    fn exponential_with_factor() {
179        let mut iter = Exponential::from_millis_with_factor(1000, 2.0);
180        assert_eq!(iter.next(), Some(Duration::from_millis(1000)));
181        assert_eq!(iter.next(), Some(Duration::from_millis(2000)));
182        assert_eq!(iter.next(), Some(Duration::from_millis(4000)));
183        assert_eq!(iter.next(), Some(Duration::from_millis(8000)));
184        assert_eq!(iter.next(), Some(Duration::from_millis(16000)));
185        assert_eq!(iter.next(), Some(Duration::from_millis(32000)));
186    }
187
188    #[test]
189    fn exponential_overflow() {
190        let mut iter = Exponential::from_millis(U64_MAX);
191        assert_eq!(iter.next(), Some(Duration::from_millis(U64_MAX)));
192        assert_eq!(iter.next(), Some(Duration::from_millis(U64_MAX)));
193    }
194
195    #[test]
196    fn fibonacci() {
197        let mut iter = Fibonacci::from_millis(10);
198        assert_eq!(iter.next(), Some(Duration::from_millis(10)));
199        assert_eq!(iter.next(), Some(Duration::from_millis(10)));
200        assert_eq!(iter.next(), Some(Duration::from_millis(20)));
201        assert_eq!(iter.next(), Some(Duration::from_millis(30)));
202        assert_eq!(iter.next(), Some(Duration::from_millis(50)));
203        assert_eq!(iter.next(), Some(Duration::from_millis(80)));
204    }
205
206    #[test]
207    fn fibonacci_saturated() {
208        let mut iter = Fibonacci::from_millis(U64_MAX);
209        assert_eq!(iter.next(), Some(Duration::from_millis(U64_MAX)));
210        assert_eq!(iter.next(), Some(Duration::from_millis(U64_MAX)));
211    }
212}