use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;
use tokio_util::sync::CancellationToken;
pub struct HealthMonitor {
#[allow(dead_code)]
interval: Duration,
max_failures: u32,
failures: AtomicU32,
shutdown: CancellationToken,
}
impl HealthMonitor {
#[must_use]
pub fn new(interval: Duration, max_failures: u32) -> Self {
Self {
interval,
max_failures,
failures: AtomicU32::new(0),
shutdown: CancellationToken::new(),
}
}
pub fn shutdown_token(&self) -> CancellationToken {
self.shutdown.clone()
}
pub fn reset(&self) {
self.failures.store(0, Ordering::SeqCst);
}
pub fn is_healthy(&self) -> bool {
self.failures.load(Ordering::SeqCst) < self.max_failures
}
pub fn record_success(&self) {
self.failures.store(0, Ordering::SeqCst);
}
pub fn record_failure(&self) -> bool {
let failures = self.failures.fetch_add(1, Ordering::SeqCst) + 1;
failures >= self.max_failures
}
pub fn stop(&self) {
self.shutdown.cancel();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_health_monitor() {
let monitor = HealthMonitor::new(Duration::from_secs(5), 3);
assert!(monitor.is_healthy());
assert!(!monitor.record_failure());
assert!(monitor.is_healthy());
assert!(!monitor.record_failure());
assert!(monitor.is_healthy());
assert!(monitor.record_failure());
assert!(!monitor.is_healthy());
monitor.reset();
assert!(monitor.is_healthy());
}
#[test]
fn test_health_monitor_success_resets() {
let monitor = HealthMonitor::new(Duration::from_secs(5), 3);
monitor.record_failure();
monitor.record_failure();
monitor.record_success();
assert!(monitor.is_healthy());
assert!(!monitor.record_failure());
assert!(!monitor.record_failure());
assert!(monitor.record_failure());
}
}