Skip to main content

wowlab_common/
retry.rs

1//! Deterministic retry-delay schedules without transport policy or sleeping.
2
3use std::time::Duration;
4
5/// Bounded exponential delay schedule.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub struct ExponentialSchedule {
8    initial: Duration,
9    maximum: Duration,
10    multiplier: u32,
11    current: Duration,
12}
13
14impl ExponentialSchedule {
15    /// Create a schedule whose first delay is `initial` and whose delays never exceed `maximum`.
16    #[must_use]
17    pub fn new(initial: Duration, maximum: Duration, multiplier: u32) -> Self {
18        Self {
19            initial,
20            maximum,
21            multiplier: multiplier.max(1),
22            current: initial.min(maximum),
23        }
24    }
25
26    /// Return the current delay and advance the schedule for the next attempt.
27    pub fn next_delay(&mut self) -> Duration {
28        let delay = self.current;
29
30        self.current = self
31            .current
32            .checked_mul(self.multiplier)
33            .unwrap_or(self.maximum)
34            .min(self.maximum);
35
36        delay
37    }
38
39    /// Reset the next delay to the initial bounded value.
40    pub fn reset(&mut self) {
41        self.current = self.initial.min(self.maximum);
42    }
43
44    /// Inspect the delay that will be returned by [`Self::next_delay`].
45    #[must_use]
46    pub const fn current_delay(&self) -> Duration {
47        self.current
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use googletest::prelude::*;
54
55    use super::*;
56
57    #[gtest]
58    fn grows_until_maximum_and_resets() -> Result<()> {
59        let mut schedule =
60            ExponentialSchedule::new(Duration::from_millis(100), Duration::from_millis(500), 2);
61
62        verify_that!(schedule.next_delay(), eq(Duration::from_millis(100)))?;
63        verify_that!(schedule.next_delay(), eq(Duration::from_millis(200)))?;
64        verify_that!(schedule.next_delay(), eq(Duration::from_millis(400)))?;
65        verify_that!(schedule.next_delay(), eq(Duration::from_millis(500)))?;
66        verify_that!(schedule.next_delay(), eq(Duration::from_millis(500)))?;
67
68        schedule.reset();
69
70        verify_that!(schedule.current_delay(), eq(Duration::from_millis(100)))
71    }
72
73    #[gtest]
74    fn normalizes_degenerate_bounds_and_multiplier() -> Result<()> {
75        let mut schedule =
76            ExponentialSchedule::new(Duration::from_secs(2), Duration::from_secs(1), 0);
77
78        verify_that!(schedule.next_delay(), eq(Duration::from_secs(1)))?;
79
80        verify_that!(schedule.next_delay(), eq(Duration::from_secs(1)))
81    }
82}