feat(supervisor): sliding retry-window tracker

This commit is contained in:
2026-05-25 11:34:55 +02:00
parent 54045da2df
commit d237e980e9
+80
View File
@@ -0,0 +1,80 @@
use std::collections::VecDeque;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct RetryWindow {
window: Duration,
cap: u32,
events: VecDeque<Instant>,
}
impl RetryWindow {
pub fn new(window: Duration, cap: u32) -> Self {
Self {
window,
cap,
events: VecDeque::new(),
}
}
pub fn record(&mut self, now: Instant) {
self.events.push_back(now);
self.prune(now);
}
pub fn cap_reached(&mut self, now: Instant) -> bool {
self.prune(now);
self.events.len() as u32 >= self.cap
}
pub fn count(&mut self, now: Instant) -> u32 {
self.prune(now);
self.events.len() as u32
}
fn prune(&mut self, now: Instant) {
while let Some(&front) = self.events.front() {
if now.duration_since(front) > self.window {
self.events.pop_front();
} else {
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn below_cap_not_reached() {
let mut w = RetryWindow::new(Duration::from_secs(60), 3);
let t = Instant::now();
w.record(t);
w.record(t);
assert!(!w.cap_reached(t));
}
#[test]
fn at_cap_reached() {
let mut w = RetryWindow::new(Duration::from_secs(60), 3);
let t = Instant::now();
w.record(t);
w.record(t);
w.record(t);
assert!(w.cap_reached(t));
}
#[test]
fn old_events_pruned() {
let mut w = RetryWindow::new(Duration::from_secs(60), 3);
let t0 = Instant::now();
w.record(t0);
w.record(t0);
w.record(t0);
let t1 = t0 + Duration::from_secs(61);
assert_eq!(w.count(t1), 0);
assert!(!w.cap_reached(t1));
}
}