use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
pub const EXIT_CODE_INTERRUPTED: i32 = 130;
#[derive(Debug, Clone)]
pub struct ShutdownHandler {
flag: Arc<AtomicBool>,
}
impl ShutdownHandler {
#[must_use]
pub fn new() -> Self {
Self {
flag: Arc::new(AtomicBool::new(false)),
}
}
#[must_use]
pub fn is_shutdown_requested(&self) -> bool {
self.flag.load(Ordering::SeqCst)
}
pub fn request_shutdown(&self) {
self.flag.store(true, Ordering::SeqCst);
}
#[must_use]
pub fn get_flag(&self) -> Arc<AtomicBool> {
Arc::clone(&self.flag)
}
pub fn reset(&self) {
self.flag.store(false, Ordering::SeqCst);
}
}
impl Default for ShutdownHandler {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub enum SignalError {
InstallFailed(ctrlc::Error),
}
impl std::fmt::Display for SignalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SignalError::InstallFailed(e) => write!(f, "Failed to install signal handler: {}", e),
}
}
}
impl std::error::Error for SignalError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
SignalError::InstallFailed(e) => Some(e),
}
}
}
impl From<ctrlc::Error> for SignalError {
fn from(err: ctrlc::Error) -> Self {
SignalError::InstallFailed(err)
}
}
pub fn install_handler() -> Result<ShutdownHandler, SignalError> {
let handler = ShutdownHandler::new();
let flag = handler.get_flag();
ctrlc::set_handler(move || {
flag.store(true, Ordering::SeqCst);
let _ = writeln!(std::io::stderr(), "\nInterrupted. Cleaning up...");
let _ = std::io::stderr().flush();
log::info!("Shutdown signal received");
})?;
log::debug!("Ctrl+C signal handler installed");
Ok(handler)
}
#[must_use]
pub fn create_handler() -> ShutdownHandler {
ShutdownHandler::new()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shutdown_handler_new() {
let handler = ShutdownHandler::new();
assert!(!handler.is_shutdown_requested());
}
#[test]
fn test_shutdown_handler_default() {
let handler = ShutdownHandler::default();
assert!(!handler.is_shutdown_requested());
}
#[test]
fn test_request_shutdown() {
let handler = ShutdownHandler::new();
assert!(!handler.is_shutdown_requested());
handler.request_shutdown();
assert!(handler.is_shutdown_requested());
}
#[test]
fn test_reset() {
let handler = ShutdownHandler::new();
handler.request_shutdown();
assert!(handler.is_shutdown_requested());
handler.reset();
assert!(!handler.is_shutdown_requested());
}
#[test]
fn test_get_flag_shares_state() {
let handler = ShutdownHandler::new();
let flag = handler.get_flag();
assert!(!flag.load(Ordering::SeqCst));
handler.request_shutdown();
assert!(flag.load(Ordering::SeqCst));
}
#[test]
fn test_flag_modification_reflects_in_handler() {
let handler = ShutdownHandler::new();
let flag = handler.get_flag();
flag.store(true, Ordering::SeqCst);
assert!(handler.is_shutdown_requested());
}
#[test]
fn test_clone_shares_flag() {
let handler = ShutdownHandler::new();
let cloned = handler.clone();
handler.request_shutdown();
assert!(cloned.is_shutdown_requested());
}
#[test]
fn test_create_handler() {
let handler = create_handler();
assert!(!handler.is_shutdown_requested());
}
#[test]
fn test_exit_code_interrupted() {
assert_eq!(EXIT_CODE_INTERRUPTED, 130);
}
#[test]
fn test_signal_error_display() {
fn assert_display<T: std::fmt::Display>() {}
assert_display::<SignalError>();
}
#[test]
fn test_signal_error_debug() {
fn assert_debug<T: std::fmt::Debug>() {}
assert_debug::<SignalError>();
}
#[test]
fn test_shutdown_handler_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<ShutdownHandler>();
}
}