backoff/
exponential.rs

1use instant::Instant;
2use std::marker::PhantomData;
3use std::time::Duration;
4
5use crate::backoff::Backoff;
6use crate::clock::Clock;
7use crate::default;
8
9#[derive(Debug)]
10pub struct ExponentialBackoff<C> {
11    /// The current retry interval.
12    pub current_interval: Duration,
13    /// The initial retry interval.
14    pub initial_interval: Duration,
15    /// The randomization factor to use for creating a range around the retry interval.
16    ///
17    /// A randomization factor of 0.5 results in a random period ranging between 50% below and 50%
18    /// above the retry interval.
19    pub randomization_factor: f64,
20    /// The value to multiply the current interval with for each retry attempt.
21    pub multiplier: f64,
22    /// The maximum value of the back off period. Once the retry interval reaches this
23    /// value it stops increasing.
24    pub max_interval: Duration,
25    /// The system time. It is calculated when an [`ExponentialBackoff`](struct.ExponentialBackoff.html) instance is
26    /// created and is reset when [`retry`](../trait.Operation.html#method.retry) is called.
27    pub start_time: Instant,
28    /// The maximum elapsed time after instantiating [`ExponentialBackfff`](struct.ExponentialBackoff.html) or calling
29    /// [`reset`](trait.Backoff.html#method.reset) after which [`next_backoff`](../trait.Backoff.html#method.reset) returns `None`.
30    pub max_elapsed_time: Option<Duration>,
31    /// The clock used to get the current time.
32    pub clock: C,
33}
34
35impl<C> Default for ExponentialBackoff<C>
36where
37    C: Clock + Default,
38{
39    fn default() -> ExponentialBackoff<C> {
40        let mut eb = ExponentialBackoff {
41            current_interval: Duration::from_millis(default::INITIAL_INTERVAL_MILLIS),
42            initial_interval: Duration::from_millis(default::INITIAL_INTERVAL_MILLIS),
43            randomization_factor: default::RANDOMIZATION_FACTOR,
44            multiplier: default::MULTIPLIER,
45            max_interval: Duration::from_millis(default::MAX_INTERVAL_MILLIS),
46            max_elapsed_time: Some(Duration::from_millis(default::MAX_ELAPSED_TIME_MILLIS)),
47            clock: C::default(),
48            start_time: Instant::now(),
49        };
50        eb.reset();
51        eb
52    }
53}
54
55impl<C: Clock> ExponentialBackoff<C> {
56    /// Returns the elapsed time since start_time.
57    pub fn get_elapsed_time(&self) -> Duration {
58        self.clock.now().duration_since(self.start_time)
59    }
60
61    fn get_random_value_from_interval(
62        randomization_factor: f64,
63        random: f64,
64        current_interval: Duration,
65    ) -> Duration {
66        let current_interval_nanos = duration_to_nanos(current_interval);
67
68        let delta = randomization_factor * current_interval_nanos;
69        let min_interval = current_interval_nanos - delta;
70        let max_interval = current_interval_nanos + delta;
71        // Get a random value from the range [minInterval, maxInterval].
72        // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
73        // we want a 33% chance for selecting either 1, 2 or 3.
74        let diff = max_interval - min_interval;
75        let nanos = min_interval + (random * (diff + 1.0));
76        nanos_to_duration(nanos)
77    }
78
79    fn increment_current_interval(&mut self) -> Duration {
80        let current_interval_nanos = duration_to_nanos(self.current_interval);
81        let max_interval_nanos = duration_to_nanos(self.max_interval);
82        // Check for overflow, if overflow is detected set the current interval to the max interval.
83        if current_interval_nanos >= max_interval_nanos / self.multiplier {
84            self.max_interval
85        } else {
86            let nanos = current_interval_nanos * self.multiplier;
87            nanos_to_duration(nanos)
88        }
89    }
90}
91
92fn duration_to_nanos(d: Duration) -> f64 {
93    d.as_secs() as f64 * 1_000_000_000.0 + f64::from(d.subsec_nanos())
94}
95
96fn nanos_to_duration(nanos: f64) -> Duration {
97    let secs = nanos / 1_000_000_000.0;
98    let nanos = nanos as u64 % 1_000_000_000;
99    Duration::new(secs as u64, nanos as u32)
100}
101
102impl<C> Backoff for ExponentialBackoff<C>
103where
104    C: Clock,
105{
106    fn reset(&mut self) {
107        self.current_interval = self.initial_interval;
108        self.start_time = self.clock.now();
109    }
110
111    fn next_backoff(&mut self) -> Option<Duration> {
112        let elapsed_time = self.get_elapsed_time();
113
114        match self.max_elapsed_time {
115            Some(v) if elapsed_time > v => None,
116            _ => {
117                let random = rand::random::<f64>();
118                let randomized_interval = Self::get_random_value_from_interval(
119                    self.randomization_factor,
120                    random,
121                    self.current_interval,
122                );
123                self.current_interval = self.increment_current_interval();
124
125                if let Some(max_elapsed_time) = self.max_elapsed_time {
126                    if elapsed_time + randomized_interval <= max_elapsed_time {
127                        Some(randomized_interval)
128                    } else {
129                        None
130                    }
131                } else {
132                    Some(randomized_interval)
133                }
134            }
135        }
136    }
137}
138
139impl<C> Clone for ExponentialBackoff<C>
140where
141    C: Clone,
142{
143    fn clone(&self) -> Self {
144        let clock = self.clock.clone();
145        ExponentialBackoff { clock, ..*self }
146    }
147}
148
149/// Builder for [`ExponentialBackoff`](type.ExponentialBackoff.html).
150///
151/// TODO: Example
152#[derive(Debug)]
153pub struct ExponentialBackoffBuilder<C> {
154    initial_interval: Duration,
155    randomization_factor: f64,
156    multiplier: f64,
157    max_interval: Duration,
158    max_elapsed_time: Option<Duration>,
159    _clock: PhantomData<C>,
160}
161
162impl<C> Default for ExponentialBackoffBuilder<C> {
163    fn default() -> Self {
164        Self {
165            initial_interval: Duration::from_millis(default::INITIAL_INTERVAL_MILLIS),
166            randomization_factor: default::RANDOMIZATION_FACTOR,
167            multiplier: default::MULTIPLIER,
168            max_interval: Duration::from_millis(default::MAX_INTERVAL_MILLIS),
169            max_elapsed_time: Some(Duration::from_millis(default::MAX_ELAPSED_TIME_MILLIS)),
170            _clock: PhantomData,
171        }
172    }
173}
174
175impl<C> ExponentialBackoffBuilder<C>
176where
177    C: Clock + Default,
178{
179    pub fn new() -> Self {
180        Default::default()
181    }
182
183    /// The initial retry interval.
184    pub fn with_initial_interval(&mut self, initial_interval: Duration) -> &mut Self {
185        self.initial_interval = initial_interval;
186        self
187    }
188
189    /// The randomization factor to use for creating a range around the retry interval.
190    ///
191    /// A randomization factor of 0.5 results in a random period ranging between 50% below and 50%
192    /// above the retry interval.
193    pub fn with_randomization_factor(&mut self, randomization_factor: f64) -> &mut Self {
194        self.randomization_factor = randomization_factor;
195        self
196    }
197
198    /// The value to multiply the current interval with for each retry attempt.
199    pub fn with_multiplier(&mut self, multiplier: f64) -> &mut Self {
200        self.multiplier = multiplier;
201        self
202    }
203
204    /// The maximum value of the back off period. Once the retry interval reaches this
205    /// value it stops increasing.
206    pub fn with_max_interval(&mut self, max_interval: Duration) -> &mut Self {
207        self.max_interval = max_interval;
208        self
209    }
210
211    /// The maximum elapsed time after instantiating [`ExponentialBackfff`](struct.ExponentialBackoff.html) or calling
212    /// [`reset`](trait.Backoff.html#method.reset) after which [`next_backoff`](../trait.Backoff.html#method.reset) returns `None`.
213    pub fn with_max_elapsed_time(&mut self, max_elapsed_time: Option<Duration>) -> &mut Self {
214        self.max_elapsed_time = max_elapsed_time;
215        self
216    }
217
218    pub fn build(&self) -> ExponentialBackoff<C> {
219        ExponentialBackoff {
220            current_interval: self.initial_interval,
221            initial_interval: self.initial_interval,
222            randomization_factor: self.randomization_factor,
223            multiplier: self.multiplier,
224            max_interval: self.max_interval,
225            max_elapsed_time: self.max_elapsed_time,
226            clock: C::default(),
227            start_time: Instant::now(),
228        }
229    }
230}
231
232#[cfg(test)]
233use crate::clock::SystemClock;
234
235#[test]
236fn get_randomized_interval() {
237    // 33% chance of being 1.
238    let f = ExponentialBackoff::<SystemClock>::get_random_value_from_interval;
239    assert_eq!(Duration::new(0, 1), f(0.5, 0.0, Duration::new(0, 2)));
240    assert_eq!(Duration::new(0, 1), f(0.5, 0.33, Duration::new(0, 2)));
241    // 33% chance of being 2.
242    assert_eq!(Duration::new(0, 2), f(0.5, 0.34, Duration::new(0, 2)));
243    assert_eq!(Duration::new(0, 2), f(0.5, 0.66, Duration::new(0, 2)));
244    // 33% chance of being 3.
245    assert_eq!(Duration::new(0, 3), f(0.5, 0.67, Duration::new(0, 2)));
246    assert_eq!(Duration::new(0, 3), f(0.5, 0.99, Duration::new(0, 2)));
247}
248
249#[test]
250fn exponential_backoff_builder() {
251    let initial_interval = Duration::from_secs(1);
252    let max_interval = Duration::from_secs(2);
253    let multiplier = 3.0;
254    let randomization_factor = 4.0;
255    let backoff: ExponentialBackoff<SystemClock> = ExponentialBackoffBuilder::new()
256        .with_initial_interval(initial_interval)
257        .with_multiplier(multiplier)
258        .with_randomization_factor(randomization_factor)
259        .with_max_interval(max_interval)
260        .with_max_elapsed_time(None)
261        .build();
262    assert_eq!(backoff.initial_interval, initial_interval);
263    assert_eq!(backoff.current_interval, initial_interval);
264    assert_eq!(backoff.multiplier, multiplier);
265    assert_eq!(backoff.randomization_factor, randomization_factor);
266    assert_eq!(backoff.max_interval, max_interval);
267    assert_eq!(backoff.max_elapsed_time, None);
268}
269
270#[test]
271fn exponential_backoff_default_builder() {
272    let backoff: ExponentialBackoff<SystemClock> = ExponentialBackoffBuilder::new().build();
273    assert_eq!(
274        backoff.initial_interval,
275        Duration::from_millis(default::INITIAL_INTERVAL_MILLIS)
276    );
277    assert_eq!(
278        backoff.current_interval,
279        Duration::from_millis(default::INITIAL_INTERVAL_MILLIS)
280    );
281    assert_eq!(backoff.multiplier, default::MULTIPLIER);
282    assert_eq!(backoff.randomization_factor, default::RANDOMIZATION_FACTOR);
283    assert_eq!(
284        backoff.max_interval,
285        Duration::from_millis(default::MAX_INTERVAL_MILLIS)
286    );
287    assert_eq!(
288        backoff.max_elapsed_time,
289        Some(Duration::from_millis(default::MAX_ELAPSED_TIME_MILLIS))
290    );
291}