futures_backoff/strategy/
mod.rs

1use std::time::Duration;
2
3use super::{Action, Condition, Retry, RetryIf};
4
5mod fixed_interval;
6mod exponential_backoff;
7mod fibonacci_backoff;
8mod jitter;
9
10pub use self::fixed_interval::FixedInterval;
11pub use self::exponential_backoff::ExponentialBackoff;
12pub use self::fibonacci_backoff::FibonacciBackoff;
13pub use self::jitter::jitter;
14
15#[derive(Debug)]
16enum FactorType {
17    Exponential,
18    Fibonacci,
19    Fixed
20}
21
22/// Configurable retry strategy.
23///
24/// Implements `Default`, which returns an exponential backoff strategy
25/// with a delay of 1 second and a maximum of 5 retries.
26///
27/// # Example
28///
29/// ```rust
30/// # extern crate futures;
31/// # extern crate futures_backoff;
32/// # use futures::{Future, future};
33/// # use futures_backoff::Strategy;
34/// #
35/// # fn main() {
36/// let strategy = Strategy::default()
37///     .with_max_retries(3);
38///
39/// let future = strategy.retry(|| {
40///     // do some real-world stuff here...
41///     future::ok::<u32, ::std::io::Error>(42)
42/// });
43/// #
44/// # assert_eq!(future.wait().unwrap(), 42);
45/// # }
46/// ```
47#[derive(Debug)]
48pub struct Strategy {
49    factor: FactorType,
50    delay: Duration,
51    max_delay: Option<Duration>,
52    max_retries: usize,
53    jitter: bool
54}
55
56impl Default for Strategy {
57    fn default() -> Strategy {
58        Strategy {
59            factor: FactorType::Exponential,
60            delay: Duration::from_millis(1000),
61            max_delay: None,
62            max_retries: 5,
63            jitter: false
64        }
65    }
66}
67
68impl Strategy {
69    /// Creates a retry strategy driven by exponential back-off.
70    ///
71    /// The specified duration will be multiplied by `2^n`, where `n` is
72    /// the number of failed attempts.
73    pub fn exponential(delay: Duration) -> Strategy {
74        Strategy::new(FactorType::Exponential, delay)
75    }
76
77    /// Creates a retry strategy driven by a fibonacci back-off.
78    ///
79    /// The specified duration will be multiplied by `fib(n)`, where `n` is
80    /// the number of failed attempts.
81    ///
82    /// Depending on the problem at hand, a fibonacci retry strategy might
83    /// perform better and lead to better throughput than the `ExponentialBackoff`
84    /// strategy.
85    ///
86    /// See ["A Performance Comparison of Different Backoff Algorithms under Different Rebroadcast Probabilities for MANETs."](http://www.comp.leeds.ac.uk/ukpew09/papers/12.pdf)
87    /// for more details.
88    pub fn fibonacci(delay: Duration) -> Strategy {
89        Strategy::new(FactorType::Fibonacci, delay)
90    }
91
92    /// Creates a retry strategy driven by a fixed delay.
93    pub fn fixed(delay: Duration) -> Strategy {
94        Strategy::new(FactorType::Fixed, delay)
95    }
96
97    fn new(factor: FactorType, delay: Duration) -> Strategy {
98        Strategy {
99            factor: factor,
100            delay: delay,
101            max_delay: None,
102            max_retries: 5,
103            jitter: false
104        }
105    }
106
107    /// Sets the maximum delay between two attempts.
108    ///
109    /// By default there is no maximum.
110    pub fn with_max_delay(mut self, duration: Duration) -> Self {
111        self.max_delay = Some(duration);
112        self
113    }
114
115    /// Sets the maximum number of retry attempts.
116    ///
117    /// By default a retry will be attempted 5 times before giving up.
118    pub fn with_max_retries(mut self, retries: usize) -> Self {
119        self.max_retries = retries;
120        self
121    }
122
123    /// Enables or disables jitter on the delay.
124    ///
125    /// Jitter will introduce a random variance to the retry strategy,
126    /// which can be helpful to mitigate the "Thundering Herd" problem.
127    pub fn with_jitter(mut self, jitter: bool) -> Self {
128        self.jitter = jitter;
129        self
130    }
131
132    pub(crate) fn iter(&self) -> StrategyIter {
133        let factor_iter = match self.factor {
134            FactorType::Exponential =>
135                FactorIter::Exponential(ExponentialBackoff::new()),
136            FactorType::Fibonacci =>
137                FactorIter::Fibonacci(FibonacciBackoff::new()),
138            FactorType::Fixed =>
139                FactorIter::Fixed(FixedInterval::new())
140        };
141        StrategyIter {
142            factor_iter: factor_iter,
143            delay: self.delay,
144            max_delay: self.max_delay,
145            retries: self.max_retries,
146            jitter: self.jitter
147        }
148    }
149
150    /// Run the given action, and use this strategy to retry on failure.
151    pub fn retry<A: Action>(&self, action: A) -> Retry<A> {
152        Retry::new(self, action)
153    }
154
155    /// Run the given action, and use this strategy to retry on failure if the error satisfies a given condition.
156    pub fn retry_if<A: Action, C>(&self, action: A, condition: C) -> RetryIf<A, C>
157        where C: Condition<A::Error>
158    {
159        RetryIf::new(self, action, condition)
160    }
161}
162
163enum FactorIter {
164    Exponential(ExponentialBackoff),
165    Fibonacci(FibonacciBackoff),
166    Fixed(FixedInterval),
167}
168
169impl Iterator for FactorIter {
170    type Item = u32;
171
172    fn next(&mut self) -> Option<u32> {
173        match self {
174            &mut FactorIter::Exponential(ref mut iter) => iter.next(),
175            &mut FactorIter::Fibonacci(ref mut iter) => iter.next(),
176            &mut FactorIter::Fixed(ref mut iter) => iter.next(),
177        }
178    }
179}
180
181pub(crate) struct StrategyIter {
182    factor_iter: FactorIter,
183    delay: Duration,
184    max_delay: Option<Duration>,
185    retries: usize,
186    jitter: bool
187}
188
189impl Iterator for StrategyIter {
190    type Item = Duration;
191
192    fn next(&mut self) -> Option<Duration> {
193        if self.retries > 0 {
194            if let Some(factor) = self.factor_iter.next() {
195                if let Some(mut delay) = self.delay.checked_mul(factor) {
196                    if self.jitter {
197                        delay = jitter(delay);
198                    }
199                    if let Some(max_delay) = self.max_delay {
200                        delay = ::std::cmp::min(delay, max_delay);
201                    }
202                    self.retries -= 1;
203                    return Some(delay)
204                }
205            }
206        }
207        None
208    }
209}
210
211#[test]
212fn fixed_returns_delay() {
213    let mut s = Strategy::fixed(Duration::from_millis(123)).iter();
214
215    assert_eq!(s.next(), Some(Duration::from_millis(123)));
216    assert_eq!(s.next(), Some(Duration::from_millis(123)));
217    assert_eq!(s.next(), Some(Duration::from_millis(123)));
218}
219
220#[test]
221fn fibonacci_returns_the_fibonacci_series_starting_at_10() {
222    let mut s = Strategy::fibonacci(Duration::from_millis(10)).iter();
223
224    assert_eq!(s.next(), Some(Duration::from_millis(10)));
225    assert_eq!(s.next(), Some(Duration::from_millis(10)));
226    assert_eq!(s.next(), Some(Duration::from_millis(20)));
227    assert_eq!(s.next(), Some(Duration::from_millis(30)));
228    assert_eq!(s.next(), Some(Duration::from_millis(50)));
229}
230
231#[test]
232fn fibonacci_stops_increasing_at_max_delay() {
233    let mut s = Strategy::fibonacci(Duration::from_millis(10))
234      .with_max_delay(Duration::from_millis(30)).iter();
235
236    assert_eq!(s.next(), Some(Duration::from_millis(10)));
237    assert_eq!(s.next(), Some(Duration::from_millis(10)));
238    assert_eq!(s.next(), Some(Duration::from_millis(20)));
239    assert_eq!(s.next(), Some(Duration::from_millis(30)));
240    assert_eq!(s.next(), Some(Duration::from_millis(30)));
241}
242
243#[test]
244fn fibonacci_returns_max_when_max_less_than_base() {
245    let mut s = Strategy::fibonacci(Duration::from_millis(10))
246      .with_max_delay(Duration::from_millis(10)).iter();
247
248    assert_eq!(s.next(), Some(Duration::from_millis(10)));
249    assert_eq!(s.next(), Some(Duration::from_millis(10)));
250}
251
252#[test]
253fn exponential_returns_multiples_of_10ms() {
254    let mut s = Strategy::exponential(Duration::from_millis(10)).iter();
255
256    assert_eq!(s.next(), Some(Duration::from_millis(10)));
257    assert_eq!(s.next(), Some(Duration::from_millis(20)));
258    assert_eq!(s.next(), Some(Duration::from_millis(40)));
259}
260
261#[test]
262fn exponential_returns_multiples_of_100ms() {
263    let mut s = Strategy::exponential(Duration::from_millis(100)).iter();
264
265    assert_eq!(s.next(), Some(Duration::from_millis(100)));
266    assert_eq!(s.next(), Some(Duration::from_millis(200)));
267    assert_eq!(s.next(), Some(Duration::from_millis(400)));
268}
269
270#[test]
271fn exponential_stops_increasing_at_max_delay() {
272    let mut s = Strategy::exponential(Duration::from_millis(20))
273      .with_max_delay(Duration::from_millis(40)).iter();
274
275    assert_eq!(s.next(), Some(Duration::from_millis(20)));
276    assert_eq!(s.next(), Some(Duration::from_millis(40)));
277    assert_eq!(s.next(), Some(Duration::from_millis(40)));
278}
279
280#[test]
281fn exponential_returns_max_when_max_less_than_base() {
282    let mut s = Strategy::exponential(Duration::from_millis(20))
283      .with_max_delay(Duration::from_millis(10)).iter();
284
285    assert_eq!(s.next(), Some(Duration::from_millis(10)));
286    assert_eq!(s.next(), Some(Duration::from_millis(10)));
287}