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