use std::time::Duration;
pub struct ReconnectBackoff {
current: u64,
max: u64,
}
impl ReconnectBackoff {
pub fn new(max_seconds: u64) -> Self {
Self {
current: 1,
max: max_seconds.max(1),
}
}
pub fn reset(&mut self) {
self.current = 1;
}
pub fn next_delay(&mut self) -> Duration {
let delay = self.current;
self.current = self.current.saturating_mul(2).min(self.max);
Duration::from_secs(delay)
}
}
#[cfg(test)]
mod tests {
use super::ReconnectBackoff;
use std::time::Duration;
#[test]
fn exponential_backoff_caps_at_max() {
let mut backoff = ReconnectBackoff::new(5);
let delays = [
backoff.next_delay(),
backoff.next_delay(),
backoff.next_delay(),
backoff.next_delay(),
backoff.next_delay(),
];
assert_eq!(
delays,
[
Duration::from_secs(1),
Duration::from_secs(2),
Duration::from_secs(4),
Duration::from_secs(5),
Duration::from_secs(5),
]
);
}
#[test]
fn reset_starts_again_at_one_second() {
let mut backoff = ReconnectBackoff::new(8);
assert_eq!(backoff.next_delay(), Duration::from_secs(1));
assert_eq!(backoff.next_delay(), Duration::from_secs(2));
backoff.reset();
assert_eq!(backoff.next_delay(), Duration::from_secs(1));
}
#[test]
fn max_seconds_below_one_defaults_to_one() {
let mut backoff = ReconnectBackoff::new(0);
assert_eq!(backoff.next_delay(), Duration::from_secs(1));
assert_eq!(backoff.next_delay(), Duration::from_secs(1));
}
#[test]
fn large_max_saturates_without_overflow() {
let mut backoff = ReconnectBackoff::new(u64::MAX);
backoff.current = (u64::MAX / 2) + 1;
assert_eq!(
backoff.next_delay(),
Duration::from_secs((u64::MAX / 2) + 1)
);
assert_eq!(backoff.next_delay(), Duration::from_secs(u64::MAX));
assert_eq!(backoff.next_delay(), Duration::from_secs(u64::MAX));
}
}