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