use anyhow::{bail, Result};
use colored::Colorize;
use std::time::{Duration, Instant};
pub async fn handle_stop() -> Result<()> {
let targets = find_daemon_pids();
if targets.is_empty() {
bail!("No daemon running");
}
println!(
"{} Stopping trusty-memory daemon ({} process(es): {:?})…",
"⟳".cyan(),
targets.len(),
targets
);
for pid in &targets {
let _ = send_signal(*pid, "TERM");
}
let deadline = Instant::now() + Duration::from_secs(5);
loop {
std::thread::sleep(Duration::from_millis(100));
let any_alive = targets.iter().any(|p| pid_alive(*p));
if !any_alive {
println!("{} Daemon stopped", "✓".green());
cleanup_addr_file();
return Ok(());
}
if Instant::now() >= deadline {
break;
}
}
let stragglers: Vec<u32> = targets.iter().copied().filter(|p| pid_alive(*p)).collect();
if !stragglers.is_empty() {
println!(
"{} {} process(es) ignored SIGTERM — sending SIGKILL: {:?}",
"⚠".yellow(),
stragglers.len(),
stragglers
);
for pid in &stragglers {
let _ = send_signal(*pid, "KILL");
}
std::thread::sleep(Duration::from_millis(500));
}
if targets.iter().any(|p| pid_alive(*p)) {
println!("{} Daemon may still be shutting down", "⚠".yellow());
} else {
println!("{} Daemon stopped", "✓".green());
cleanup_addr_file();
}
Ok(())
}
fn cleanup_addr_file() {
if let Ok(dir) = trusty_common::resolve_data_dir("trusty-memory") {
let _ = std::fs::remove_file(dir.join("http_addr"));
}
}
pub(crate) fn find_daemon_pids() -> Vec<u32> {
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
let mut sys = System::new_with_specifics(
RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing()),
);
sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
let me = std::process::id();
let mut out = Vec::new();
for (pid, proc_) in sys.processes() {
let raw = pid.as_u32();
if raw == me {
continue;
}
if proc_.name().to_string_lossy() == "trusty-memory" {
let is_daemon = proc_.cmd().iter().any(|a| a.to_string_lossy() == "serve");
if is_daemon {
out.push(raw);
}
}
}
out
}
#[cfg(unix)]
fn send_signal(pid: u32, sig: &str) -> std::io::Result<()> {
let status = std::process::Command::new("kill")
.arg(format!("-{sig}"))
.arg(pid.to_string())
.status()?;
if !status.success() {
return Err(std::io::Error::other(format!(
"kill -{sig} {pid} exited {status}"
)));
}
Ok(())
}
#[cfg(not(unix))]
fn send_signal(_pid: u32, _sig: &str) -> std::io::Result<()> {
Err(std::io::Error::other(
"signals unsupported on this platform",
))
}
#[cfg(unix)]
fn pid_alive(pid: u32) -> bool {
std::process::Command::new("kill")
.arg("-0")
.arg(pid.to_string())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
#[cfg(not(unix))]
fn pid_alive(_pid: u32) -> bool {
true
}