use std::process::ExitCode;
use std::time::{Duration, Instant};
use super::pid;
const GRACEFUL_TIMEOUT: Duration = Duration::from_secs(10);
const POLL_INTERVAL: Duration = Duration::from_millis(200);
pub fn dispatch() -> ExitCode {
let Some((gateway_pid, _, _)) = pid::read_pid() else {
println!("Gateway is not running.");
return ExitCode::SUCCESS;
};
if !pid::is_process_alive(gateway_pid) {
println!("Gateway process (pid {gateway_pid}) is no longer alive; cleaning up PID file.");
let _ = pid::remove_pid();
return ExitCode::SUCCESS;
}
#[cfg(unix)]
{
let ret = unsafe { libc::kill(gateway_pid as libc::pid_t, libc::SIGTERM) };
if ret != 0 {
let err = std::io::Error::last_os_error();
eprintln!("error: could not send SIGTERM to pid {gateway_pid}: {err}");
return ExitCode::FAILURE;
}
let deadline = Instant::now() + GRACEFUL_TIMEOUT;
while Instant::now() < deadline {
if !pid::is_process_alive(gateway_pid) {
break;
}
std::thread::sleep(POLL_INTERVAL);
}
if pid::is_process_alive(gateway_pid) {
eprintln!(
"warning: gateway (pid {gateway_pid}) did not stop within {}s; \
sending SIGKILL. Audit log may be truncated.",
GRACEFUL_TIMEOUT.as_secs()
);
unsafe {
libc::kill(gateway_pid as libc::pid_t, libc::SIGKILL);
}
}
}
#[cfg(not(unix))]
{
eprintln!("error: stop is only supported on Unix platforms");
return ExitCode::FAILURE;
}
let _ = pid::remove_pid();
println!("Gateway stopped.");
ExitCode::SUCCESS
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::MutexGuard;
struct EnvGuard {
_lock: MutexGuard<'static, ()>,
prior: Option<String>,
}
impl EnvGuard {
fn set(value: &str) -> Self {
let lock = crate::test_support::env_guard();
let prior = std::env::var("AA_DATA_DIR").ok();
std::env::set_var("AA_DATA_DIR", value);
Self { _lock: lock, prior }
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
match self.prior.take() {
Some(v) => std::env::set_var("AA_DATA_DIR", v),
None => std::env::remove_var("AA_DATA_DIR"),
}
}
}
#[test]
fn dispatch_returns_success_when_no_pid_file() {
let tmp = tempfile::tempdir().unwrap();
let _guard = EnvGuard::set(tmp.path().to_str().unwrap());
assert_eq!(dispatch(), ExitCode::SUCCESS);
}
#[test]
fn dispatch_cleans_up_stale_pid_file_for_dead_process() {
let tmp = tempfile::tempdir().unwrap();
let _guard = EnvGuard::set(tmp.path().to_str().unwrap());
let mut child = std::process::Command::new("true")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.expect("failed to spawn 'true'");
let dead_pid = child.id();
child.wait().expect("wait failed");
pid::write_pid(dead_pid, "127.0.0.1:50051", "2026-05-18T00:00:00Z").unwrap();
assert_eq!(dispatch(), ExitCode::SUCCESS);
assert!(pid::read_pid().is_none());
}
}