use std::sync::Mutex;
use std::time::{Duration, Instant};
pub struct ResetTracker {
max: usize,
window: Duration,
state: Mutex<WindowState>,
}
struct WindowState {
count: usize,
started: Instant,
}
impl ResetTracker {
pub fn new(max: usize, window: Duration) -> Self {
Self {
max,
window,
state: Mutex::new(WindowState {
count: 0,
started: Instant::now(),
}),
}
}
pub fn record_reset(&self) -> bool {
let mut st = self
.state
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let now = Instant::now();
if now.duration_since(st.started) > self.window {
st.count = 0;
st.started = now;
}
st.count += 1;
st.count > self.max
}
pub fn current(&self) -> usize {
self.state
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.count
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn under_limit_not_terminated() {
let t = ResetTracker::new(10, Duration::from_secs(3));
for _ in 0..5 {
assert!(!t.record_reset());
}
}
#[test]
fn burst_over_limit_terminated() {
let t = ResetTracker::new(10, Duration::from_secs(3));
let mut killed = false;
for _ in 0..15 {
killed = t.record_reset();
}
assert!(killed);
}
#[test]
fn window_expiry_resets_count() {
let t = ResetTracker::new(2, Duration::from_millis(10));
assert!(!t.record_reset());
assert!(!t.record_reset());
std::thread::sleep(Duration::from_millis(20));
assert!(!t.record_reset());
}
}