#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
pub use fallow_process::ScopedChild;
pub mod scoped_child {
pub use fallow_process::{output, status};
}
static SHUTDOWN: AtomicBool = AtomicBool::new(false);
static GRACEFUL: AtomicBool = AtomicBool::new(false);
static INSTALLED: OnceLock<()> = OnceLock::new();
pub fn install_handlers() -> std::io::Result<()> {
if INSTALLED.get().is_some() {
return Ok(());
}
let result = platform_install();
if result.is_ok() {
let _ = INSTALLED.set(());
}
result
}
pub fn install_panic_drain() {
static PANIC_HOOK: OnceLock<()> = OnceLock::new();
PANIC_HOOK.get_or_init(|| {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
previous(info);
fallow_process::try_drain_and_kill();
}));
});
}
pub fn is_shutting_down() -> bool {
SHUTDOWN.load(Ordering::SeqCst)
}
pub fn set_graceful_mode() {
GRACEFUL.store(true, Ordering::SeqCst);
}
pub fn clear_graceful_mode() {
GRACEFUL.store(false, Ordering::SeqCst);
}
pub struct GracefulModeGuard;
impl GracefulModeGuard {
pub fn new() -> Self {
set_graceful_mode();
Self
}
}
impl Default for GracefulModeGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for GracefulModeGuard {
fn drop(&mut self) {
clear_graceful_mode();
}
}
#[cfg(unix)]
fn platform_install() -> std::io::Result<()> {
unix::install()
}
#[cfg(windows)]
fn platform_install() -> std::io::Result<()> {
windows::install()
}
#[cfg(not(any(unix, windows)))]
fn platform_install() -> std::io::Result<()> {
Ok(())
}
fn handle_signal(exit_code: i32) {
SHUTDOWN.store(true, Ordering::SeqCst);
fallow_process::drain_and_kill();
if GRACEFUL.load(Ordering::SeqCst) {
return;
}
#[expect(
clippy::exit,
reason = "signal handler MUST terminate the process; that is the entire point of the path"
)]
std::process::exit(exit_code);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn graceful_mode_guard_sets_and_clears() {
assert!(!GRACEFUL.load(Ordering::SeqCst));
{
let _g = GracefulModeGuard::new();
assert!(GRACEFUL.load(Ordering::SeqCst));
}
assert!(!GRACEFUL.load(Ordering::SeqCst));
}
#[test]
fn install_handlers_is_idempotent() {
let _ = install_handlers();
assert!(install_handlers().is_ok());
}
}