use crate::cli::CloseArgs; use crate::error::Result; use crate::{instance, profile, ui}; use std::time::{Duration, Instant}; const GRACEFUL_WAIT: Duration = Duration::from_secs(5); pub fn run(args: CloseArgs) -> Result<()> { let name = profile::normalize(&args.name); profile::load(&name)?; // validate the profile itself exists if close_instance(&name, args.force)? { ui::ok(&format!("Closed '{name}'.")); } else { ui::info(&format!("'{name}' is not open.")); } Ok(()) } /// Stops `name`'s supervisor if one is actually running, and clears any /// stale instance file either way, shared with `remove` and `wipe`. /// Returns whether anything was actually running. pub fn close_instance(name: &str, force: bool) -> Result { let Some(pid) = instance::running_pid(name)? else { instance::delete(name)?; // clears a stale file left by a crash return Ok(false); }; if force { // SIGKILL the whole process group (the supervisor is its own // group leader via setsid), not just the supervisor pid; a plain // single-pid SIGKILL would leave `ssh` orphaned. unsafe { libc::kill(-pid, libc::SIGKILL) }; } else { unsafe { libc::kill(pid, libc::SIGTERM) }; let deadline = Instant::now() + GRACEFUL_WAIT; while instance::process_alive(pid) && Instant::now() < deadline { std::thread::sleep(Duration::from_millis(100)); } if instance::process_alive(pid) { unsafe { libc::kill(-pid, libc::SIGKILL) }; } } // The supervisor removes its own instance file on a clean SIGTERM // shutdown; this covers the force-killed case where it never got to. instance::delete(name)?; Ok(true) }