#[cfg(unix)]
use crate::core::parse_signal_name;
use crate::core::{
apply_filters, apply_to_each, flatten_targets, resolve_targets_excluding_self, NameMatch,
Process,
};
use crate::error::{ProcError, Result};
use crate::ui::Printer;
use clap::Args;
#[derive(Args, Debug)]
pub struct StopCommand {
#[arg(required = true, num_args = 1.., value_parser = crate::core::validate_target_arg)]
target: Vec<String>,
#[arg(long, short = 'y')]
yes: bool,
#[arg(long)]
dry_run: bool,
#[arg(long, short = 'j')]
json: bool,
#[arg(long, short = 'v')]
verbose: bool,
#[arg(long, short, default_value = "10")]
timeout: u64,
#[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
pub in_dir: Option<String>,
#[arg(long = "by", short = 'b')]
pub by_name: Option<String>,
#[arg(long)]
pub cmd: bool,
#[arg(long, short = 'S')]
pub signal: Option<String>,
}
impl StopCommand {
pub fn execute(&self) -> Result<()> {
let printer = Printer::from_flags(self.json, self.verbose);
#[cfg(unix)]
let custom_signal = if let Some(ref sig_name) = self.signal {
Some(parse_signal_name(sig_name)?)
} else {
None
};
#[cfg(not(unix))]
if self.signal.is_some() {
return Err(ProcError::NotSupported(
"stop --signal on Windows (proc stop asks the process to exit)".to_string(),
));
}
let targets = flatten_targets(&self.target);
let mode = NameMatch::from_flag(self.cmd);
let resolved = resolve_targets_excluding_self(&targets, mode);
if let Some(warning) = resolved.warning() {
printer.warning(&warning);
}
let printer = printer.with_not_found(&resolved.not_found);
let mut processes = resolved.processes;
apply_filters(&mut processes, &self.in_dir, &self.by_name, mode)?;
if self.dry_run || !self.yes {
Process::measure_cpu(&mut processes);
}
if processes.is_empty() {
return Err(ProcError::ProcessNotFound(self.target.join(" ")));
}
if self.dry_run {
printer.print_dry_run("stop", &processes);
return Ok(());
}
if !printer.ask_confirm("stop", &processes, self.yes)? {
return Ok(());
}
let (signalled, mut failed) = apply_to_each(&processes, |proc| {
#[cfg(unix)]
if let Some(signal) = custom_signal {
return proc.send_signal(signal);
}
ask_to_end(proc)
});
let outlived = self.wait_for_exit(&signalled);
let (stopped, failed_to_kill) = apply_to_each(&signalled, |proc| {
if outlived.contains(&proc.pid) {
proc.kill_and_wait()
} else {
Ok(())
}
});
failed.extend(failed_to_kill);
in_process_order(&processes, &mut failed);
printer.print_action_result("stop", &stopped, &failed)
}
fn wait_for_exit(&self, processes: &[Process]) -> Vec<u32> {
let start = std::time::Instant::now();
let timeout = std::time::Duration::from_secs(self.timeout);
let mut running: Vec<&Process> = processes.iter().collect();
loop {
running.retain(|p| p.is_running());
if running.is_empty() || start.elapsed() >= timeout {
return running.iter().map(|p| p.pid).collect();
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}
fn in_process_order(processes: &[Process], failed: &mut [(Process, String)]) {
failed.sort_by_key(|(failed, _)| {
processes
.iter()
.position(|p| p.pid == failed.pid)
.unwrap_or(usize::MAX)
});
}
fn ask_to_end(proc: &Process) -> Result<()> {
match proc.terminate() {
Err(_) if cfg!(not(unix)) && proc.is_running() => Ok(()),
result => result,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::{end, gone_record, idle_child, record_of};
#[test]
fn failures_from_both_passes_read_in_the_order_of_the_approved_list() {
let mut child = idle_child();
let record = record_of(&child);
end(&mut child);
let with_pid = |pid| Process {
pid,
..record.clone()
};
let approved = [with_pid(30), with_pid(10), with_pid(20), with_pid(40)];
let mut failed = vec![
(with_pid(40), "signal".to_string()),
(with_pid(30), "signal".to_string()),
(with_pid(20), "kill".to_string()),
];
in_process_order(&approved, &mut failed);
let order: Vec<(u32, &str)> = failed.iter().map(|(p, e)| (p.pid, e.as_str())).collect();
assert_eq!(order, [(30, "signal"), (20, "kill"), (40, "signal")]);
}
#[test]
fn a_process_that_is_gone_cannot_be_asked_to_end() {
let mut child = idle_child();
let record = gone_record(&mut child);
let result = ask_to_end(&record);
assert!(
matches!(&result, Err(ProcError::ProcessNotFound(_))),
"{result:?}"
);
}
#[test]
fn a_running_process_is_asked_and_the_answer_does_not_matter_on_windows() {
let mut child = idle_child();
let record = record_of(&child);
let result = ask_to_end(&record);
end(&mut child);
assert!(result.is_ok(), "{result:?}");
}
}