use std::time::Duration; #[derive(Debug, Clone)] pub struct Backoff { initial: Duration, max: Duration, current: Option, } impl Backoff { pub fn new(initial: Duration, max: Duration) -> Self { Self { initial, max, current: None, } } #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Duration { let next = match self.current { None => self.initial, Some(d) => (d * 2).min(self.max), }; self.current = Some(next); next } pub fn reset(&mut self) { self.current = None; } } #[cfg(test)] mod tests { use super::*; #[test] fn starts_at_initial() { let mut b = Backoff::new(Duration::from_secs(1), Duration::from_secs(30)); assert_eq!(b.next(), Duration::from_secs(1)); } #[test] fn doubles_each_call() { let mut b = Backoff::new(Duration::from_secs(1), Duration::from_secs(30)); assert_eq!(b.next(), Duration::from_secs(1)); assert_eq!(b.next(), Duration::from_secs(2)); assert_eq!(b.next(), Duration::from_secs(4)); assert_eq!(b.next(), Duration::from_secs(8)); } #[test] fn caps_at_max() { let mut b = Backoff::new(Duration::from_secs(1), Duration::from_secs(5)); for _ in 0..10 { b.next(); } assert_eq!(b.next(), Duration::from_secs(5)); } #[test] fn reset_starts_over() { let mut b = Backoff::new(Duration::from_secs(1), Duration::from_secs(30)); b.next(); b.next(); b.next(); b.reset(); assert_eq!(b.next(), Duration::from_secs(1)); } }