proc_cli/commands/
stuck.rs1use crate::core::Process;
9use crate::error::Result;
10use crate::ui::{OutputFormat, Printer};
11use clap::Args;
12use dialoguer::Confirm;
13use std::time::Duration;
14
15#[derive(Args, Debug)]
17pub struct StuckCommand {
18 #[arg(long, short = 't', default_value = "300")]
20 pub timeout: u64,
21
22 #[arg(long, short = 'k')]
24 pub kill: bool,
25
26 #[arg(long, short = 'y')]
28 pub yes: bool,
29
30 #[arg(long, short = 'j')]
32 pub json: bool,
33
34 #[arg(long, short = 'v')]
36 pub verbose: bool,
37}
38
39impl StuckCommand {
40 pub fn execute(&self) -> Result<()> {
42 let format = if self.json {
43 OutputFormat::Json
44 } else {
45 OutputFormat::Human
46 };
47 let printer = Printer::new(format, self.verbose);
48
49 let timeout = Duration::from_secs(self.timeout);
50 let processes = Process::find_stuck(timeout)?;
51
52 if processes.is_empty() {
53 printer.success(&format!(
54 "No stuck processes found (threshold: {}s)",
55 self.timeout
56 ));
57 return Ok(());
58 }
59
60 printer.warning(&format!(
61 "Found {} potentially stuck process{}",
62 processes.len(),
63 if processes.len() == 1 { "" } else { "es" }
64 ));
65 printer.print_processes(&processes);
66
67 if self.kill {
69 if !self.yes && !self.json {
70 let confirmed = Confirm::new()
71 .with_prompt(format!(
72 "Kill {} stuck process{}?",
73 processes.len(),
74 if processes.len() == 1 { "" } else { "es" }
75 ))
76 .default(false)
77 .interact()
78 .unwrap_or(false);
79
80 if !confirmed {
81 printer.warning("Cancelled");
82 return Ok(());
83 }
84 }
85
86 let mut killed = Vec::new();
87 let mut failed = Vec::new();
88
89 for proc in processes {
90 match proc.kill_and_wait() {
92 Ok(_) => killed.push(proc),
93 Err(e) => failed.push((proc, e.to_string())),
94 }
95 }
96
97 printer.print_kill_result(&killed, &failed);
98 }
99
100 Ok(())
101 }
102}