retry_future/retry_strategy/
exponential.rs

1use crate::{RetryStrategy, TooManyAttempts};
2use std::time::Duration;
3
4/// Retry futures exponentially.
5///
6/// ## Examples
7///
8/// ```rust
9/// use retry_future::RetryStrategy;
10/// use retry_future::ExponentialRetryStrategy;
11/// use std::time::Duration;
12///
13/// let mut strategy = ExponentialRetryStrategy {
14///     base: 3,
15///     max_attempts: 5,
16///     initial_delay: Duration::from_secs(1),
17///     ..Default::default()
18/// };
19///
20/// assert_eq!(strategy.check_attempt(0).unwrap(), Duration::from_secs(1));
21/// assert_eq!(strategy.check_attempt(1).unwrap(), Duration::from_secs(3));
22/// assert_eq!(strategy.check_attempt(2).unwrap(), Duration::from_secs(9));
23/// assert_eq!(strategy.check_attempt(3).unwrap(), Duration::from_secs(27));
24/// assert_eq!(strategy.check_attempt(4).unwrap(), Duration::from_secs(81));
25///
26/// assert!(strategy.check_attempt(5).is_err());
27/// ```
28#[derive(Debug, Copy, Clone)]
29pub struct ExponentialRetryStrategy {
30    pub base: usize,
31    pub max_attempts: usize,
32    pub initial_delay: Duration,
33    /// See [RetryStrategy::retry_early_returned_errors](crate::retry_strategy::RetryStrategy::retry_early_returned_errors)
34    pub retry_early_returned_errors: bool,
35}
36
37impl Default for ExponentialRetryStrategy {
38    fn default() -> Self {
39        Self {
40            base: 2,
41            max_attempts: 3,
42            initial_delay: Duration::from_millis(500),
43            retry_early_returned_errors: true,
44        }
45    }
46}
47
48impl ExponentialRetryStrategy {
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    pub fn max_attempts(mut self, max_attempts: usize) -> Self {
54        self.max_attempts = max_attempts;
55        self
56    }
57
58    pub fn initial_delay(mut self, initial_delay: Duration) -> Self {
59        self.initial_delay = initial_delay;
60        self
61    }
62
63    /// See [RetryStrategy::retry_early_returned_errors](crate::retry_strategy::RetryStrategy::retry_early_returned_errors)
64    pub fn retry_early_returned_errors(mut self, retry_early_returned_errors: bool) -> Self {
65        self.retry_early_returned_errors = retry_early_returned_errors;
66        self
67    }
68}
69
70impl RetryStrategy for ExponentialRetryStrategy {
71    fn check_attempt(&mut self, attempts_before: usize) -> Result<Duration, TooManyAttempts> {
72        let exponent = self.base.pow(attempts_before as u32);
73        if self.max_attempts == attempts_before {
74            Err(TooManyAttempts)
75        } else {
76            Ok(self.initial_delay * exponent as u32)
77        }
78    }
79
80    fn retry_early_returned_errors(&self) -> bool {
81        self.retry_early_returned_errors
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn check_exponent() {
91        let mut strategy = ExponentialRetryStrategy {
92            base: 2,
93            initial_delay: Duration::from_secs(1),
94            max_attempts: 5,
95            ..Default::default()
96        };
97        assert_eq!(strategy.check_attempt(0).unwrap(), Duration::from_secs(1));
98        assert_eq!(strategy.check_attempt(1).unwrap(), Duration::from_secs(2));
99        assert_eq!(strategy.check_attempt(2).unwrap(), Duration::from_secs(4));
100        assert_eq!(strategy.check_attempt(3).unwrap(), Duration::from_secs(8));
101        assert_eq!(strategy.check_attempt(4).unwrap(), Duration::from_secs(16));
102
103        assert!(strategy.check_attempt(5).is_err());
104    }
105}