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