use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShutdownResult {
Clean,
Timeout {
remaining: usize,
},
}
pub struct GracefulShutdown {
shutdown_requested: AtomicBool,
active_count: AtomicUsize,
timeout: Duration,
}
impl GracefulShutdown {
pub fn new(timeout: Duration) -> Self {
Self {
shutdown_requested: AtomicBool::new(false),
active_count: AtomicUsize::new(0),
timeout,
}
}
#[must_use]
pub fn is_shutdown_requested(&self) -> bool {
self.shutdown_requested.load(Ordering::Acquire)
}
#[must_use]
pub fn active_count(&self) -> usize {
self.active_count.load(Ordering::Acquire)
}
pub fn register(&self) -> Option<ShutdownGuard<'_>> {
if self.is_shutdown_requested() {
return None; }
self.active_count.fetch_add(1, Ordering::AcqRel);
Some(ShutdownGuard { shutdown: self })
}
pub fn shutdown(&self) -> ShutdownResult {
self.shutdown_requested.store(true, Ordering::Release);
let deadline = Instant::now() + self.timeout;
loop {
let active = self.active_count.load(Ordering::Acquire);
if active == 0 {
return ShutdownResult::Clean;
}
if Instant::now() >= deadline {
return ShutdownResult::Timeout { remaining: active };
}
std::thread::sleep(Duration::from_millis(10));
}
}
pub fn reset(&self) {
self.shutdown_requested.store(false, Ordering::Release);
}
}
impl Default for GracefulShutdown {
fn default() -> Self {
Self::new(Duration::from_secs(30))
}
}
pub struct ShutdownGuard<'a> {
shutdown: &'a GracefulShutdown,
}
impl Drop for ShutdownGuard<'_> {
fn drop(&mut self) {
self.shutdown.active_count.fetch_sub(1, Ordering::AcqRel);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shutdown_result_eq() {
assert_eq!(ShutdownResult::Clean, ShutdownResult::Clean);
assert_eq!(
ShutdownResult::Timeout { remaining: 5 },
ShutdownResult::Timeout { remaining: 5 }
);
assert_ne!(ShutdownResult::Clean, ShutdownResult::Timeout { remaining: 0 });
}
#[test]
fn test_graceful_shutdown_new() {
let shutdown = GracefulShutdown::new(Duration::from_secs(5));
assert!(!shutdown.is_shutdown_requested());
assert_eq!(shutdown.active_count(), 0);
}
#[test]
fn test_graceful_shutdown_default() {
let shutdown = GracefulShutdown::default();
assert!(!shutdown.is_shutdown_requested());
assert_eq!(shutdown.timeout, Duration::from_secs(30));
}
#[test]
fn test_graceful_shutdown_register() {
let shutdown = GracefulShutdown::new(Duration::from_secs(5));
let guard1 = shutdown.register();
assert!(guard1.is_some());
assert_eq!(shutdown.active_count(), 1);
let guard2 = shutdown.register();
assert!(guard2.is_some());
assert_eq!(shutdown.active_count(), 2);
drop(guard1);
assert_eq!(shutdown.active_count(), 1);
drop(guard2);
assert_eq!(shutdown.active_count(), 0);
}
#[test]
fn test_graceful_shutdown_clean() {
let shutdown = GracefulShutdown::new(Duration::from_millis(100));
let guard = shutdown.register();
drop(guard);
let result = shutdown.shutdown();
assert_eq!(result, ShutdownResult::Clean);
}
#[test]
fn test_graceful_shutdown_rejects_after_shutdown() {
let shutdown = GracefulShutdown::new(Duration::from_millis(10));
let result = shutdown.shutdown();
assert_eq!(result, ShutdownResult::Clean);
let guard = shutdown.register();
assert!(guard.is_none(), "Should reject new operations after shutdown");
}
#[test]
fn test_graceful_shutdown_reset() {
let shutdown = GracefulShutdown::new(Duration::from_millis(10));
shutdown.shutdown();
assert!(shutdown.is_shutdown_requested());
shutdown.reset();
assert!(!shutdown.is_shutdown_requested());
let guard = shutdown.register();
assert!(guard.is_some());
}
#[test]
fn test_falsify_shutdown_timeout() {
let shutdown = GracefulShutdown::new(Duration::from_millis(50));
let _guard = shutdown.register();
assert_eq!(shutdown.active_count(), 1);
let start = Instant::now();
let result = shutdown.shutdown();
let elapsed = start.elapsed();
match result {
ShutdownResult::Timeout { remaining } => {
assert_eq!(remaining, 1, "Should report exactly 1 remaining operation");
}
ShutdownResult::Clean => {
panic!("FALSIFICATION FAILED: Shutdown reported Clean with active operation");
}
}
assert!(
elapsed >= Duration::from_millis(40),
"FALSIFICATION FAILED: Shutdown returned too early ({:?} < 40ms)",
elapsed
);
}
#[test]
fn test_falsify_guard_drop_semantics() {
let shutdown = GracefulShutdown::new(Duration::from_secs(1));
let mut guards: Vec<_> = (0..100).filter_map(|_| shutdown.register()).collect();
assert_eq!(shutdown.active_count(), 100, "FALSIFICATION FAILED: Not all guards registered");
guards.truncate(50);
assert_eq!(
shutdown.active_count(),
50,
"FALSIFICATION FAILED: Guard drop did not correctly decrement count"
);
drop(guards);
assert_eq!(
shutdown.active_count(),
0,
"FALSIFICATION FAILED: Final drop did not clear all guards"
);
}
#[test]
fn test_falsify_rejection_after_shutdown_flag() {
let shutdown = GracefulShutdown::new(Duration::from_secs(5));
shutdown.shutdown_requested.store(true, Ordering::Release);
let mut accepted = 0;
for _ in 0..100 {
if shutdown.register().is_some() {
accepted += 1;
}
}
assert_eq!(
accepted, 0,
"FALSIFICATION FAILED: {} operations accepted after shutdown flag set",
accepted
);
}
}