cargo_e/
e_processmanager.rs

1// src/e_process_manager.rs
2
3use crate::e_cargocommand_ext::{CargoProcessHandle, CargoProcessResult};
4use crate::e_command_builder::TerminalError;
5use crate::e_target::CargoTarget;
6use crate::Cli;
7use chrono::Local;
8use std::collections::HashMap;
9use std::process::ExitStatus;
10use std::sync::atomic::AtomicUsize;
11use std::sync::mpsc::{self, Receiver, Sender};
12use std::sync::{Arc, Mutex};
13use std::thread::{self, sleep};
14use std::time::{Duration, SystemTime};
15use sysinfo::System;
16// use std::io::Write;
17#[cfg(feature = "tui")]
18use crossterm::{
19    cursor, execute,
20    terminal::{Clear, ClearType},
21};
22use std::io::{self, Write};
23use std::sync::atomic::Ordering;
24use std::sync::Mutex as StdMutex;
25#[cfg(unix)]
26use {
27    nix::sys::signal::{kill as nix_kill, Signal},
28    nix::unistd::Pid,
29    std::os::unix::process::ExitStatusExt,
30};
31
32
33impl ProcessObserver for ProcessManager {
34    fn on_spawn(&self, pid: u32, handle: CargoProcessHandle) {
35        self.processes
36            .lock()
37            .unwrap()
38            .insert(pid, Arc::new(Mutex::new(handle)));
39    }
40    // let pid = handle.lock().unwrap().pid;
41    // self.processes.lock().unwrap().insert(pid, handle);
42    // Ok(())
43}
44
45// #[cfg(feature = "uses_async")]
46// use tokio::sync::Notify;
47
48// pub static PROCESS_MANAGER: Lazy<ProcessManager> = Lazy::new(ProcessManager::new);
49
50pub trait ProcessObserver: Send + Sync + 'static {
51    fn on_spawn(&self, pid: u32, handle: CargoProcessHandle);
52}
53pub trait SignalTimeTracker {
54    /// Returns the time when the last signal was received, if any.
55    fn last_signal_time(&self) -> Option<SystemTime>;
56    /// Returns the duration between the last two signals, if at least two signals were received.
57    fn time_between_signals(&self) -> Option<Duration>;
58}
59
60pub struct SignalTimes {
61    times: StdMutex<Vec<SystemTime>>,
62}
63
64impl SignalTimes {
65    pub fn new() -> Self {
66        Self {
67            times: StdMutex::new(Vec::new()),
68        }
69    }
70    pub fn record_signal(&self) {
71        let mut times = self.times.lock().unwrap();
72        times.push(SystemTime::now());
73    }
74}
75
76impl SignalTimeTracker for SignalTimes {
77    fn last_signal_time(&self) -> Option<SystemTime> {
78        let times = self.times.lock().unwrap();
79        times.last().cloned()
80    }
81    fn time_between_signals(&self) -> Option<Duration> {
82        let times = self.times.lock().unwrap();
83        if times.len() >= 2 {
84            let last = times[times.len() - 1];
85            let prev = times[times.len() - 2];
86            last.duration_since(prev).ok()
87        } else {
88            None
89        }
90    }
91}
92#[derive()]
93pub struct ProcessManager {
94    signalled_count: AtomicUsize,
95    signal_tx: Sender<()>,
96    processes: Mutex<HashMap<u32, Arc<Mutex<CargoProcessHandle>>>>,
97    results: Mutex<Vec<CargoProcessResult>>,
98    signal_times: SignalTimes, // <-- Add this line
99}
100
101impl Drop for ProcessManager {
102    fn drop(&mut self) {}
103}
104
105impl std::fmt::Debug for ProcessManager {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        let processes_len = self.processes.lock().map(|p| p.len()).unwrap_or(0);
108        let results_len = self.results.lock().map(|r| r.len()).unwrap_or(0);
109        let signalled_count = self.signalled_count.load(Ordering::SeqCst);
110        let signal_times = self.signal_times.times.lock().map(|v| v.len()).unwrap_or(0);
111        f.debug_struct("ProcessManager")
112            .field("signalled_count", &signalled_count)
113            .field("signal_tx", &"Sender<()>")
114            .field("processes.len", &processes_len)
115            .field("results.len", &results_len)
116            .field("signal_times.count", &signal_times)
117            .finish()
118    }
119}
120
121impl ProcessManager {
122    pub fn new(_cli: &Cli) -> Arc<Self> {
123        let (tx, rx) = mpsc::channel();
124        let manager = Arc::new(Self {
125            signalled_count: AtomicUsize::new(0),
126            signal_tx: tx.clone(),
127            processes: Mutex::new(HashMap::new()),
128            results: Mutex::new(Vec::new()),
129            signal_times: SignalTimes::new(), // <-- Add this line
130        });
131        ProcessManager::install_handler(Arc::clone(&manager), rx);
132        crate::GLOBAL_MANAGER.get_or_init(|| Arc::clone(&manager));
133        manager
134    }
135
136    pub fn last_signal_time(&self) -> Option<SystemTime> {
137        self.signal_times.last_signal_time()
138    }
139    pub fn time_between_signals(&self) -> Option<Duration> {
140        self.signal_times.time_between_signals()
141    }
142    pub fn reset_signalled(&self) {
143        self.signalled_count.store(0, Ordering::SeqCst);
144    }
145    pub fn has_signalled(&self) -> usize {
146        self.signalled_count.load(Ordering::SeqCst)
147    }
148
149    fn install_handler(self_: Arc<Self>, rx: Receiver<()>) {
150        match ctrlc::set_handler({
151            let tx = self_.signal_tx.clone();
152            move || {
153                let _ = tx.send(());
154            }
155        }) {
156            Ok(_) => {
157                thread::spawn(move || {
158                    while rx.recv().is_ok() {
159                        self_.signalled_count.fetch_add(1, Ordering::SeqCst);
160                        println!(
161                            "ctrlc> signal received.  {}",
162                            self_.signalled_count.load(Ordering::SeqCst)
163                        );
164                        self_.handle_signal();
165                    }
166                });
167            }
168            Err(e) => {
169                eprintln!("ctrlc> Failed to install Ctrl+C handler: {}", e);
170                return;
171            }
172        }
173    }
174
175    fn handle_signal(&self) {
176        self.signal_times.record_signal();
177        println!("ctrlc>");
178        let mut processes = self.processes.lock().unwrap();
179        for (pid, handle) in processes.iter() {
180            println!("ctrlc> Terminating process with PID: {}", pid);
181            if let Ok(mut h) = handle.lock() {
182                let _ = h.kill();
183                let final_diagnostics = {
184                    let diag_lock = h.diagnostics.lock().unwrap();
185                    diag_lock.clone()
186                };
187                h.result.diagnostics = final_diagnostics.clone();
188
189                if let Some(exit_status) = h.child.try_wait().ok().flatten() {
190                    h.result.exit_status = Some(exit_status);
191                }
192
193                h.result.end_time = Some(SystemTime::now());
194                if let (Some(start), Some(end)) = (h.result.start_time, h.result.end_time) {
195                    h.result.elapsed_time = Some(end.duration_since(start).unwrap_or_default());
196                }
197                self.record_result(h.result.clone());
198            }
199        }
200        processes.clear();
201    }
202
203    /// Updates the status line in the terminal.
204    /// When `raw_mode` is true, it uses crossterm commands to clear the current line.
205    /// Otherwise, it uses the carriage return (`\r`) trick.
206    pub fn update_status_line(output: &str, raw_mode: bool) -> io::Result<()> {
207        let mut stdout = io::stdout();
208        if raw_mode {
209            // Move cursor to beginning and clear the current line.
210            #[cfg(feature = "tui")]
211            {
212                execute!(
213                    stdout,
214                    cursor::MoveToColumn(0),
215                    Clear(ClearType::CurrentLine)
216                )?;
217                print!("{}", output);
218            }
219            #[cfg(not(feature = "tui"))]
220            print!("\r{}", output);
221        } else {
222            // In non-raw mode, the \r trick can work.
223            print!("\r{}", output);
224        }
225        stdout.flush()
226    }
227
228    pub fn register(&self, handle: CargoProcessHandle) -> u32 {
229        let pid = handle.pid;
230        self.processes
231            .lock()
232            .unwrap()
233            .insert(pid, Arc::new(Mutex::new(handle)));
234
235        // #[cfg(feature = "uses_async")]
236        // self.notifier.notify_waiters();
237
238        pid
239    }
240
241    pub fn take(&self, pid: u32) -> Option<Arc<Mutex<CargoProcessHandle>>> {
242        self.processes.lock().unwrap().remove(&pid)
243    }
244
245    pub fn remove(&self, pid: u32) {
246        if let Some(handle_arc) = self.processes.lock().unwrap().remove(&pid) {
247            let mut h = handle_arc.lock().unwrap();
248            let final_diagnostics = {
249                let diag_lock = h.diagnostics.lock().unwrap();
250                diag_lock.clone()
251            };
252            h.result.diagnostics = final_diagnostics.clone();
253
254            // Ensure `es` is properly defined or assigned
255            if let Some(exit_status) = h.child.try_wait().ok().flatten() {
256                h.result.exit_status = Some(exit_status);
257            }
258
259            h.result.end_time = Some(SystemTime::now());
260            if let (Some(start), Some(end)) = (h.result.start_time, h.result.end_time) {
261                h.result.elapsed_time = Some(end.duration_since(start).unwrap_or_default());
262            }
263            self.record_result(h.result.clone());
264            drop(h);
265            // This was the only Arc reference, so dropping it here will run CargoProcessHandle::drop()
266            drop(handle_arc);
267        }
268    }
269
270    pub fn try_wait(&self, pid: u32) -> anyhow::Result<Option<ExitStatus>> {
271        // 1. Lock the processes map just long enough to clone the Arc.
272        let handle_arc = {
273            let processes = self.processes.lock().unwrap();
274            // Clone the Arc to keep the handle in the map while getting your own reference.
275            processes
276                .get(&pid)
277                .ok_or_else(|| anyhow::anyhow!("Process handle with PID {} not found", pid))?
278                .clone()
279        };
280
281        // 2. Lock the individual process handle to perform try_wait.
282        let mut handle = handle_arc.lock().unwrap();
283        // Here, try_wait returns a Result<Option<ExitStatus>, std::io::Error>.
284        // The '?' operator will convert any std::io::Error to anyhow::Error automatically.
285        let status = handle.child.try_wait()?;
286
287        // Return the exit status (or None) wrapped in Ok.
288        Ok(status)
289    }
290
291    pub fn get(&self, pid: u32) -> Option<Arc<Mutex<CargoProcessHandle>>> {
292        self.processes.lock().unwrap().get(&pid).cloned()
293    }
294
295    pub fn list(&self) -> Vec<u32> {
296        self.processes.lock().unwrap().keys().cloned().collect()
297    }
298
299    pub fn status(&self) {
300        let processes = self.processes.lock().unwrap();
301        if processes.is_empty() {
302            println!("No active cargo processes.");
303        } else {
304            println!("Active processes:");
305            for pid in processes.keys() {
306                println!(" - PID: {}", pid);
307            }
308        }
309    }
310
311    // /// Attempts to kill the process corresponding to the provided PID.
312    // /// Returns Ok(true) if the process was found and successfully killed (or had already exited),
313    // /// Ok(false) if the process was not found or did not exit within the maximum attempts,
314    // /// or an error if something went wrong.
315    // pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
316    //     // Retrieve a clone of the handle (Arc) for the given PID without removing it.
317    //     let handle = {
318    //         let processes = self.processes.lock().unwrap();
319    //         processes.get(&pid).cloned()
320    //     };
321
322    //     if let Some(handle) = handle {
323    //         eprintln!("Attempting to kill PID: {}", pid);
324
325    //         let max_attempts = 3;
326    //         let mut attempts = 0;
327    //         let mut process_exited = false;
328
329    //         loop {
330    //             // Lock the process handle for this iteration.
331    //             if let Ok(mut h) = handle.lock() {
332    //                 // Check if the process has already exited.
333    //                 match h.child.try_wait() {
334    //                     Ok(Some(status)) => {
335    //                         eprintln!("Process {} already exited with status: {:?}", pid, status);
336    //                         process_exited = true;
337    //                         break;
338    //                     }
339    //                     Ok(None) => {
340    //                         // Process is still running.
341    //                         if attempts == 0 {
342    //                             #[cfg(not(target_os = "windows"))] {
343    //                                 eprintln!("Sending initial Ctrl+C signal to PID: {}", pid);
344    //                                 crate::e_runall::send_ctrl_c(&mut h.child)?;
345    //                             }
346    //                             #[cfg(target_os = "windows")] {
347    //                                 eprintln!("Sending initial kill signal to PID: {}", pid);
348    //                             }
349    //                         } else {
350    //                             eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
351    //                         }
352    //                         // Attempt to kill the process.
353    //                         if let Err(e) = h.kill() {
354    //                             eprintln!("Failed to send kill signal to PID {}: {}", pid, e);
355    //                         }
356    //                         // Mark that an exit was requested.
357    //                         h.requested_exit = true;
358    //                     }
359    //                     Err(e) => {
360    //                         eprintln!("Error checking exit status for PID {}: {}", pid, e);
361    //                         break;
362    //                     }
363    //                 }
364    //             } else {
365    //                 eprintln!("Could not lock process handle for PID {}", pid);
366    //                 break;
367    //             }
368
369    //             attempts += 1;
370    //             // Allow some time for the process to exit.
371    //             sleep(Duration::from_millis(2000));
372
373    //             // Re-check if the process has exited.
374    //             if let Ok(mut h) = handle.lock() {
375    //                 match h.child.try_wait() {
376    //                     Ok(Some(status)) => {
377    //                         eprintln!("Process {} exited with status: {:?}", pid, status);
378    //                         process_exited = true;
379    //                         break;
380    //                     }
381    //                     Ok(None) => {
382    //                         eprintln!("Process {} still running after attempt {}.", pid, attempts);
383    //                     }
384    //                     Err(e) => {
385    //                         eprintln!("Error rechecking process {}: {}", pid, e);
386    //                         break;
387    //                     }
388    //                 }
389    //             }
390
391    //             if attempts >= max_attempts {
392    //                 eprintln!("Maximum kill attempts reached for PID {}.", pid);
393    //                 break;
394    //             }
395    //         }
396
397    //         // If the process exited, remove it from the map.
398    //         if process_exited {
399    //             let mut processes = self.processes.lock().unwrap();
400    //             processes.remove(&pid);
401    //             eprintln!("Process {} removed from map after exit.", pid);
402    //         } else {
403    //             eprintln!(
404    //                 "Process {} did not exit after {} attempts; it remains in the map for future handling.",
405    //                 pid, attempts
406    //             );
407    //         }
408    //         Ok(process_exited)
409    //     } else {
410    //         eprintln!("Process handle with PID {} not found.", pid);
411    //         Ok(false)
412    //     }
413    // }
414
415    /// Attempts to kill the process corresponding to the provided PID.
416    /// Returns Ok(true) if the process was found and exited (even via signal),
417    /// Ok(false) if the process wasn’t found or didn’t exit after trying
418    /// all signals (in which case we drop the handle), or Err if something went wrong.
419    pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
420        // Grab the handle, if any.
421        let handle_opt = {
422            let procs = self.processes.lock().unwrap();
423            procs.get(&pid).cloned()
424        };
425
426        if let Some(handle) = handle_opt {
427            eprintln!("Attempting to kill PID: {}", pid);
428            #[cfg(unix)]
429            let signals = [
430                Signal::SIGHUP,
431                Signal::SIGINT,
432                Signal::SIGQUIT,
433                Signal::SIGABRT,
434                Signal::SIGKILL,
435                Signal::SIGALRM,
436                Signal::SIGTERM,
437            ];
438            #[cfg(unix)]
439            let max_attempts = signals.len();
440
441            #[cfg(windows)]
442            let max_attempts = 3; // arbitrary, since Child::kill() is always SIGKILL
443
444            let mut attempts = 0;
445            let mut did_exit = false;
446
447            while attempts < max_attempts {
448                // 1) Check status
449                if let Ok(mut h) = handle.lock() {
450                    match h.child.try_wait() {
451                        Ok(Some(status)) => {
452                            // Child has exited—report how.
453                            #[cfg(unix)]
454                            {
455                                if let Some(sig) = status.signal() {
456                                    eprintln!("Process {} terminated by signal {}", pid, sig);
457                                } else if let Some(code) = status.code() {
458                                    eprintln!("Process {} exited with code {}", pid, code);
459                                } else {
460                                    eprintln!(
461                                        "Process {} exited with unknown status: {:?}",
462                                        pid, status
463                                    );
464                                }
465                            }
466                            #[cfg(not(unix))]
467                            {
468                                if let Some(code) = status.code() {
469                                    eprintln!("Process {} exited with code {}", pid, code);
470                                } else {
471                                    eprintln!(
472                                        "Process {} exited with unknown status: {:?}",
473                                        pid, status
474                                    );
475                                }
476                            }
477                            did_exit = true;
478                            break;
479                        }
480                        Ok(None) => {
481                            // Still running → send the next signal
482                            #[cfg(unix)]
483                            {
484                                let sig = signals[attempts];
485                                eprintln!(
486                                    "Attempt {}: sending {:?} to PID {}",
487                                    attempts + 1,
488                                    sig,
489                                    pid
490                                );
491                                nix_kill(Pid::from_raw(pid as i32), sig)?;
492                            }
493                            #[cfg(windows)]
494                            {
495                                // // Remove the handle so it drops (and on Windows that will kill if still alive)
496                                // {
497                                //     let mut procs = self.processes.lock().unwrap();
498                                //     procs.remove(&pid);
499                                // }
500
501                                eprintln!("Attempt {}: killing PID {}", attempts + 1, pid);
502                                if let Err(e) = h.child.kill() {
503                                    eprintln!("Failed to kill PID {}: {}", pid, e);
504                                }
505                                _ = std::process::Command::new("taskkill")
506                                    .args(["/F", "/PID", &pid.to_string()])
507                                    .spawn();
508                            }
509                            h.requested_exit = true;
510                        }
511                        Err(e) => {
512                            eprintln!("Error checking status for PID {}: {}", pid, e);
513                            break;
514                        }
515                    }
516                } else {
517                    eprintln!("Could not lock handle for PID {}", pid);
518                    break;
519                }
520
521                attempts += 1;
522                if did_exit {
523                    break;
524                }
525
526                // Give it a moment before retrying
527                thread::sleep(Duration::from_secs(2));
528            }
529            // Remove the handle so it drops (and on Windows that will kill if still alive)
530            {
531                let mut procs = self.processes.lock().unwrap();
532                if let Some(handle_arc) = procs.remove(&pid) {
533                    let mut handle = handle_arc.lock().unwrap();
534                    let final_diagnostics = {
535                        let diag_lock = handle.diagnostics.lock().unwrap();
536                        diag_lock.clone()
537                    };
538                    handle.result.diagnostics = final_diagnostics.clone();
539
540                    // Ensure `es` is properly defined or assigned
541                    if let Some(exit_status) = handle.child.try_wait().ok().flatten() {
542                        handle.result.exit_status = Some(exit_status);
543                    }
544
545                    handle.result.end_time = Some(SystemTime::now());
546                    if let (Some(start), Some(end)) =
547                        (handle.result.start_time, handle.result.end_time)
548                    {
549                        handle.result.elapsed_time =
550                            Some(end.duration_since(start).unwrap_or_default());
551                    }
552                    self.record_result(handle.result.clone());
553                } else {
554                    eprintln!("No process found with PID: {}", pid);
555                }
556            }
557
558            if did_exit {
559                eprintln!("Process {} removed from map after exit.", pid);
560            } else {
561                eprintln!(
562                    "Dropped handle for PID {} after {} attempts (process may still be running).",
563                    pid, attempts
564                );
565            }
566
567            Ok(did_exit)
568        } else {
569            eprintln!("Process handle with PID {} not found.", pid);
570            Ok(false)
571        }
572    }
573
574    //     /// Attempts to kill the process corresponding to the provided PID.
575    // /// Returns Ok(true) if the process was found and successfully killed
576    // /// (or had already exited), Ok(false) if the process was not found
577    // /// or did not exit within the maximum attempts (in which case we drop
578    // /// the handle), or an error if something went wrong.
579    // pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
580    //     // Grab a clone of the Arc<Mutex<ProcessHandle>> if it exists
581    //     let handle_opt = {
582    //         let processes = self.processes.lock().unwrap();
583    //         processes.get(&pid).cloned()
584    //     };
585
586    //     if let Some(handle) = handle_opt {
587    //         eprintln!("Attempting to kill PID: {}", pid);
588
589    //         let max_attempts = 3;
590    //         let mut attempts = 0;
591    //         let mut process_exited = false;
592
593    //         loop {
594    //             // 1) Check status / send signal
595    //             if let Ok(mut h) = handle.lock() {
596    //                 match h.child.try_wait() {
597    //                     Ok(Some(status)) => {
598    //                         // Already exited
599    //                         eprintln!(
600    //                             "Process {} already exited with status: {:?}",
601    //                             pid, status
602    //                         );
603    //                         process_exited = true;
604    //                         break;
605    //                     }
606    //                     Ok(None) => {
607    //                         // Still running → send signal
608    //                         if attempts == 0 {
609    //                             #[cfg(not(target_os = "windows"))]
610    //                             {
611    //                                 eprintln!("Sending initial Ctrl+C to PID: {}", pid);
612    //                                 crate::e_runall::send_ctrl_c(&mut h.child)?;
613    //                             }
614    //                             #[cfg(target_os = "windows")]
615    //                             {
616    //                                 eprintln!("Sending initial kill to PID: {}", pid);
617    //                             }
618    //                         } else {
619    //                             eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
620    //                         }
621
622    //                         if let Err(e) = h.kill() {
623    //                             eprintln!("Failed to send kill to PID {}: {}", pid, e);
624    //                         }
625    //                         h.requested_exit = true;
626    //                     }
627    //                     Err(e) => {
628    //                         eprintln!("Error checking status for PID {}: {}", pid, e);
629    //                         break;
630    //                     }
631    //                 }
632    //             } else {
633    //                 eprintln!("Could not lock handle for PID {}", pid);
634    //                 break;
635    //             }
636
637    //             attempts += 1;
638    //             if attempts >= max_attempts {
639    //                 eprintln!("Maximum kill attempts reached for PID {}. Dropping handle.", pid);
640    //                 break;
641    //             }
642
643    //             // 2) Wait a bit before re-checking
644    //             thread::sleep(Duration::from_millis(2_000));
645
646    //             // 3) Re-check exit status
647    //             if let Ok(mut h) = handle.lock() {
648    //                 match h.child.try_wait() {
649    //                     Ok(Some(status)) => {
650    //                         eprintln!("Process {} exited with status: {:?}", pid, status);
651    //                         process_exited = true;
652    //                         break;
653    //                     }
654    //                     Ok(None) => {
655    //                         eprintln!("Process {} still running after attempt {}.", pid, attempts);
656    //                     }
657    //                     Err(e) => {
658    //                         eprintln!("Error rechecking process {}: {}", pid, e);
659    //                         break;
660    //                     }
661    //                 }
662    //             }
663    //         }
664
665    //         // Remove the handle (dropping it) whether or not the process exited
666    //         {
667    //             let mut processes = self.processes.lock().unwrap();
668    //             processes.remove(&pid);
669    //         }
670
671    //         if process_exited {
672    //             eprintln!("Process {} removed from map after exit.", pid);
673    //         } else {
674    //             eprintln!(
675    //                 "Dropped handle for PID {} after {} attempts (process may still be running).",
676    //                 pid, attempts
677    //             );
678    //         }
679
680    //         Ok(process_exited)
681    //     } else {
682    //         eprintln!("Process handle with PID {} not found.", pid);
683    //         Ok(false)
684    //     }
685    // }
686
687    /// Attempts to kill one process.
688    /// Returns Ok(true) if a process was found and killed, Ok(false) if none found,
689    /// or an error if something went wrong.
690    pub fn kill_one(&self) -> anyhow::Result<bool> {
691        // First, lock the map briefly to pick one process handle.
692        let maybe_entry = {
693            let processes = self.processes.lock().unwrap();
694            // Clone the Arc so that we don’t take ownership from the map.
695            processes
696                .iter()
697                .next()
698                .map(|(&pid, handle)| (pid, handle.clone()))
699        };
700
701        if let Some((pid, handle)) = maybe_entry {
702            eprintln!("Attempting to kill PID: {}", pid);
703
704            // We'll attempt to kill the process up to `max_attempts` times.
705            let max_attempts = 3;
706            let mut attempts = 0;
707            let mut process_exited = false;
708
709            loop {
710                // Lock the process handle for this iteration.
711                if let Ok(mut h) = handle.lock() {
712                    // Check if the process has already exited.
713                    match h.child.try_wait() {
714                        Ok(Some(status)) => {
715                            eprintln!("Process {} already exited with status: {:?}", pid, status);
716                            process_exited = true;
717                            sleep(Duration::from_millis(3_000));
718                            break;
719                        }
720                        Ok(None) => {
721                            // Process is still running. On the first attempt, or forcefully on later attempts.
722                            if attempts == 0 {
723                                eprintln!("Sending initial kill signal to PID: {}", pid);
724                            } else {
725                                eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
726                            }
727                            sleep(Duration::from_millis(3_000));
728                            // Try to kill the process. Handle errors by printing a debug message.
729                            if let Err(e) = h.kill() {
730                                eprintln!("Failed to send kill signal to PID {}: {}", pid, e);
731                                sleep(Duration::from_millis(3_000));
732                            }
733                        }
734                        Err(e) => {
735                            eprintln!("Error checking exit status for PID {}: {}", pid, e);
736                            sleep(Duration::from_millis(3_000));
737                            break;
738                        }
739                    }
740                } else {
741                    eprintln!("Could not lock process handle for PID {}", pid);
742                    sleep(Duration::from_millis(3_000));
743                    break;
744                }
745
746                attempts += 1;
747                // Allow some time for the process to exit.
748                sleep(Duration::from_millis(3_000));
749
750                // Check again after the sleep.
751                if let Ok(mut h) = handle.lock() {
752                    match h.child.try_wait() {
753                        Ok(Some(status)) => {
754                            eprintln!("Process {} exited with status: {:?}", pid, status);
755                            sleep(Duration::from_millis(3_000));
756                            process_exited = true;
757                            break;
758                        }
759                        Ok(None) => {
760                            eprintln!("Process {} still running after attempt {}.", pid, attempts);
761                            sleep(Duration::from_millis(3_000));
762                        }
763                        Err(e) => {
764                            eprintln!("Error rechecking process {}: {}", pid, e);
765                            sleep(Duration::from_millis(3_000));
766                            break;
767                        }
768                    }
769                }
770
771                if attempts >= max_attempts {
772                    eprintln!("Maximum kill attempts reached for PID {}.", pid);
773                    sleep(Duration::from_millis(3_000));
774                    break;
775                }
776            }
777
778            // 4) In all cases, remove the handle so it drops
779            {
780                let mut ps = self.processes.lock().unwrap();
781                ps.remove(&pid);
782            }
783            if process_exited {
784                eprintln!("Process {} removed from map after exit.", pid);
785            } else {
786                eprintln!(
787                    "Dropped handle for PID {} after {} attempts (process may still be running).",
788                    pid, attempts
789                );
790            }
791            sleep(Duration::from_millis(3_000));
792            Ok(process_exited)
793        } else {
794            println!("No processes to kill.");
795            sleep(Duration::from_millis(3_000));
796            Ok(false)
797        }
798    }
799    // pub fn kill_one(&self) {
800    //     let mut processes = self.processes.lock().unwrap();
801    //     if let Some((&pid, handle)) = processes.iter().next() {
802    //         eprintln!("Killing PID: {}", pid);
803    //         if let Ok(mut h) = handle.lock() {
804    //             let _ = h.kill();
805    //         }
806    //         processes.remove(&pid);
807    //     } else {
808    //         println!("No processes to kill.");
809    //     }
810    // }
811
812    pub fn kill_all(&self) {
813        let mut processes = self.processes.lock().unwrap();
814        for (pid, handle) in processes.drain() {
815            eprintln!("Killing PID: {}", pid);
816            if let Ok(mut h) = handle.lock() {
817                let _ = h.kill();
818            }
819        }
820    }
821
822    // Returns the terminal error for a given PID.
823    pub fn get_terminal_error(&self, pid: u32) -> Option<TerminalError> {
824        // Lock the process map
825        let processes = self.processes.lock().unwrap();
826
827        // Check if the process exists
828        if let Some(handle) = processes.get(&pid) {
829            // Lock the handle to access the terminal error flag
830            let handle = handle.lock().unwrap();
831            // Return the terminal error flag value
832            return Some(handle.terminal_error_flag.lock().unwrap().clone());
833        }
834
835        // If no process is found for the given PID, return None
836        None
837    }
838
839    //     pub fn install_ctrlc_handler(self: Arc<Self>) {
840    //     let manager = Arc::clone(&self);
841    //     ctrlc::set_handler(move || {
842    //         eprintln!("CTRL-C detected. Killing all processes.");
843    //         manager.kill_all();
844    //         std::process::exit(1);
845    //     })
846    //     .expect("Failed to install ctrl-c handler");
847    // }
848
849    /// Wait for the process to finish, show interactive status, then return a result
850    pub fn wait(
851        &self,
852        pid: u32,
853        _duration: Option<Duration>,
854    ) -> anyhow::Result<CargoProcessResult> {
855        // Hide the cursor and ensure it’s restored on exit
856        #[allow(dead_code)]
857        struct CursorHide;
858        impl Drop for CursorHide {
859            fn drop(&mut self) {
860                #[cfg(feature = "tui")]
861                {
862                    let _ = crossterm::execute!(std::io::stdout(), crossterm::cursor::Show);
863                }
864            }
865        }
866        #[cfg(feature = "tui")]
867        let _cursor_hide = {
868            let mut out = std::io::stdout();
869            crossterm::execute!(out, crossterm::cursor::Hide)?;
870            CursorHide
871        };
872
873        // 1. Remove the handle from the map
874        let handle_arc = {
875            let mut map = self.processes.lock().unwrap();
876            map.remove(&pid).ok_or_else(|| {
877                let result = CargoProcessResult {
878                    target_name: String::new(), // Placeholder, should be set properly in actual use
879                    cmd: String::new(),         // Placeholder, should be set properly in actual use
880                    args: Vec::new(),           // Placeholder, should be set properly in actual use
881                    pid,
882                    exit_status: None,
883                    diagnostics: Vec::new(),
884                    start_time: None,
885                    end_time: Some(SystemTime::now()),
886                    elapsed_time: None,
887                    terminal_error: None, // Placeholder, should be set properly in actual use
888                    build_finished_time: None, // Placeholder, should be set properly in actual use
889                    build_elapsed: None,  // Placeholder, should be set properly in actual use
890                    runtime_elapsed: None, // Placeholder, should be set properly in actual use
891                    stats: crate::e_cargocommand_ext::CargoStats::default(), // Provide a default instance of CargoStats
892                    build_output_size: 0,        // Default value set to 0
893                    runtime_output_size: 0, // Placeholder, should be set properly in actual use
894                    is_filter: false,       // Placeholder, should be set properly in actual use
895                    is_could_not_compile: false, // Placeholder, should be set properly in actual use
896                };
897                self.record_result(result.clone());
898                anyhow::anyhow!("Process handle with PID {} not found", pid)
899            })?
900        };
901
902        // 2. Unwrap Arc<Mutex<...>> to get the handle
903        let mut handle = Arc::try_unwrap(handle_arc)
904            .map_err(|_| anyhow::anyhow!("Process handle for PID {} still shared", pid))?
905            .into_inner()
906            .unwrap();
907
908        // 3. Interactive polling loop
909        let mut system = if handle.is_filter {
910            Some(System::new_all())
911        } else {
912            None
913        };
914        const POLL: Duration = Duration::from_secs(1);
915        let mut loop_cnter = 0;
916        loop {
917            loop_cnter += 1;
918            if handle.is_filter && loop_cnter % 2 == 0 {
919                if let Some(ref mut sys) = system {
920                    sys.refresh_all();
921                }
922            }
923
924            if handle.is_filter {
925                if let Some(ref sys) = system {
926                    if let Some(process) = sys.process((pid as usize).into()) {
927                        let status = handle.format_status(Some(process));
928                        if !status.is_empty() {
929                            print!("\r{}", status);
930                        }
931                    }
932                }
933                std::io::stdout().flush().unwrap();
934            }
935
936            if let Some(es) = handle.child.try_wait()? {
937                let final_diagnostics = {
938                    let diag_lock = handle.diagnostics.lock().unwrap();
939                    diag_lock.clone()
940                };
941                handle.result.diagnostics = final_diagnostics.clone();
942                handle.result.exit_status = Some(es);
943                handle.result.end_time = Some(SystemTime::now());
944                if let (Some(start), Some(end)) = (handle.result.start_time, handle.result.end_time)
945                {
946                    handle.result.elapsed_time =
947                        Some(end.duration_since(start).unwrap_or_default());
948                }
949                println!(
950                    "\nProcess with PID {} finished {:?} {}",
951                    pid,
952                    es,
953                    handle.result.diagnostics.len()
954                );
955                break;
956            }
957            std::thread::sleep(POLL);
958        }
959
960        if handle.is_filter {
961            // 4. Extract diagnostics out of Arc<Mutex<_>>
962            let diagnostics = Arc::try_unwrap(handle.diagnostics)
963                .map(|m| m.into_inner().unwrap())
964                .unwrap_or_else(|arc| arc.lock().unwrap().clone());
965
966            // 5. Move them into the final result
967            handle.result.diagnostics = diagnostics;
968        }
969        self.record_result(handle.result.clone());
970        Ok(handle.result)
971    }
972
973    pub fn record_result(&self, result: CargoProcessResult) {
974        let mut results = self.results.lock().unwrap();
975        results.push(result);
976    }
977
978    pub fn generate_report(&self, create_gist: bool) {
979        let results = self.results.lock().unwrap();
980        let report = crate::e_reports::generate_markdown_report(&results);
981        if let Err(e) = crate::e_reports::save_report_to_file(&report, "run_report.md") {
982            eprintln!("Failed to save report: {}", e);
983        }
984        if create_gist {
985            crate::e_reports::create_gist(&report, "run_report.md").unwrap_or_else(|e| {
986                eprintln!("Failed to create Gist: {}", e);
987            });
988        }
989    }
990
991    pub fn format_process_status(
992        pid: u32,
993        start_time: Option<SystemTime>,
994        system: Arc<Mutex<System>>,
995        target: &CargoTarget,
996        target_dimensions: (usize, usize),
997    ) -> String {
998        // let start_dt: chrono::DateTime<Local> =
999        //     start_time.unwrap_or_else(|| SystemTime::UNIX_EPOCH).into();
1000        let start_str = start_time
1001            .map(|st| {
1002                chrono::DateTime::<Local>::from(st)
1003                    .format("%H:%M:%S")
1004                    .to_string()
1005            })
1006            .unwrap_or_else(|| "-".to_string());
1007        let colored_start = nu_ansi_term::Color::LightCyan.paint(&start_str).to_string();
1008        let plain_start = start_str;
1009        if start_time.is_none() {
1010            return String::new();
1011        }
1012        // Refresh the system stats and look up the process.
1013        if let Some(process) = system.lock().unwrap().process((pid as usize).into()) {
1014            let cpu_usage = process.cpu_usage();
1015            let mem_kb = process.memory();
1016            let mem_human = if mem_kb >= 1024 {
1017                format!("{:.2} MB", mem_kb as f64 / 1024.0)
1018            } else {
1019                format!("{} KB", mem_kb)
1020            };
1021
1022            // Calculate runtime.
1023            let now = SystemTime::now();
1024            let runtime_duration = match start_time {
1025                Some(start) => now
1026                    .duration_since(start)
1027                    .unwrap_or_else(|_| Duration::from_secs(0)),
1028                None => Duration::from_secs(0),
1029            };
1030            let runtime_str = crate::e_fmt::format_duration(runtime_duration);
1031            // compute the max number of digits in either dimension:
1032            let max_digits = target_dimensions
1033                .0
1034                .max(target_dimensions.1)
1035                .to_string()
1036                .len();
1037            let left_display = format!(
1038                "{:0width$}of{:0width$} | {} | {} | PID: {} | CPU: {:.2}% | Mem: {}",
1039                target_dimensions.0,
1040                target_dimensions.1,
1041                nu_ansi_term::Color::Green
1042                    .paint(target.display_name.clone())
1043                    .to_string(),
1044                colored_start,
1045                pid,
1046                cpu_usage,
1047                mem_human,
1048                width = max_digits,
1049            );
1050            let left_plain = format!(
1051                "{:0width$}of{:0width$} | {} | {} | PID: {} | CPU: {:.2}% | Mem: {}",
1052                target_dimensions.0,
1053                target_dimensions.1,
1054                target.display_name,
1055                plain_start,
1056                pid,
1057                cpu_usage,
1058                mem_human,
1059                width = max_digits,
1060            );
1061
1062            // Get terminal width.
1063            #[cfg(feature = "tui")]
1064            let (cols, _) = crossterm::terminal::size().unwrap_or((80, 20));
1065            #[cfg(not(feature = "tui"))]
1066            let (cols, _) = (80, 20);
1067            let total_width = cols as usize;
1068
1069            // Format the runtime with underlining.
1070            let right_display = nu_ansi_term::Style::new()
1071                .reset_before_style()
1072                .underline()
1073                .paint(&runtime_str)
1074                .to_string();
1075            let left_len = left_plain.len();
1076            let right_len = runtime_str.len();
1077            let padding = if total_width > left_len + right_len {
1078                total_width - left_len - right_len
1079            } else {
1080                1
1081            };
1082
1083            format!("{}{}{}", left_display, " ".repeat(padding), right_display)
1084        } else {
1085            // String::from("xxx")
1086            format!("\rProcess with PID {} not found in sysinfo", pid)
1087        }
1088    }
1089
1090    /// Print the exact diagnostic output as captured.
1091    pub fn print_exact_output(&self) {
1092        let processes = self.processes.lock().unwrap();
1093        for handle in processes.iter() {
1094            println!("--- Full Diagnostics for PID {} ---", handle.0);
1095            let handle_lock = handle.1.lock().unwrap();
1096            let diags = handle_lock.diagnostics.lock().unwrap();
1097            for diag in diags.iter() {
1098                // Print the entire diagnostic.
1099                println!("{:?}: {}", diag.level, diag.message);
1100            }
1101        }
1102    }
1103
1104    /// Print a one‑line summary per warning, numbered with leading zeros.
1105    pub fn print_prefixed_summary(&self) {
1106        // 1. Grab a snapshot of the handles (Arc clones) under the manager lock.
1107        let guard = self.processes.lock().unwrap();
1108        let handles: Vec<_> = guard.iter().map(|h| h.clone()).collect();
1109
1110        // 2. Now we can iterate without holding the manager lock.
1111        for handle in handles {
1112            // Lock only the diagnostics for this handle.
1113            let handle_lock = handle.1.lock().unwrap();
1114            let diags = handle_lock.diagnostics.lock().unwrap();
1115
1116            // Collect warnings.
1117            let warnings: Vec<_> = diags.iter().filter(|d| d.level.eq("warning")).collect();
1118
1119            // Determine width for zero-padding for warnings.
1120            let warning_width = warnings.len().to_string().len().max(1);
1121            println!(
1122                "\n\n--- Warnings for PID {} --- {} {}",
1123                handle.0,
1124                warning_width,
1125                warnings.len()
1126            );
1127
1128            for (i, diag) in warnings.iter().enumerate() {
1129                // Format the index with leading zeros for warnings.
1130                let index = format!("{:0width$}", i + 1, width = warning_width);
1131                // Print the warning with the index.
1132                println!("{}: {}", index, diag.message.trim());
1133            }
1134
1135            // Collect errors.
1136            let errors: Vec<_> = diags.iter().filter(|d| d.level.eq("error")).collect();
1137
1138            // Determine width for zero-padding for errors.
1139            let error_width = errors.len().to_string().len().max(1);
1140            println!(
1141                "\n\n--- Errors for PID {} --- {} {}",
1142                handle.0,
1143                error_width,
1144                errors.len()
1145            );
1146
1147            for (i, diag) in errors.iter().enumerate() {
1148                // Format the index with leading zeros for errors.
1149                let index = format!("{:0width$}", i + 1, width = error_width);
1150                // Print the error with the index.
1151                println!("{}: {}", index, diag.message.trim());
1152            }
1153        }
1154    }
1155
1156    /// file:line:col – source_line, colored by level.
1157    pub fn print_compact(&self) {
1158        let processes = self.processes.lock().unwrap();
1159        for handle in processes.iter() {
1160            println!("--- Compact for PID {} ---", handle.0);
1161            let handle_lock = handle.1.lock().unwrap();
1162            let diags = handle_lock.diagnostics.lock().unwrap();
1163            for diag in diags.iter() {
1164                println!("{}: {} {}", diag.level, diag.lineref, diag.message.trim());
1165            }
1166        }
1167    }
1168    /// Print a shortened version: warnings first then errors.
1169    pub fn print_shortened_output(&self) {
1170        let processes = self.processes.lock().unwrap();
1171        for handle in processes.iter() {
1172            println!("\n\n\n--- Summary for PID {} ---", handle.0);
1173            let handle_lock = handle.1.lock().unwrap();
1174            let diags = handle_lock.diagnostics.lock().unwrap();
1175
1176            // Filter diagnostics for warnings and errors.
1177            let warnings: Vec<_> = diags.iter().filter(|d| d.level.eq("warning")).collect();
1178            let errors: Vec<_> = diags.iter().filter(|d| d.level.eq("error")).collect();
1179
1180            // Print warnings.
1181            if !warnings.is_empty() {
1182                println!("Warnings:");
1183                for diag in warnings {
1184                    println!("print_shortened_output:{}", diag.message.trim());
1185                }
1186            } else {
1187                println!("No warnings.");
1188            }
1189
1190            // Print errors.
1191            if !errors.is_empty() {
1192                println!("Errors:");
1193                for diag in errors {
1194                    println!("print_shortened_output: {}", diag.message.trim());
1195                }
1196            } else {
1197                println!("No errors.");
1198            }
1199        }
1200    }
1201}
1202
1203// #[cfg(feature = "uses_async")]
1204// impl ProcessManager {
1205//     pub async fn wait_for_processes(&self) {
1206//         loop {
1207//             {
1208//                 if self.processes.lock().unwrap().is_empty() {
1209//                     break;
1210//                 }
1211//             }
1212//             self.notifier.notified().await;
1213//         }
1214//     }
1215// }
1216
1217pub struct CursorGuard;
1218
1219impl Drop for CursorGuard {
1220    fn drop(&mut self) {
1221        #[cfg(feature = "tui")]
1222        {
1223            let mut stdout = std::io::stdout();
1224            let _ = crossterm::execute!(stdout, crossterm::cursor::Show);
1225        }
1226    }
1227}