use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::{self, JoinHandle};
use rsfulmen::signals::testing::SignalInjector;
pub use rsfulmen::signals::{DoubleTapConfig, SignalManager, SignalManagerError};
use crate::SysprimsResult;
#[derive(Clone)]
pub struct StopFlagHandle(Arc<AtomicBool>);
impl StopFlagHandle {
pub fn set(&self) {
self.0.store(true, Ordering::SeqCst);
}
}
pub struct GuardSignals {
stop_flag: Arc<AtomicBool>,
manager: SignalManager,
injector: SignalInjector,
listener_thread: Option<JoinHandle<()>>,
}
impl GuardSignals {
pub fn start() -> SysprimsResult<Self> {
let manager = SignalManager::new();
let stop_flag = Arc::new(AtomicBool::new(false));
let injector = SignalInjector::new(&manager);
let flag = Arc::clone(&stop_flag);
let mgr = manager.clone();
let _term_reg = manager
.handle(crate::signals::SIGTERM, move || {
flag.store(true, Ordering::SeqCst);
mgr.stop();
Ok(())
})
.map_err(|e| crate::SysprimsError::internal(format!("SIGTERM handler: {e}")))?;
let flag = Arc::clone(&stop_flag);
let mgr = manager.clone();
let _int_reg = manager
.handle(crate::signals::SIGINT, move || {
flag.store(true, Ordering::SeqCst);
mgr.stop();
Ok(())
})
.map_err(|e| crate::SysprimsError::internal(format!("SIGINT handler: {e}")))?;
manager.enable_double_tap(DoubleTapConfig::from_catalog());
let listener_mgr = manager.clone();
let listener_flag = Arc::clone(&stop_flag);
let handle = thread::Builder::new()
.name("guard-signals".into())
.spawn(move || {
let _term = _term_reg;
let _int = _int_reg;
if let Err(_e) = listener_mgr.listen() {
listener_flag.store(true, Ordering::SeqCst);
}
})
.map_err(|e| crate::SysprimsError::internal(format!("signal thread: {e}")))?;
Ok(Self {
stop_flag,
manager,
injector,
listener_thread: Some(handle),
})
}
#[inline]
pub fn should_stop(&self) -> bool {
self.stop_flag.load(Ordering::SeqCst)
}
pub fn request_stop(&self) {
self.stop_flag.store(true, Ordering::SeqCst);
self.manager.stop();
}
pub fn manager(&self) -> &SignalManager {
&self.manager
}
pub fn stop_flag_handle(&self) -> StopFlagHandle {
StopFlagHandle(Arc::clone(&self.stop_flag))
}
}
impl Drop for GuardSignals {
fn drop(&mut self) {
let _ = self.injector.inject(crate::signals::SIGTERM);
if let Some(handle) = self.listener_thread.take() {
let _ = handle.join();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
fn test_injector(gs: &GuardSignals) -> SignalInjector {
SignalInjector::new(gs.manager())
}
#[test]
fn test_guard_signals_starts_and_drops_cleanly() {
let gs = GuardSignals::start();
assert!(gs.is_ok(), "GuardSignals::start() should succeed");
let gs = gs.unwrap();
assert!(!gs.should_stop(), "should not be stopped initially");
drop(gs);
}
#[test]
fn test_guard_signals_request_stop_and_drop() {
let gs = GuardSignals::start().unwrap();
assert!(!gs.should_stop());
gs.request_stop();
assert!(gs.should_stop(), "should be stopped after request_stop()");
drop(gs);
}
#[test]
fn test_guard_signals_injected_sigterm() {
let gs = GuardSignals::start().unwrap();
let inj = test_injector(&gs);
inj.inject(crate::signals::SIGTERM)
.expect("inject should succeed");
#[cfg(windows)]
let deadline = std::time::Instant::now() + Duration::from_secs(10);
#[cfg(not(windows))]
let deadline = std::time::Instant::now() + Duration::from_secs(2);
while !gs.should_stop() && std::time::Instant::now() < deadline {
thread::sleep(Duration::from_millis(25));
}
assert!(gs.should_stop(), "should be stopped after injected SIGTERM");
drop(gs);
}
}