azure_core/policies/retry_policies/
exponential_retry.rs

1use super::RetryPolicy;
2use std::time::Duration;
3
4/// Retry policy with exponential back-off.
5///
6/// Retry policy with exponential back-off (with an added random delay up to 256 ms). Each retry
7/// will happen at least after an exponential wait time. So if x is the first retry wait, the
8/// second will be x*2, the third x*4 and so on. The policy will retry until the maximum number of
9/// retries have been reached or the maximum allowed delay has passed (whichever comes first). The
10/// wait time is not precise.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct ExponentialRetryPolicy {
13    initial_delay: Duration,
14    max_retries: u32,
15    max_elapsed: Duration,
16    max_delay: Duration,
17}
18
19impl ExponentialRetryPolicy {
20    pub(crate) fn new(
21        initial_delay: Duration,
22        max_retries: u32,
23        max_elapsed: Duration,
24        max_delay: Duration,
25    ) -> Self {
26        Self {
27            initial_delay: initial_delay.max(Duration::from_millis(1)),
28            max_retries,
29            max_elapsed,
30            max_delay: max_delay.max(Duration::from_secs(1)),
31        }
32    }
33}
34
35impl RetryPolicy for ExponentialRetryPolicy {
36    fn is_expired(&self, time_since_start: Duration, retry_count: u32) -> bool {
37        retry_count >= self.max_retries || time_since_start >= self.max_elapsed
38    }
39
40    fn sleep_duration(&self, retry_count: u32) -> Duration {
41        let sleep_ms = self.initial_delay.as_millis() as u64 * 2u64.pow(retry_count)
42            + u64::from(rand::random::<u8>());
43        let sleep_ms = sleep_ms.min(self.max_delay.as_millis().try_into().unwrap_or(u64::MAX));
44        Duration::from_millis(sleep_ms)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn exponentialy_increases_correctly() {
54        let options = crate::options::ExponentialRetryOptions::default();
55        let policy = ExponentialRetryPolicy::new(
56            options.initial_delay,
57            options.max_retries,
58            options.max_total_elapsed,
59            options.max_delay,
60        );
61
62        let mut elapsed_time = Duration::from_secs(0);
63        let mut retry_count = 0;
64        let mut durations = vec![];
65        while !policy.is_expired(elapsed_time, retry_count) {
66            retry_count += 1; // increase at beginning since we only check expiration if we need to retry
67            let duration = policy.sleep_duration(retry_count);
68            durations.push(duration);
69            elapsed_time += duration; // simulate sleep
70        }
71
72        let actual = durations
73            .into_iter()
74            .map(|d| d.as_secs())
75            .collect::<Vec<_>>();
76        let expected = &[0, 0, 1, 3, 6, 12, 25, 30];
77        assert_eq!(
78            actual.len(),
79            expected.len(),
80            "Different number of durations than expected"
81        );
82
83        for (&a, &e) in actual.iter().zip(expected.iter()) {
84            // Check within one second to account for the jitter
85            assert!(
86                a == e || a + 1 == e || a == e + 1,
87                "actual != expected\nActual: {actual:?}\nExpected: {expected:?}"
88            );
89        }
90    }
91}