#[cfg(unix)]
const CANCEL_SIGNALS: [(i32, u8); 3] = [
(signal_hook::consts::SIGINT, EXIT_SIGINT as u8),
(signal_hook::consts::SIGTERM, EXIT_SIGTERM as u8),
(signal_hook::consts::SIGHUP, EXIT_SIGHUP as u8),
];
#[cfg(unix)]
fn register_cancel_signal(signal: &Arc<ShutdownSignal>, sig: i32, code: u8) -> Result<()> {
signal_hook::flag::register(sig, Arc::clone(&signal.flag)).context("flag handler")?;
let target = Arc::clone(signal);
unsafe {
signal_hook::low_level::register(sig, move || target.record_signal(code))
.context("counter handler")?;
}
Ok(())
}
pub fn install_handlers_early() -> Option<Arc<ShutdownSignal>> {
let flag = Arc::new(AtomicBool::new(false));
let signal = Arc::new(ShutdownSignal {
flag: Arc::clone(&flag),
count: AtomicU8::new(0),
signal_code: AtomicU8::new(0),
});
#[cfg(unix)]
{
for (sig, code) in CANCEL_SIGNALS {
if let Err(e) = register_cancel_signal(&signal, sig, code) {
crate::best_effort::no_channel_left(writeln!(
std::io::stderr(),
"atomwrite: failed to register handler for signal {sig}: {e:#}"
));
}
}
if let Err(e) = register_broken_pipe(&signal) {
crate::best_effort::no_channel_left(writeln!(
std::io::stderr(),
"atomwrite: failed to register the SIGPIPE handler: {e:#}"
));
}
}
GLOBAL_SHUTDOWN.set(Arc::clone(&signal)).ok();
Some(signal)
}
pub fn write_ready_file(path: &std::path::Path) {
if let Some(parent) = path.parent()
&& let Err(e) = std::fs::create_dir_all(parent)
{
tracing::debug!(error = %e, parent = %parent.display(), "cannot create --ready-file parent");
}
if let Err(e) = std::fs::write(path, std::process::id().to_string()) {
tracing::warn!(error = %e, path = %path.display(), "cannot write --ready-file");
}
}
#[cfg(unix)]
fn register_broken_pipe(signal: &Arc<ShutdownSignal>) -> Result<()> {
let target = Arc::clone(signal);
unsafe {
signal_hook::low_level::register(signal_hook::consts::SIGPIPE, move || {
target.record_broken_pipe();
})
.context("SIGPIPE handler")?;
}
Ok(())
}
#[cfg(unix)]
fn wait_stderr_writable(max: std::time::Duration) {
let slice = max.min(std::time::Duration::from_millis(
crate::constants::SHUTDOWN_BANNER_POLL_SLICE_MS,
));
let timeout_ms = i32::try_from(slice.as_millis()).unwrap_or(i32::MAX).max(1);
let mut fd = libc::pollfd {
fd: libc::STDERR_FILENO,
events: libc::POLLOUT,
revents: 0,
};
unsafe {
libc::poll(&mut fd, 1, timeout_ms);
}
}
pub fn write_shutdown_message() {
if is_global_broken_pipe() {
return;
}
#[cfg(unix)]
{
let banner = format!("{}\n", rust_i18n::t!("cancel.shutting-down"));
let shutdown_msg = banner.as_bytes();
let mut written = 0usize;
let give_up_at = std::time::Instant::now()
+ std::time::Duration::from_millis(crate::constants::SHUTDOWN_BANNER_MAX_WAIT_MS);
while written < shutdown_msg.len() {
let ret = unsafe {
libc::write(
libc::STDERR_FILENO,
shutdown_msg[written..].as_ptr().cast::<libc::c_void>(),
shutdown_msg.len() - written,
)
};
if ret < 0 {
let err = std::io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::EINTR) => continue,
Some(libc::EAGAIN) => {
let left = give_up_at.saturating_duration_since(std::time::Instant::now());
if left.is_zero() {
break;
}
wait_stderr_writable(left);
continue;
}
_ => break,
}
}
written += ret as usize;
}
}
}
pub fn install_handlers() -> Result<Arc<ShutdownSignal>> {
if let Some(existing) = GLOBAL_SHUTDOWN.get() {
return Ok(Arc::clone(existing));
}
let flag = Arc::new(AtomicBool::new(false));
let signal = Arc::new(ShutdownSignal {
flag: Arc::clone(&flag),
count: AtomicU8::new(0),
signal_code: AtomicU8::new(0),
});
#[cfg(unix)]
{
for (sig, code) in CANCEL_SIGNALS {
register_cancel_signal(&signal, sig, code)
.with_context(|| format!("failed to register handler for signal {sig}"))?;
}
register_broken_pipe(&signal)?;
}
#[cfg(windows)]
{
let sig_win = Arc::clone(&signal);
ctrlc::set_handler(move || {
let was_first = sig_win.count.load(Ordering::Acquire) == 0;
sig_win.record_signal(EXIT_SIGINT as u8);
if was_first {
eprintln!("\n{}", rust_i18n::t!("cancel.shutting-down"));
}
})
.context("failed to register Ctrl+C handler")?;
}
#[cfg(not(any(unix, windows)))]
{
tracing::warn!("signal handlers not available on this platform — Ctrl+C may not work");
}
GLOBAL_SHUTDOWN.set(Arc::clone(&signal)).ok();
Ok(signal)
}
pub fn get_or_install_handlers() -> Result<Arc<ShutdownSignal>> {
if let Some(existing) = GLOBAL_SHUTDOWN.get() {
return Ok(Arc::clone(existing));
}
install_handlers()
}
pub fn arm_global_timeout(secs: u64) {
if secs == 0 {
return;
}
let Some(sig) = GLOBAL_SHUTDOWN.get().cloned() else {
tracing::warn!(
timeout_secs = secs,
"arm_global_timeout called before handlers installed; deadline ignored"
);
return;
};
record_global_deadline(secs);
let builder = std::thread::Builder::new().name(crate::constants::THREAD_NAME_TIMEOUT.into());
if let Err(e) = builder.spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(secs));
if !sig.is_shutdown() {
tracing::warn!(
timeout_secs = secs,
"global --timeout-secs deadline elapsed; cooperative cancel"
);
sig.record_signal(EXIT_TIMEOUT);
}
}) {
tracing::warn!(error = %e, "failed to spawn global timeout watchdog");
}
}
pub fn write_timeout_message() {
eprintln!("{}", rust_i18n::t!("cancel.timeout-elapsed"));
}