use super::daemon_utils::daemon_port_path;
use anyhow::{bail, Result};
use colored::Colorize;
use std::time::{Duration, Instant};
pub async fn handle_stop() -> Result<()> {
let lock_path = dirs::data_local_dir().map(|d| d.join("trusty-search").join("daemon.lock"));
let port_path = daemon_port_path();
let primary_pid = lock_path
.as_ref()
.and_then(|p| std::fs::read_to_string(p).ok())
.and_then(|s| s.trim().parse::<u32>().ok());
let mut targets: Vec<u32> = find_daemon_pids();
if let Some(p) = primary_pid {
if !targets.contains(&p) {
targets.push(p);
}
}
let me = std::process::id();
targets.retain(|&pid| pid != me);
if targets.is_empty() {
bail!("No daemon running");
}
if let Some(p) = primary_pid {
println!("{} Stopping daemon (PID {})…", "⟳".cyan(), p);
}
let orphans: Vec<u32> = targets
.iter()
.copied()
.filter(|p| Some(*p) != primary_pid)
.collect();
if !orphans.is_empty() {
println!(
"{} Found {} orphan trusty-search process(es): {:?} — terminating",
"⚠".yellow(),
orphans.len(),
orphans
);
}
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));
let port_gone = port_path.as_ref().map(|p| !p.exists()).unwrap_or(true);
if !any_alive && port_gone {
println!("{} Daemon stopped", "✓".green());
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 let Some(p) = port_path.as_ref() {
if p.exists() && !targets.iter().any(|pid| pid_alive(*pid)) {
let _ = std::fs::remove_file(p);
}
}
if targets.iter().any(|p| pid_alive(*p)) {
println!("{} Daemon may still be shutting down", "⚠".yellow());
} else {
println!("{} Daemon stopped", "✓".green());
}
Ok(())
}
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-search" {
let is_daemon = proc_.cmd().iter().any(|a| a.to_string_lossy() == "start");
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 {
match nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid as i32), None) {
Ok(()) => true,
Err(nix::errno::Errno::EPERM) => true,
Err(_) => false,
}
}
#[cfg(not(unix))]
fn pid_alive(_pid: u32) -> bool {
true
}