diff options
Diffstat (limited to 'src/backoff.rs')
| -rw-r--r-- | src/backoff.rs | 58 |
1 files changed, 58 insertions, 0 deletions
diff --git a/src/backoff.rs b/src/backoff.rs new file mode 100644 index 0000000..1f87b8d --- /dev/null +++ b/src/backoff.rs @@ -0,0 +1,58 @@ +//! Exponential backoff: a delay that doubles, up to a limit, while something keeps +//! failing, and starts over when it succeeds. Only the delay; each user keeps its own +//! point in time (next attempt, deadline). + +use std::time::Duration; + +#[derive(Clone, Copy, Debug)] +pub(crate) struct Backoff { + initial: Duration, + max: Duration, + current: Duration, +} + +impl Backoff { + pub(crate) const fn new(initial: Duration, max: Duration) -> Self { + Self { + initial, + max, + current: initial, + } + } + + /// The delay to use now. + pub(crate) fn current(&self) -> Duration { + self.current + } + + /// Doubles the delay, up to the limit. + pub(crate) fn grow(&mut self) { + self.current = self.current.saturating_mul(2).min(self.max); + } + + /// The delay to use now; the next call gives twice as much (up to the limit). + pub(crate) fn next(&mut self) -> Duration { + let delay = self.current; + self.grow(); + delay + } + + /// Back to the initial delay. + pub(crate) fn reset(&mut self) { + self.current = self.initial; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn doubles_up_to_the_limit_and_resets() { + let mut backoff = Backoff::new(Duration::from_secs(1), Duration::from_secs(5)); + let delays: Vec<_> = (0..5).map(|_| backoff.next().as_secs()).collect(); + assert_eq!(delays, [1, 2, 4, 5, 5]); + backoff.reset(); + assert_eq!(backoff.current(), Duration::from_secs(1)); + } +} |
