use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, OnceLock};
use signal_hook::consts::{SIGHUP, SIGTERM};
use signal_hook::iterator::Signals;
use super::TerminationSignal;
type Post = Box<dyn Fn(TerminationSignal) + Send>;
static POST: Mutex<Option<(u64, Post)>> = Mutex::new(None);
fn start_listener() -> std::io::Result<()> {
static STARTED: OnceLock<Result<(), String>> = OnceLock::new();
STARTED
.get_or_init(|| {
let wanted: Vec<i32> = [SIGTERM, SIGHUP]
.into_iter()
.filter(|&signal| !ignored(signal))
.collect();
if wanted.is_empty() {
return Ok(());
}
let mut signals = Signals::new(wanted).map_err(|e| e.to_string())?;
std::thread::Builder::new()
.name("teksilo-quit-signals".into())
.spawn(move || {
for signal in signals.forever() {
deliver(signal);
}
})
.map(drop)
.map_err(|e| e.to_string())
})
.clone()
.map_err(std::io::Error::other)
}
fn ignored(signal: i32) -> bool {
unsafe {
let mut old: libc::sigaction = std::mem::zeroed();
libc::sigaction(signal, std::ptr::null(), &mut old) == 0
&& old.sa_sigaction == libc::SIG_IGN
}
}
fn post_slot() -> std::sync::MutexGuard<'static, Option<(u64, Post)>> {
POST.lock().unwrap_or_else(|poison| poison.into_inner())
}
fn deliver(signal: i32) {
let post = post_slot();
match post.as_ref() {
Some((_, post)) => post(TerminationSignal { signal }),
None => {
drop(post);
let _ = signal_hook::low_level::emulate_default_handler(signal);
}
}
}
pub struct SignalWatch {
number: u64,
}
impl SignalWatch {
pub fn new(post: impl Fn(TerminationSignal) + Send + 'static) -> std::io::Result<Self> {
static NEXT: AtomicU64 = AtomicU64::new(0);
let number = NEXT.fetch_add(1, Ordering::Relaxed);
*post_slot() = Some((number, Box::new(post)));
let watch = Self { number };
start_listener()?;
Ok(watch)
}
}
impl Drop for SignalWatch {
fn drop(&mut self) {
let mut slot = post_slot();
if slot
.as_ref()
.is_some_and(|(number, _)| *number == self.number)
{
*slot = None;
}
}
}
impl std::fmt::Debug for SignalWatch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SignalWatch").finish()
}
}