use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
#[cfg(unix)]
use std::os::unix::net::UnixDatagram;
#[derive(Debug, Clone, Default)]
pub struct SignalFlags {
pub sigint_requested: Arc<AtomicBool>,
pub sigterm_requested: Arc<AtomicBool>,
pub reload_requested: Arc<AtomicBool>,
}
impl SignalFlags {
pub fn new() -> Self {
Self::default()
}
pub fn install(&self) -> Result<(), String> {
#[cfg(unix)]
{
use signal_hook::consts::signal::{SIGHUP, SIGINT, SIGTERM};
use signal_hook::flag;
flag::register(SIGINT, self.sigint_requested.clone())
.map_err(|e| format!("install SIGINT handler: {}", e))?;
flag::register(SIGTERM, self.sigterm_requested.clone())
.map_err(|e| format!("install SIGTERM handler: {}", e))?;
flag::register(SIGHUP, self.reload_requested.clone())
.map_err(|e| format!("install SIGHUP handler: {}", e))?;
}
#[cfg(windows)]
{
use signal_hook::consts::signal::{SIGINT, SIGTERM};
use signal_hook::flag;
flag::register(SIGINT, self.sigint_requested.clone())
.map_err(|e| format!("install SIGINT handler: {}", e))?;
flag::register(SIGTERM, self.sigterm_requested.clone())
.map_err(|e| format!("install SIGTERM handler: {}", e))?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct NotifySocket {
socket_path: Option<std::path::PathBuf>,
}
impl NotifySocket {
pub fn from_env() -> Self {
match std::env::var("NOTIFY_SOCKET").ok() {
Some(path) if !path.is_empty() => Self {
socket_path: Some(std::path::PathBuf::from(path)),
},
_ => Self::default(),
}
}
pub fn is_active(&self) -> bool {
self.socket_path.is_some()
}
fn send(&self, msg: &str) {
let Some(ref path) = self.socket_path else {
return;
};
match Self::send_to(path, msg) {
Ok(()) => {}
Err(e) => {
eprintln!(
"[scheduler] notify-socket write failed: {}; continuing without notifications",
e
);
}
}
}
#[cfg(unix)]
fn send_to(path: &Path, msg: &str) -> std::io::Result<()> {
let path_str = path.to_string_lossy();
if let Some(stripped) = path_str.strip_prefix('@') {
return send_to_abstract(stripped, msg);
}
let socket = UnixDatagram::unbound()?;
socket.connect(path)?;
socket.send(msg.as_bytes())?;
Ok(())
}
#[cfg(not(unix))]
fn send_to(_path: &Path, _msg: &str) -> std::io::Result<()> {
Ok(())
}
pub fn notify_ready(&self) {
self.send("READY=1\n");
}
pub fn notify_status(&self, status: &str) {
let mut s = String::with_capacity(status.len() + 8);
s.push_str("STATUS=");
s.push_str(status);
s.push('\n');
self.send(&s);
}
pub fn notify_stopping(&self) {
self.send("STOPPING=1\n");
}
pub fn notify_watchdog(&self) {
self.send("WATCHDOG=1\n");
}
}
#[cfg(target_os = "linux")]
fn send_to_abstract(name: &str, msg: &str) -> std::io::Result<()> {
use std::os::linux::net::SocketAddrExt;
let addr = std::os::unix::net::SocketAddr::from_abstract_name(name.as_bytes())?;
let socket = UnixDatagram::unbound()?;
socket.connect_addr(&addr)?;
socket.send(msg.as_bytes())?;
Ok(())
}
#[cfg(all(unix, not(target_os = "linux")))]
fn send_to_abstract(_name: &str, _msg: &str) -> std::io::Result<()> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"abstract NOTIFY_SOCKET addresses are Linux-only; configure the supervisor to use a filesystem-path socket",
))
}
#[cfg(not(unix))]
#[allow(dead_code)]
fn send_to_abstract(_name: &str, _msg: &str) -> std::io::Result<()> {
Ok(())
}
pub fn watchdog_interval_ms() -> Option<u64> {
let raw = std::env::var("WATCHDOG_USEC").ok()?;
let usec: u64 = raw.parse().ok()?;
let ms = usec / 1000;
Some(ms / 2)
}