From 54045da2df447afbeb3c711f7ccc53510061a348 Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Mon, 25 May 2026 11:34:53 +0200 Subject: [PATCH] feat(supervisor): exponential backoff calculator --- crates/xy-supervisor/src/backoff.rs | 70 +++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 crates/xy-supervisor/src/backoff.rs diff --git a/crates/xy-supervisor/src/backoff.rs b/crates/xy-supervisor/src/backoff.rs new file mode 100644 index 0000000..e4909a5 --- /dev/null +++ b/crates/xy-supervisor/src/backoff.rs @@ -0,0 +1,70 @@ +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, + } + } + + 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)); + } +}