use std::time::Duration;
pub const INITIAL: Duration = Duration::from_secs(1);
pub const MAX: Duration = Duration::from_secs(10);
pub struct Backoff {
next: Duration,
}
impl Backoff {
pub fn unreached() -> Self {
Self {
next: Duration::ZERO,
}
}
pub fn connected() -> Self {
Self { next: INITIAL }
}
pub fn delay(&self) -> Duration {
self.next
}
pub fn failed(&mut self) {
self.next = (self.next * 2).max(INITIAL).min(MAX);
}
pub fn reset(&mut self) {
self.next = INITIAL;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_dial_is_immediate() {
assert_eq!(Backoff::unreached().delay(), Duration::ZERO);
}
#[test]
fn a_dropped_connection_waits_a_step() {
assert_eq!(Backoff::connected().delay(), INITIAL);
}
#[test]
fn an_unreached_peer_backs_off() {
let mut backoff = Backoff::unreached();
backoff.failed();
assert_eq!(backoff.delay(), INITIAL);
let mut seen = backoff.delay();
for _ in 0..10 {
backoff.failed();
assert!(
backoff.delay() > seen || backoff.delay() == MAX,
"delay stalled at {:?}",
backoff.delay()
);
seen = backoff.delay();
}
assert_eq!(seen, MAX);
}
#[test]
fn the_delay_climbs_to_the_ceiling_and_stays() {
let mut backoff = Backoff::connected();
for expected in [2, 4, 8, 10, 10, 10] {
backoff.failed();
assert_eq!(backoff.delay(), Duration::from_secs(expected));
}
}
#[test]
fn connecting_again_clears_the_climb() {
let mut backoff = Backoff::connected();
for _ in 0..8 {
backoff.failed();
}
assert_eq!(backoff.delay(), MAX);
backoff.reset();
assert_eq!(backoff.delay(), INITIAL);
}
}