try_again/delay/
exponential.rs

1use crate::StdDuration;
2use crate::tracked_iterator::{FiniteIterator, IntoTrackedIterator};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct ExponentialBackoff {
6    pub initial_delay: StdDuration,
7}
8
9impl ExponentialBackoff {
10    pub fn of_initial_delay(initial_delay: impl Into<StdDuration>) -> Self {
11        Self {
12            initial_delay: initial_delay.into(),
13        }
14    }
15
16    pub fn uncapped(self) -> ExponentialBackoffWithCap {
17        ExponentialBackoffWithCap {
18            initial_delay: self.initial_delay,
19            last_delay: StdDuration::ZERO,
20            max_delay: None,
21            first: true,
22        }
23    }
24
25    pub fn capped_at(self, max_delay: impl Into<StdDuration>) -> ExponentialBackoffWithCap {
26        ExponentialBackoffWithCap {
27            initial_delay: self.initial_delay,
28            last_delay: StdDuration::ZERO,
29            max_delay: Some(max_delay.into()),
30            first: true,
31        }
32    }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct ExponentialBackoffWithCap {
37    pub initial_delay: StdDuration,
38    pub last_delay: StdDuration,
39    pub max_delay: Option<StdDuration>,
40    pub first: bool,
41}
42
43impl ExponentialBackoffWithCap {
44    pub fn take(self, count: usize) -> FiniteIterator<std::iter::Take<ExponentialBackoffWithCap>> {
45        self.into_tracked().take(count)
46    }
47}
48
49impl Iterator for ExponentialBackoffWithCap {
50    type Item = StdDuration;
51
52    fn next(&mut self) -> Option<Self::Item> {
53        if self.first {
54            self.first = false;
55            self.last_delay = self.initial_delay;
56            return Some(self.initial_delay);
57        }
58
59        let mut next = self.last_delay * 2;
60        if let Some(max_delay) = self.max_delay {
61            if next > max_delay {
62                next = max_delay;
63            }
64        }
65        self.last_delay = next;
66        Some(next)
67    }
68}
69
70#[cfg(test)]
71mod test {
72    use super::*;
73    use crate::IntoStdDuration;
74    use assertr::prelude::*;
75
76    #[test]
77    fn uncapped_exponential_backoff_delay_strategy_returns_initial_delay_for_the_first_try_and_doubles_the_delay_for_each_retry_until_reaching_max_tries()
78     {
79        let mut delay = ExponentialBackoff::of_initial_delay(50.millis())
80            .uncapped()
81            .take(4);
82
83        assert_that(delay.next()).is_some().is_equal_to(50.millis());
84        assert_that(delay.next())
85            .is_some()
86            .is_equal_to(100.millis());
87        assert_that(delay.next())
88            .is_some()
89            .is_equal_to(200.millis());
90        assert_that(delay.next())
91            .is_some()
92            .is_equal_to(400.millis());
93        assert_that(delay.next()).is_none();
94    }
95
96    #[test]
97    fn capped_exponential_backoff_delay_strategy_returns_initial_delay_for_the_first_try_and_doubles_the_delay_for_each_retry_until_capping_at_specified_max_delay_before_reaching_max_tries()
98     {
99        let mut delay = ExponentialBackoff::of_initial_delay(50.millis())
100            .capped_at(250.millis())
101            .take(5);
102
103        assert_that(delay.next()).is_some().is_equal_to(50.millis());
104        assert_that(delay.next())
105            .is_some()
106            .is_equal_to(100.millis());
107        assert_that(delay.next())
108            .is_some()
109            .is_equal_to(200.millis());
110        assert_that(delay.next())
111            .is_some()
112            .is_equal_to(250.millis());
113        assert_that(delay.next())
114            .is_some()
115            .is_equal_to(250.millis());
116        assert_that(delay.next()).is_none();
117    }
118}