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}