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, GLOBAL_MANAGER};
7use chrono::Local;
8use std::process::ExitStatus;
9use std::sync::atomic::AtomicUsize;
10use std::sync::mpsc::{self, Receiver, Sender};
11use std::sync::{Arc, Mutex};
12use std::thread::{self, sleep};
13use std::time::{Duration, SystemTime};
14use sysinfo::System;
15// use std::io::Write;
16#[cfg(feature = "tui")]
17use crossterm::{
18 cursor, execute,
19 terminal::{Clear, ClearType},
20};
21use std::io::{self, Write};
22use std::sync::atomic::Ordering;
23use std::sync::Mutex as StdMutex;
24#[cfg(target_os = "windows")]
25use windows::Win32::Foundation::CloseHandle;
26#[cfg(target_os = "windows")]
27use windows::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION};
28#[cfg(unix)]
29use {
30 nix::sys::signal::{kill as nix_kill, Signal},
31 nix::unistd::Pid,
32 std::os::unix::process::ExitStatusExt,
33};
34
35impl ProcessObserver for ProcessManager {
36 fn on_spawn(&self, pid: u32, handle: CargoProcessHandle) {
37 self.processes.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: dashmap::DashMap<u32, Arc<Mutex<CargoProcessHandle>>>,
96 results: dashmap::DashMap<u32, 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.len();
107 let results_len = self.results.len();
108 let signalled_count = self.signalled_count.load(Ordering::SeqCst);
109 f.debug_struct("ProcessManager")
110 .field("signalled_count", &signalled_count)
111 .field("signal_tx", &"Sender<()>")
112 .field("processes.len", &processes_len)
113 .field("results.len", &results_len)
114 .finish()
115 }
116}
117
118impl ProcessManager {
119 pub fn new(_cli: &Cli) -> Arc<Self> {
120 let (tx, rx) = mpsc::channel();
121 let manager = Arc::new(Self {
122 signalled_count: AtomicUsize::new(0),
123 signal_tx: tx.clone(),
124 processes: dashmap::DashMap::new(),
125 results: dashmap::DashMap::new(),
126 signal_times: SignalTimes::new(),
127 });
128 ProcessManager::install_handler(Arc::clone(&manager), rx);
129 crate::GLOBAL_MANAGER.get_or_init(|| Arc::clone(&manager));
130 crate::GLOBAL_EWINDOW_PIDS.get_or_init(|| dashmap::DashMap::new());
131 manager
132 }
133 pub fn cleanup(&self) {
134 // eprintln!("[ProcessManager::drop] Dropping ProcessManager and cleaning up processes.");
135 // Try to kill all managed processes.
136 for entry in self.processes.iter() {
137 // let pid = *entry.key();
138 // eprintln!("[ProcessManager::drop] Attempting to kill PID {}", pid);
139 if let Ok(mut handle) = entry.value().try_lock() {
140 let _ = handle.kill();
141 }
142 // If you want to avoid locking, you would need to redesign CargoProcessHandle to allow lock-free signaling.
143 }
144 self.e_window_kill_all();
145 }
146
147 pub fn last_signal_time(&self) -> Option<SystemTime> {
148 self.signal_times.last_signal_time()
149 }
150 pub fn time_between_signals(&self) -> Option<Duration> {
151 self.signal_times.time_between_signals()
152 }
153 pub fn reset_signalled(&self) {
154 self.signalled_count.store(0, Ordering::SeqCst);
155 }
156 pub fn has_signalled(&self) -> usize {
157 self.signalled_count.load(Ordering::SeqCst)
158 }
159
160 fn install_handler(self_: Arc<Self>, rx: Receiver<()>) {
161 match ctrlc::set_handler({
162 let tx = self_.signal_tx.clone();
163 move || {
164 let _ = tx.send(());
165 }
166 }) {
167 Ok(_) => {
168 thread::spawn(move || {
169 while rx.recv().is_ok() {
170 self_.signalled_count.fetch_add(1, Ordering::SeqCst);
171 println!(
172 "ctrlc> signal received. {}",
173 self_.signalled_count.load(Ordering::SeqCst)
174 );
175 // Record the signal time and check if two signals were received in quick succession
176 self_.signal_times.record_signal();
177 if let Some(duration) = self_.signal_times.time_between_signals() {
178 // If the last two signals were within .5 seconds, exit immediately
179 if duration < Duration::from_millis(500) {
180 println!("ctrlc> Received two signals in quick succession, exiting immediately.");
181 std::process::exit(1);
182 }
183 }
184 self_.handle_signal();
185 }
186 });
187 }
188 Err(e) => {
189 eprintln!("ctrlc> Failed to install Ctrl+C handler: {}", e);
190 return;
191 }
192 }
193 }
194
195 fn handle_signal(&self) {
196 self.signal_times.record_signal();
197 println!("ctrlc>");
198 let processes: Vec<_> = self
199 .processes
200 .iter()
201 .map(|entry| (*entry.key(), entry.value().clone()))
202 .collect();
203 for (pid, handle) in processes {
204 if let Ok(mut h) = handle.try_lock() {
205 if h.removed {
206 continue;
207 }
208
209 println!("ctrlc> Terminating process with PID: {}", pid);
210 let _ = h.kill();
211 h.removed = true;
212 let diag_lock = match h.diagnostics.try_lock() {
213 Ok(lock) => lock.clone(),
214 Err(e) => {
215 eprintln!("Failed to acquire diagnostics lock for PID {}: {}", pid, e);
216 Vec::new()
217 }
218 };
219 h.result.diagnostics = diag_lock.clone();
220
221 if let Some(exit_status) = h.child.try_wait().ok().flatten() {
222 h.result.exit_status = Some(exit_status);
223 }
224
225 h.result.end_time = Some(SystemTime::now());
226 if let (Some(start), Some(end)) = (h.result.start_time, h.result.end_time) {
227 h.result.elapsed_time = Some(end.duration_since(start).unwrap_or_default());
228 }
229 self.record_result(h.result.clone());
230 if let Some(manager) = GLOBAL_MANAGER.get() {
231 manager.e_window_kill_all();
232 }
233 } else {
234 eprintln!("Failed to acquire lock for PID {}", pid);
235 }
236 //self.processes.remove(&pid);
237 }
238 }
239
240 /// Updates the status line in the terminal.
241 /// When `raw_mode` is true, it uses crossterm commands to clear the current line.
242 /// Otherwise, it uses the carriage return (`\r`) trick.
243 pub fn update_status_line(output: &str, raw_mode: bool) -> io::Result<()> {
244 let mut stdout = io::stdout();
245 if raw_mode {
246 // Move cursor to beginning and clear the current line.
247 #[cfg(feature = "tui")]
248 {
249 execute!(
250 stdout,
251 cursor::MoveToColumn(0),
252 Clear(ClearType::CurrentLine)
253 )?;
254 print!("\r{}\r", output);
255 }
256 #[cfg(not(feature = "tui"))]
257 print!("\r{}\r", output);
258 } else {
259 // In non-raw mode, the \r trick can work.
260 print!("\r{}\r", output);
261 }
262 stdout.flush()
263 }
264
265 pub fn register(&self, handle: CargoProcessHandle) -> u32 {
266 let pid = handle.pid;
267 self.processes.insert(pid, Arc::new(Mutex::new(handle)));
268 pid
269 }
270
271 pub fn take(&self, pid: u32) -> Option<Arc<Mutex<CargoProcessHandle>>> {
272 // self.processes.remove(&pid).map(|(_, handle)| handle)
273 self.processes.get(&pid).map(|entry| entry.clone())
274 }
275
276 pub fn remove(&self, pid: u32) {
277 println!(
278 "[ProcessManager::remove] Removing process with PID: {}",
279 pid
280 );
281 if let Some(handle_arc) = self.processes.get(&pid) {
282 match handle_arc.try_lock() {
283 Ok(mut h) => {
284 h.removed = true;
285 let final_diagnostics = {
286 let diag_lock = match h.diagnostics.try_lock() {
287 Ok(lock) => lock.clone(),
288 Err(_) => Vec::new(),
289 };
290 diag_lock
291 };
292 h.result.diagnostics = final_diagnostics.clone();
293
294 if let Some(exit_status) = h.child.try_wait().ok().flatten() {
295 h.result.exit_status = Some(exit_status);
296 }
297
298 h.result.end_time = Some(SystemTime::now());
299 if let (Some(start), Some(end)) = (h.result.start_time, h.result.end_time) {
300 h.result.elapsed_time = Some(end.duration_since(start).unwrap_or_default());
301 }
302 h.result.pid = pid;
303 self.record_result(h.result.clone());
304 drop(h);
305 }
306 Err(e) => {
307 eprintln!("Failed to acquire lock for PID {}: {}", pid, e);
308 }
309 }
310 }
311 }
312
313 pub fn try_wait(&self, pid: u32) -> anyhow::Result<Option<ExitStatus>> {
314 // 1. Lock the processes map just long enough to clone the Arc.
315 let handle_arc = {
316 // Use DashMap's get method directly (no lock needed)
317 self.processes
318 .get(&pid)
319 .map(|entry| entry.clone())
320 .ok_or_else(|| anyhow::anyhow!("Process handle with PID {} not found", pid))?
321 };
322
323 // 2. Lock the individual process handle to perform try_wait.
324 let mut handle = match handle_arc.try_lock() {
325 Ok(h) => h,
326 Err(e) => {
327 return Err(anyhow::anyhow!(
328 "Failed to acquire process handle lock for PID {}: {}",
329 pid,
330 e
331 ))
332 }
333 };
334 // Here, try_wait returns a Result<Option<ExitStatus>, std::io::Error>.
335 // The '?' operator will convert any std::io::Error to anyhow::Error automatically.
336 let status = handle.child.try_wait()?;
337 if let Some(status) = status {
338 if status.success() || status.code().is_some() {
339 handle.removed = true;
340 }
341 }
342 drop(handle);
343 // Return the exit status (or None) wrapped in Ok.
344 Ok(status)
345 }
346
347 pub fn get(&self, pid: u32) -> Option<Arc<Mutex<CargoProcessHandle>>> {
348 self.processes.get(&pid).map(|entry| entry.clone())
349 }
350
351 pub fn is_alive(&self, pid: u32) -> bool {
352 // Cross-platform check if a PID is still running
353 #[cfg(unix)]
354 {
355 // On Unix, sending signal 0 checks if the process exists
356 unsafe { libc::kill(pid as i32, 0) == 0 }
357 }
358
359 #[cfg(windows)]
360 {
361 // Use the windows crate to check if the process exists.
362
363 unsafe {
364 match OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) {
365 Ok(handle) => {
366 if !handle.is_invalid() {
367 let _ = CloseHandle(handle);
368 return true;
369 } else {
370 return false;
371 }
372 }
373 Err(_) => return false,
374 }
375 }
376 }
377 // if let Some(handle_arc) = self.processes.get(&pid) {
378 // let handle = handle_arc.lock().unwrap();
379 // !handle.removed
380 // } else {
381 // false
382 // }
383 }
384
385 pub fn list(&self) -> Vec<u32> {
386 self.processes.iter().map(|entry| *entry.key()).collect()
387 }
388
389 pub fn status(&self) {
390 if self.processes.is_empty() {
391 println!("No active cargo processes.");
392 } else {
393 println!("Active processes:");
394 for pid in self.processes.iter().map(|entry| *entry.key()) {
395 println!(" - PID: {}", pid);
396 }
397 }
398 }
399
400 // /// Attempts to kill the process corresponding to the provided PID.
401 // /// Returns Ok(true) if the process was found and successfully killed (or had already exited),
402 // /// Ok(false) if the process was not found or did not exit within the maximum attempts,
403 // /// or an error if something went wrong.
404 // pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
405 // // Retrieve a clone of the handle (Arc) for the given PID without removing it.
406 // let handle = {
407 // let processes = self.processes.lock().unwrap();
408 // processes.get(&pid).cloned()
409 // };
410
411 // if let Some(handle) = handle {
412 // eprintln!("Attempting to kill PID: {}", pid);
413
414 // let max_attempts = 3;
415 // let mut attempts = 0;
416 // let mut process_exited = false;
417
418 // loop {
419 // // Lock the process handle for this iteration.
420 // if let Ok(mut h) = handle.lock() {
421 // // Check if the process has already exited.
422 // match h.child.try_wait() {
423 // Ok(Some(status)) => {
424 // eprintln!("Process {} already exited with status: {:?}", pid, status);
425 // process_exited = true;
426 // break;
427 // }
428 // Ok(None) => {
429 // // Process is still running.
430 // if attempts == 0 {
431 // #[cfg(not(target_os = "windows"))] {
432 // eprintln!("Sending initial Ctrl+C signal to PID: {}", pid);
433 // crate::e_runall::send_ctrl_c(&mut h.child)?;
434 // }
435 // #[cfg(target_os = "windows")] {
436 // eprintln!("Sending initial kill signal to PID: {}", pid);
437 // }
438 // } else {
439 // eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
440 // }
441 // // Attempt to kill the process.
442 // if let Err(e) = h.kill() {
443 // eprintln!("Failed to send kill signal to PID {}: {}", pid, e);
444 // }
445 // // Mark that an exit was requested.
446 // h.requested_exit = true;
447 // }
448 // Err(e) => {
449 // eprintln!("Error checking exit status for PID {}: {}", pid, e);
450 // break;
451 // }
452 // }
453 // } else {
454 // eprintln!("Could not lock process handle for PID {}", pid);
455 // break;
456 // }
457
458 // attempts += 1;
459 // // Allow some time for the process to exit.
460 // sleep(Duration::from_millis(2000));
461
462 // // Re-check if the process has exited.
463 // if let Ok(mut h) = handle.lock() {
464 // match h.child.try_wait() {
465 // Ok(Some(status)) => {
466 // eprintln!("Process {} exited with status: {:?}", pid, status);
467 // process_exited = true;
468 // break;
469 // }
470 // Ok(None) => {
471 // eprintln!("Process {} still running after attempt {}.", pid, attempts);
472 // }
473 // Err(e) => {
474 // eprintln!("Error rechecking process {}: {}", pid, e);
475 // break;
476 // }
477 // }
478 // }
479
480 // if attempts >= max_attempts {
481 // eprintln!("Maximum kill attempts reached for PID {}.", pid);
482 // break;
483 // }
484 // }
485
486 // // If the process exited, remove it from the map.
487 // if process_exited {
488 // let mut processes = self.processes.lock().unwrap();
489 // processes.remove(&pid);
490 // eprintln!("Process {} removed from map after exit.", pid);
491 // } else {
492 // eprintln!(
493 // "Process {} did not exit after {} attempts; it remains in the map for future handling.",
494 // pid, attempts
495 // );
496 // }
497 // Ok(process_exited)
498 // } else {
499 // eprintln!("Process handle with PID {} not found.", pid);
500 // Ok(false)
501 // }
502 // }
503
504 /// Attempts to kill the process corresponding to the provided PID.
505 /// Returns Ok(true) if the process was found and exited (even via signal),
506 /// Ok(false) if the process wasn’t found or didn’t exit after trying
507 /// all signals (in which case we drop the handle), or Err if something went wrong.
508 pub fn kill_try_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
509 // Grab the handle, if any.
510 let handle_opt = { self.processes.get(&pid).map(|entry| entry.clone()) };
511 if let Some(handle) = handle_opt {
512 eprintln!("Attempting to kill PID: {}", pid);
513 #[cfg(unix)]
514 let signals = [
515 Signal::SIGHUP,
516 Signal::SIGINT,
517 Signal::SIGQUIT,
518 Signal::SIGABRT,
519 Signal::SIGKILL,
520 Signal::SIGALRM,
521 Signal::SIGTERM,
522 ];
523 #[cfg(unix)]
524 let max_attempts = signals.len();
525
526 #[cfg(windows)]
527 let max_attempts = 3; // arbitrary, since Child::kill() is always SIGKILL
528
529 let mut attempts = 0;
530 let mut did_exit = false;
531
532 while attempts < max_attempts {
533 // 1) Check status
534 if let Ok(mut h) = handle.try_lock() {
535 match h.child.try_wait() {
536 Ok(Some(status)) => {
537 // Child has exited—report how.
538 #[cfg(unix)]
539 {
540 if let Some(sig) = status.signal() {
541 eprintln!("Process {} terminated by signal {}", pid, sig);
542 } else if let Some(code) = status.code() {
543 eprintln!("Process {} exited with code {}", pid, code);
544 } else {
545 eprintln!(
546 "Process {} exited with unknown status: {:?}",
547 pid, status
548 );
549 }
550 }
551 #[cfg(not(unix))]
552 {
553 if let Some(code) = status.code() {
554 eprintln!("Process {} exited with code {}", pid, code);
555 } else {
556 eprintln!(
557 "Process {} exited with unknown status: {:?}",
558 pid, status
559 );
560 }
561 }
562 did_exit = true;
563 break;
564 }
565 Ok(None) => {
566 // Still running → send the next signal
567 #[cfg(unix)]
568 {
569 let sig = signals[attempts];
570 eprintln!(
571 "Attempt {}: sending {:?} to PID {}",
572 attempts + 1,
573 sig,
574 pid
575 );
576 nix_kill(Pid::from_raw(pid as i32), sig)?;
577 }
578 #[cfg(windows)]
579 {
580 // // Remove the handle so it drops (and on Windows that will kill if still alive)
581 // {
582 // let mut procs = self.processes.lock().unwrap();
583 // procs.remove(&pid);
584 // }
585 // On Windows, try to kill child processes first before killing the main process.
586 let mut sys = System::new_all();
587 sys.refresh_all();
588 // let parent_pid = sysinfo::Pid::from(pid as usize);
589
590 // // Helper function to recursively collect all descendant PIDs
591 // fn collect_descendants(
592 // sys: &System,
593 // parent: sysinfo::Pid,
594 // descendants: &mut Vec<sysinfo::Pid>,
595 // ) {
596 // let children: Vec<_> = sys
597 // .processes()
598 // .values()
599 // .filter(|p| p.parent() == Some(parent))
600 // .map(|p| p.pid())
601 // .collect();
602 // for child_pid in &children {
603 // descendants.push(*child_pid);
604 // collect_descendants(sys, *child_pid, descendants);
605 // }
606 // }
607
608 // let mut descendants = Vec::new();
609 // collect_descendants(&sys, parent_pid, &mut descendants);
610
611 // for &child_pid in &descendants {
612 // eprintln!("Attempting to kill descendant PID {} of parent PID {}", child_pid, pid);
613 // let _ = std::process::Command::new("taskkill")
614 // .args(["/F", "/PID", &child_pid.to_string()])
615 // .spawn();
616 // self.e_window_kill(child_pid.as_u32());
617 // }
618 // Only attempt to kill if the child is still alive
619 match h.child.try_wait() {
620 Ok(None) => {
621 eprintln!("Attempt {}: killing PID {}", attempts + 1, pid);
622 if let Err(e) = h.child.kill() {
623 eprintln!("Failed to kill PID {}: {}", pid, e);
624 }
625 // Only call taskkill if the process is still running
626 if h.child.try_wait()?.is_none() {
627 _ = std::process::Command::new("taskkill")
628 .args(["/F", "/PID", &pid.to_string()])
629 .spawn();
630 }
631 }
632 Ok(Some(status)) => {
633 eprintln!(
634 "PID {} already exited with status: {:?}",
635 pid, status
636 );
637 }
638 Err(e) => {
639 eprintln!("Error checking status for PID {}: {}", pid, e);
640 }
641 }
642 }
643 h.requested_exit = true;
644 }
645 Err(e) => {
646 eprintln!("Error checking status for PID {}: {}", pid, e);
647 break;
648 }
649 }
650 } else {
651 eprintln!("Could not lock handle for PID {}", pid);
652
653 break;
654 }
655
656 attempts += 1;
657 if did_exit {
658 break;
659 }
660 }
661 eprintln!(
662 "Reference count for PID {} before lock: {}",
663 pid,
664 Arc::strong_count(&handle)
665 );
666
667 // println!("doing e_window cleanup for PID {}", pid);
668 // // Handle global e_window_pids for this PID
669 // if let Some(global) = crate::GLOBAL_EWINDOW_PIDS.get() {
670 // if let Some(e_window_pid_ref) = global.get(&pid) {
671 // let e_window_pid = *e_window_pid_ref.value();
672 // eprintln!(
673 // "[DEBUG] Killing global e_window PID {} for parent PID {}",
674 // e_window_pid, pid
675 // );
676 // let _ = std::process::Command::new("taskkill")
677 // .args(["/F", "/PID", &format!("{}", e_window_pid)])
678 // .spawn();
679 // // Drop the reference before removing to avoid blocking
680 // drop(e_window_pid_ref);
681 // global.remove(&pid);
682 // } else {
683 // eprintln!("[DEBUG] No global e_window PID found for parent PID {}", pid);
684 // }
685 // }
686
687 // Remove the handle so it drops (and on Windows that will kill if still alive)
688 // if let Some((_, handle_arc)) = self.processes.remove(&pid) {
689 if did_exit {
690 eprintln!("Process {} removed from map after exit.", pid);
691 } else {
692 eprintln!(
693 "Dropped handle for PID {} after {} attempts (process may still be running).",
694 pid, attempts
695 );
696 }
697
698 Ok(did_exit)
699 } else {
700 eprintln!("Process handle with PID {} not found.", pid);
701 Ok(false)
702 }
703 }
704
705 pub fn e_window_kill(&self, pid: u32) {
706 // Ensure this function is only executed on the main thread
707 // if !std::thread::current().name().map_or(false, |name| name == "main") {
708 // eprintln!("[DEBUG] Skipping e_window_kill for PID {} as it is not running on the main thread", pid);
709 // return;
710 // }
711
712 // Try to get the e_window PID for this process from GLOBAL_EWINDOW_PIDS
713 if let Some(global) = crate::GLOBAL_EWINDOW_PIDS.get() {
714 eprintln!(
715 "[DEBUG] Searching for e_window PID for parent PID {} in map: {:?}",
716 pid, global
717 );
718
719 // Extract the e_window_pid and drop the reference to avoid deadlocks
720 if let Some(e_window_pid) = global.get(&pid).map(|entry| *entry.value()) {
721 eprintln!(
722 "[DEBUG] Killing e_window PID {} for parent PID {}",
723 e_window_pid, pid
724 );
725
726 // Check if the process is still running before attempting to kill it
727 let mut sys = sysinfo::System::new_all();
728 sys.refresh_all();
729 if sys
730 .process(sysinfo::Pid::from(e_window_pid as usize))
731 .is_some()
732 {
733 let _ = std::process::Command::new("taskkill")
734 .args(["/F", "/PID", &format!("{}", e_window_pid)])
735 .spawn();
736
737 eprintln!("[DEBUG] Successfully killed e_window PID {}", e_window_pid);
738 } else {
739 eprintln!("[DEBUG] e_window PID {} is not running", e_window_pid);
740 }
741
742 // Remove the entry after handling the PID
743 global.remove(&pid);
744 eprintln!("[DEBUG] Removed e_window PID {} from map", e_window_pid);
745 } else {
746 eprintln!("[DEBUG] No e_window PID found for parent PID {}", pid);
747 }
748 } else {
749 eprintln!("[DEBUG] GLOBAL_EWINDOW_PIDS is not initialized or empty");
750 }
751 }
752 /// Kill all e_window processes tracked in GLOBAL_EWINDOW_PIDS.
753 pub fn e_window_kill_all(&self) {
754 if let Some(global) = crate::GLOBAL_EWINDOW_PIDS.get() {
755 // Collect the PIDs first to avoid mutating while iterating
756 let pids: Vec<u32> = global.iter().map(|entry| *entry.key()).collect();
757 for pid in pids {
758 self.e_window_kill(pid);
759 }
760 }
761 }
762 // /// Attempts to kill the process corresponding to the provided PID.
763 // /// Returns Ok(true) if the process was found and successfully killed
764 // /// (or had already exited), Ok(false) if the process was not found
765 // /// or did not exit within the maximum attempts (in which case we drop
766 // /// the handle), or an error if something went wrong.
767 // pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
768 // // Grab a clone of the Arc<Mutex<ProcessHandle>> if it exists
769 // let handle_opt = {
770 // let processes = self.processes.lock().unwrap();
771 // processes.get(&pid).cloned()
772 // };
773
774 // if let Some(handle) = handle_opt {
775 // eprintln!("Attempting to kill PID: {}", pid);
776
777 // let max_attempts = 3;
778 // let mut attempts = 0;
779 // let mut process_exited = false;
780
781 // loop {
782 // // 1) Check status / send signal
783 // if let Ok(mut h) = handle.lock() {
784 // match h.child.try_wait() {
785 // Ok(Some(status)) => {
786 // // Already exited
787 // eprintln!(
788 // "Process {} already exited with status: {:?}",
789 // pid, status
790 // );
791 // process_exited = true;
792 // break;
793 // }
794 // Ok(None) => {
795 // // Still running → send signal
796 // if attempts == 0 {
797 // #[cfg(not(target_os = "windows"))]
798 // {
799 // eprintln!("Sending initial Ctrl+C to PID: {}", pid);
800 // crate::e_runall::send_ctrl_c(&mut h.child)?;
801 // }
802 // #[cfg(target_os = "windows")]
803 // {
804 // eprintln!("Sending initial kill to PID: {}", pid);
805 // }
806 // } else {
807 // eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
808 // }
809
810 // if let Err(e) = h.kill() {
811 // eprintln!("Failed to send kill to PID {}: {}", pid, e);
812 // }
813 // h.requested_exit = true;
814 // }
815 // Err(e) => {
816 // eprintln!("Error checking status for PID {}: {}", pid, e);
817 // break;
818 // }
819 // }
820 // } else {
821 // eprintln!("Could not lock handle for PID {}", pid);
822 // break;
823 // }
824
825 // attempts += 1;
826 // if attempts >= max_attempts {
827 // eprintln!("Maximum kill attempts reached for PID {}. Dropping handle.", pid);
828 // break;
829 // }
830
831 // // 2) Wait a bit before re-checking
832 // thread::sleep(Duration::from_millis(2_000));
833
834 // // 3) Re-check exit status
835 // if let Ok(mut h) = handle.lock() {
836 // match h.child.try_wait() {
837 // Ok(Some(status)) => {
838 // eprintln!("Process {} exited with status: {:?}", pid, status);
839 // process_exited = true;
840 // break;
841 // }
842 // Ok(None) => {
843 // eprintln!("Process {} still running after attempt {}.", pid, attempts);
844 // }
845 // Err(e) => {
846 // eprintln!("Error rechecking process {}: {}", pid, e);
847 // break;
848 // }
849 // }
850 // }
851 // }
852
853 // // Remove the handle (dropping it) whether or not the process exited
854 // {
855 // let mut processes = self.processes.lock().unwrap();
856 // processes.remove(&pid);
857 // }
858
859 // if process_exited {
860 // eprintln!("Process {} removed from map after exit.", pid);
861 // } else {
862 // eprintln!(
863 // "Dropped handle for PID {} after {} attempts (process may still be running).",
864 // pid, attempts
865 // );
866 // }
867
868 // Ok(process_exited)
869 // } else {
870 // eprintln!("Process handle with PID {} not found.", pid);
871 // Ok(false)
872 // }
873 // }
874
875 /// Attempts to kill one process.
876 /// Returns Ok(true) if a process was found and killed, Ok(false) if none found,
877 /// or an error if something went wrong.
878 pub fn kill_one(&self) -> anyhow::Result<bool> {
879 // Pick one process handle from DashMap (no lock needed).
880 let maybe_entry = {
881 self.processes
882 .iter()
883 .next()
884 .map(|entry| (*entry.key(), entry.value().clone()))
885 };
886
887 if let Some((pid, handle)) = maybe_entry {
888 eprintln!("Attempting to kill PID: {}", pid);
889
890 // We'll attempt to kill the process up to `max_attempts` times.
891 let max_attempts = 3;
892 let mut attempts = 0;
893 let mut process_exited = false;
894
895 loop {
896 // Lock the process handle for this iteration.
897 if let Ok(mut h) = handle.lock() {
898 // Check if the process has already exited.
899 match h.child.try_wait() {
900 Ok(Some(status)) => {
901 eprintln!("Process {} already exited with status: {:?}", pid, status);
902 process_exited = true;
903 sleep(Duration::from_millis(3_000));
904 break;
905 }
906 Ok(None) => {
907 // Process is still running. On the first attempt, or forcefully on later attempts.
908 if attempts == 0 {
909 eprintln!("Sending initial kill signal to PID: {}", pid);
910 } else {
911 eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
912 }
913 sleep(Duration::from_millis(3_000));
914 // Try to kill the process. Handle errors by printing a debug message.
915 if let Err(e) = h.kill() {
916 eprintln!("Failed to send kill signal to PID {}: {}", pid, e);
917 sleep(Duration::from_millis(3_000));
918 }
919 }
920 Err(e) => {
921 eprintln!("Error checking exit status for PID {}: {}", pid, e);
922 sleep(Duration::from_millis(3_000));
923 break;
924 }
925 }
926 } else {
927 eprintln!("Could not lock process handle for PID {}", pid);
928 sleep(Duration::from_millis(3_000));
929 break;
930 }
931
932 attempts += 1;
933 // Allow some time for the process to exit.
934 sleep(Duration::from_millis(3_000));
935
936 // Check again after the sleep.
937 if let Ok(mut h) = handle.lock() {
938 match h.child.try_wait() {
939 Ok(Some(status)) => {
940 eprintln!("Process {} exited with status: {:?}", pid, status);
941 sleep(Duration::from_millis(3_000));
942 process_exited = true;
943 break;
944 }
945 Ok(None) => {
946 eprintln!("Process {} still running after attempt {}.", pid, attempts);
947 sleep(Duration::from_millis(3_000));
948 }
949 Err(e) => {
950 eprintln!("Error rechecking process {}: {}", pid, e);
951 sleep(Duration::from_millis(3_000));
952 break;
953 }
954 }
955 }
956
957 if attempts >= max_attempts {
958 eprintln!("Maximum kill attempts reached for PID {}.", pid);
959 sleep(Duration::from_millis(3_000));
960 break;
961 }
962 }
963
964 // 4) In all cases, remove the handle so it drops
965 // {
966 // self.processes.remove(&pid);
967 // }
968 if process_exited {
969 eprintln!("2Process {} removed from map after exit.", pid);
970 } else {
971 eprintln!(
972 "Dropped handle for PID {} after {} attempts (process may still be running).",
973 pid, attempts
974 );
975 }
976 sleep(Duration::from_millis(3_000));
977 Ok(process_exited)
978 } else {
979 println!("No processes to kill.");
980 sleep(Duration::from_millis(3_000));
981 Ok(false)
982 }
983 }
984 // pub fn kill_one(&self) {
985 // let mut processes = self.processes.lock().unwrap();
986 // if let Some((&pid, handle)) = processes.iter().next() {
987 // eprintln!("Killing PID: {}", pid);
988 // if let Ok(mut h) = handle.lock() {
989 // let _ = h.kill();
990 // }
991 // processes.remove(&pid);
992 // } else {
993 // println!("No processes to kill.");
994 // }
995 // }
996
997 pub fn kill_all(&self) {
998 let pids: Vec<u32> = self.processes.iter().map(|entry| *entry.key()).collect();
999 // println!("Killing all processes: {:?}", pids);
1000 self.e_window_kill_all();
1001 for pid in pids {
1002 // Check if the process is marked as removed before killing
1003 if let Some(handle_arc) = self.processes.get(&pid) {
1004 if let Ok(h) = handle_arc.try_lock() {
1005 if h.removed {
1006 continue;
1007 }
1008 }
1009 }
1010 println!("Killing PID: {}", pid);
1011 let _ = self.kill_by_pid(pid);
1012 // if let Some((_, handle)) = self.processes.remove(&pid) {
1013 // if let Some(handle) = self.processes.get(&pid) {
1014
1015 // eprintln!("Killing PID: {}", pid);
1016 // if let Ok(mut h) = handle.lock() {
1017 // // Kill the main process
1018 // let _ = h.kill();
1019 // }
1020 // }
1021 }
1022 }
1023
1024 // Returns the terminal error for a given PID.
1025 pub fn get_terminal_error(&self, pid: u32) -> Option<TerminalError> {
1026 // Lock the process map
1027 // Use DashMap's get method directly (no lock needed)
1028 if let Some(handle) = self.processes.get(&pid) {
1029 // Lock the handle to access the terminal error flag
1030 let handle = handle.lock().unwrap();
1031 // Return the terminal error flag value
1032 return Some(handle.terminal_error_flag.lock().unwrap().clone());
1033 }
1034
1035 // If no process is found for the given PID, return None
1036 None
1037 }
1038
1039 // pub fn install_ctrlc_handler(self: Arc<Self>) {
1040 // let manager = Arc::clone(&self);
1041 // ctrlc::set_handler(move || {
1042 // eprintln!("CTRL-C detected. Killing all processes.");
1043 // manager.kill_all();
1044 // std::process::exit(1);
1045 // })
1046 // .expect("Failed to install ctrl-c handler");
1047 // }
1048
1049 /// Wait for the process to finish, show interactive status, then return a result
1050 pub fn wait(
1051 &self,
1052 pid: u32,
1053 _duration: Option<Duration>,
1054 ) -> anyhow::Result<CargoProcessResult> {
1055 // Hide the cursor and ensure it’s restored on exit
1056 #[allow(dead_code)]
1057 struct CursorHide;
1058 impl Drop for CursorHide {
1059 fn drop(&mut self) {
1060 #[cfg(feature = "tui")]
1061 {
1062 let _ = crossterm::execute!(std::io::stdout(), crossterm::cursor::Show);
1063 }
1064 }
1065 }
1066 #[cfg(feature = "tui")]
1067 let _cursor_hide = {
1068 let mut out = std::io::stdout();
1069 crossterm::execute!(out, crossterm::cursor::Hide)?;
1070 CursorHide
1071 };
1072
1073 // 1. Remove the handle from the map
1074 let handle_arc = {
1075 self.processes
1076 .get(&pid)
1077 .map(|entry| entry.clone())
1078 .ok_or_else(|| {
1079 println!("Process with PID {} not found in the process map.", pid);
1080 let result = CargoProcessResult {
1081 target_name: String::new(), // Placeholder, should be set properly in actual use
1082 cmd: String::new(), // Placeholder, should be set properly in actual use
1083 args: Vec::new(), // Placeholder, should be set properly in actual use
1084 pid,
1085 exit_status: None,
1086 diagnostics: Vec::new(),
1087 start_time: None,
1088 end_time: Some(SystemTime::now()),
1089 elapsed_time: None,
1090 terminal_error: None, // Placeholder, should be set properly in actual use
1091 build_finished_time: None, // Placeholder, should be set properly in actual use
1092 build_elapsed: None, // Placeholder, should be set properly in actual use
1093 runtime_elapsed: None, // Placeholder, should be set properly in actual use
1094 stats: crate::e_cargocommand_ext::CargoStats::default(), // Provide a default instance of CargoStats
1095 build_output_size: 0, // Default value set to 0
1096 runtime_output_size: 0, // Placeholder, should be set properly in actual use
1097 is_filter: false, // Placeholder, should be set properly in actual use
1098 is_could_not_compile: false, // Placeholder, should be set properly in actual use
1099 };
1100 self.record_result(result.clone());
1101 anyhow::anyhow!("Process handle with PID {} not found", pid)
1102 })?
1103 };
1104 // 2. Unwrap Arc<Mutex<...>> to get the handle
1105 let mut handle = match Arc::try_unwrap(handle_arc) {
1106 Ok(mutex) => match mutex.into_inner() {
1107 Ok(h) => h,
1108 Err(_) => {
1109 return Err(anyhow::anyhow!(
1110 "Failed to acquire process handle mutex for PID {}",
1111 pid
1112 ));
1113 }
1114 },
1115 Err(handle_arc_left) => {
1116 // If Arc::try_unwrap fails, we still have other references (e.g., in the DashMap).
1117 // Let's just lock and use the handle for this wait, but don't drop the Arc.
1118 let mut handle = handle_arc_left.lock().unwrap();
1119 // Set stats on the result before entering the polling loop
1120 let stats_clone = handle.stats.lock().unwrap().clone();
1121 handle.result.stats = stats_clone;
1122
1123 // 3. Interactive polling loop
1124 let mut system = if handle.is_filter {
1125 Some(System::new_all())
1126 } else {
1127 None
1128 };
1129 const POLL: Duration = Duration::from_secs(1);
1130 let mut loop_cnter = 0;
1131 loop {
1132 loop_cnter += 1;
1133 if handle.is_filter && loop_cnter % 2 == 0 {
1134 if let Some(ref mut sys) = system {
1135 sys.refresh_all();
1136 }
1137 }
1138
1139 if handle.is_filter {
1140 if let Some(ref sys) = system {
1141 if let Some(process) = sys.process(sysinfo::Pid::from(pid as usize)) {
1142 let status = handle.format_status(Some(process));
1143 if !status.is_empty() {
1144 print!("\r{}\r", status);
1145 }
1146 }
1147 }
1148 std::io::stdout().flush().unwrap();
1149 }
1150
1151 if let Some(es) = handle.child.try_wait()? {
1152 let final_diagnostics = {
1153 let diag_lock = handle.diagnostics.lock().unwrap();
1154 diag_lock.clone()
1155 };
1156 handle.result.diagnostics = final_diagnostics.clone();
1157 handle.result.exit_status = Some(es);
1158 handle.result.end_time = Some(SystemTime::now());
1159 if let (Some(start), Some(end)) =
1160 (handle.result.start_time, handle.result.end_time)
1161 {
1162 handle.result.elapsed_time =
1163 Some(end.duration_since(start).unwrap_or_default());
1164 }
1165 handle.removed = true;
1166 println!(
1167 "\nProcess with PID {} finished {:?} {}",
1168 pid,
1169 es,
1170 handle.result.diagnostics.len()
1171 );
1172 break;
1173 }
1174 std::thread::sleep(POLL);
1175 }
1176
1177 if handle.is_filter {
1178 // 4. Extract diagnostics out of Arc<Mutex<_>>
1179 let diagnostics = Arc::try_unwrap(handle.diagnostics.clone())
1180 .map(|m| m.into_inner().unwrap())
1181 .unwrap_or_else(|arc| arc.lock().unwrap().clone());
1182
1183 // 5. Move them into the final result
1184 handle.result.diagnostics = diagnostics;
1185 }
1186 self.record_result(handle.result.clone());
1187 return Ok(handle.result.clone());
1188 }
1189 };
1190
1191 // Set stats on the result before entering the polling loop
1192 handle.result.stats = handle.stats.lock().unwrap().clone();
1193 // 3. Interactive polling loop
1194 let mut system = if handle.is_filter {
1195 Some(System::new_all())
1196 } else {
1197 None
1198 };
1199 const POLL: Duration = Duration::from_secs(1);
1200 let mut loop_cnter = 0;
1201 let start_time = SystemTime::now();
1202 let timeout_duration = _duration.unwrap_or(Duration::from_secs(300)); // Default timeout of 5 minutes
1203
1204 loop {
1205 loop_cnter += 1;
1206 if handle.is_filter && loop_cnter % 2 == 0 {
1207 if let Some(ref mut sys) = system {
1208 sys.refresh_all();
1209 }
1210 }
1211
1212 if handle.is_filter {
1213 if let Some(ref sys) = system {
1214 if let Some(process) = sys.process(sysinfo::Pid::from(pid as usize)) {
1215 let status = handle.format_status(Some(process));
1216 if !status.is_empty() {
1217 print!("\r{}\r", status);
1218 }
1219 }
1220 }
1221 std::io::stdout().flush().unwrap();
1222 }
1223
1224 if let Some(es) = handle.child.try_wait()? {
1225 let final_diagnostics = {
1226 let diag_lock = handle.diagnostics.lock().unwrap();
1227 diag_lock.clone()
1228 };
1229 handle.result.diagnostics = final_diagnostics.clone();
1230 handle.result.exit_status = Some(es);
1231 handle.result.end_time = Some(SystemTime::now());
1232 if let (Some(start), Some(end)) = (handle.result.start_time, handle.result.end_time)
1233 {
1234 handle.result.elapsed_time =
1235 Some(end.duration_since(start).unwrap_or_default());
1236 }
1237 handle.removed = true;
1238 println!(
1239 "\nProcess with PID {} finished {:?} {}",
1240 pid,
1241 es,
1242 handle.result.diagnostics.len()
1243 );
1244 break;
1245 }
1246
1247 // Check for timeout
1248 if start_time.elapsed().unwrap_or_default() > timeout_duration {
1249 eprintln!("\nProcess with PID {} timed out.", pid);
1250 handle.child.kill()?; // Terminate the process
1251 handle.result.exit_status = None;
1252 handle.result.end_time = Some(SystemTime::now());
1253 self.e_window_kill(pid);
1254 handle.removed = true;
1255 break;
1256 }
1257
1258 std::thread::sleep(POLL);
1259 }
1260
1261 if handle.is_filter {
1262 // 4. Extract diagnostics out of Arc<Mutex<_>>
1263 let diagnostics = Arc::try_unwrap(handle.diagnostics)
1264 .map(|m| m.into_inner().unwrap())
1265 .unwrap_or_else(|arc| arc.lock().unwrap().clone());
1266
1267 // 5. Move them into the final result
1268 handle.result.diagnostics = diagnostics;
1269 }
1270 self.record_result(handle.result.clone());
1271 Ok(handle.result)
1272 }
1273
1274 pub fn record_result(&self, result: CargoProcessResult) {
1275 self.results.insert(result.pid, result);
1276 }
1277
1278 pub fn generate_report(&self, create_gist: bool) {
1279 let results: Vec<_> = self
1280 .results
1281 .iter()
1282 .map(|entry| entry.value().clone())
1283 .collect();
1284 let report = crate::e_reports::generate_markdown_report(&results);
1285 if let Err(e) = crate::e_reports::save_report_to_file(&report, "run_report.md") {
1286 eprintln!("Failed to save report: {}", e);
1287 }
1288 if create_gist {
1289 crate::e_reports::create_gist(&report, "run_report.md").unwrap_or_else(|e| {
1290 eprintln!("Failed to create Gist: {}", e);
1291 });
1292 }
1293 }
1294
1295 pub fn format_process_status(
1296 pid: u32,
1297 start_time: Option<SystemTime>,
1298 // system: Arc<Mutex<System>>,
1299 target: &CargoTarget,
1300 target_dimensions: (usize, usize),
1301 ) -> String {
1302 // let start_dt: chrono::DateTime<Local> =
1303 // start_time.unwrap_or_else(|| SystemTime::UNIX_EPOCH).into();
1304 let start_str = start_time
1305 .map(|st| {
1306 chrono::DateTime::<Local>::from(st)
1307 .format("%H:%M:%S")
1308 .to_string()
1309 })
1310 .unwrap_or_else(|| "-".to_string());
1311 let colored_start = nu_ansi_term::Color::LightCyan.paint(&start_str).to_string();
1312 let plain_start = start_str;
1313 if start_time.is_none() {
1314 return String::new();
1315 }
1316 // Calculate runtime.
1317 let now = SystemTime::now();
1318 let runtime_duration = match start_time {
1319 Some(start) => now
1320 .duration_since(start)
1321 .unwrap_or_else(|_| Duration::from_secs(0)),
1322 None => Duration::from_secs(0),
1323 };
1324 if runtime_duration.as_secs() == 0 {
1325 return String::new();
1326 }
1327 let runtime_str = crate::e_fmt::format_duration(runtime_duration);
1328 // compute the max number of digits in either dimension:
1329 let max_digits = target_dimensions
1330 .0
1331 .max(target_dimensions.1)
1332 .to_string()
1333 .len();
1334 let left_display = format!(
1335 "{:0width$}of{:0width$} | {} | {} | PID: {}",
1336 target_dimensions.0,
1337 target_dimensions.1,
1338 nu_ansi_term::Color::Green
1339 .paint(target.display_name.clone())
1340 .to_string(),
1341 colored_start,
1342 pid,
1343 width = max_digits,
1344 );
1345 let left_plain = format!(
1346 "{:0width$}of{:0width$} | {} | {} | PID: {}",
1347 target_dimensions.0,
1348 target_dimensions.1,
1349 target.display_name,
1350 plain_start,
1351 pid,
1352 width = max_digits,
1353 );
1354
1355 // Get terminal width.
1356 #[cfg(feature = "tui")]
1357 let (cols, _) = crossterm::terminal::size().unwrap_or((80, 20));
1358 #[cfg(not(feature = "tui"))]
1359 let (cols, _) = (80, 20);
1360 let mut total_width = cols as usize;
1361 total_width = total_width - 1;
1362 // Format the runtime with underlining.
1363 let right_display = nu_ansi_term::Style::new()
1364 .reset_before_style()
1365 .underline()
1366 .paint(&runtime_str)
1367 .to_string();
1368 let left_len = left_plain.len();
1369 // let right_len = runtime_str.len();
1370 let visible_right_len = runtime_str.len();
1371 let padding = if total_width > left_len + visible_right_len {
1372 total_width.saturating_sub(left_len + visible_right_len)
1373 } else {
1374 0
1375 };
1376
1377 let ret = format!("{}{}{}", left_display, " ".repeat(padding), right_display);
1378 if left_len + visible_right_len > total_width {
1379 let truncated_left = &left_display[..total_width.saturating_sub(visible_right_len)];
1380 return format!("{}{}", truncated_left.trim_end(), right_display);
1381 }
1382 ret
1383 }
1384
1385 /// Print the exact diagnostic output as captured.
1386 pub fn print_exact_output(&self) {
1387 for entry in self.processes.iter() {
1388 println!("--- Full Diagnostics for PID {} ---", entry.key());
1389 let handle_lock = entry.value().lock().unwrap();
1390 let diags = handle_lock.diagnostics.lock().unwrap();
1391 for diag in diags.iter() {
1392 // Print the entire diagnostic.
1393 println!("{:?}: {}", diag.level, diag.message);
1394 }
1395 }
1396 }
1397
1398 /// Print a one‑line summary per warning, numbered with leading zeros.
1399 pub fn print_prefixed_summary(&self) {
1400 // 1. Grab a snapshot of the handles (Arc clones) from DashMap.
1401 let handles: Vec<_> = self
1402 .processes
1403 .iter()
1404 .map(|entry| (entry.key().clone(), entry.value().clone()))
1405 .collect();
1406
1407 // 2. Now we can iterate without holding any DashMap guard.
1408 for (pid, handle_arc) in handles {
1409 // Lock only the diagnostics for this handle.
1410 let handle_lock = handle_arc.lock().unwrap();
1411 let diags = handle_lock.diagnostics.lock().unwrap();
1412
1413 // Collect warnings.
1414 let warnings: Vec<_> = diags.iter().filter(|d| d.level.eq("warning")).collect();
1415
1416 // Determine width for zero-padding for warnings.
1417 let warning_width = warnings.len().to_string().len().max(1);
1418 if warnings.len() > 0 {
1419 println!(
1420 "\n\n--- Warnings for PID {} --- {} {}",
1421 pid,
1422 warning_width,
1423 warnings.len()
1424 );
1425
1426 for (i, diag) in warnings.iter().enumerate() {
1427 // Format the index with leading zeros for warnings.
1428 let index = format!("{:0width$}", i + 1, width = warning_width);
1429 // Print the warning with the index.
1430 println!("{}: {}", index, diag.message.trim());
1431 }
1432 }
1433 // Collect errors.
1434 let errors: Vec<_> = diags.iter().filter(|d| d.level.eq("error")).collect();
1435 if errors.len() > 0 {
1436 // Determine width for zero-padding for errors.
1437 let error_width = errors.len().to_string().len().max(1);
1438 println!(
1439 "\n\n--- Errors for PID {} --- {} {}",
1440 pid,
1441 error_width,
1442 errors.len()
1443 );
1444
1445 for (i, diag) in errors.iter().enumerate() {
1446 // Format the index with leading zeros for errors.
1447 let index = format!("{:0width$}", i + 1, width = error_width);
1448 // Print the error with the index.
1449 println!("{}: {}", index, diag.message.trim());
1450 }
1451 }
1452 }
1453 }
1454
1455 /// file:line:col – source_line, colored by level.
1456 pub fn print_compact(&self) {
1457 for entry in self.processes.iter() {
1458 println!("--- Compact for PID {} ---", entry.key());
1459 let handle_lock = entry.value().lock().unwrap();
1460 let diags = handle_lock.diagnostics.lock().unwrap();
1461 for diag in diags.iter() {
1462 println!("{}: {} {}", diag.level, diag.lineref, diag.message.trim());
1463 }
1464 }
1465 }
1466 /// Print a shortened version: warnings first then errors.
1467 pub fn print_shortened_output(&self) {
1468 for handle in self.processes.iter() {
1469 println!("\n\n\n--- Summary for PID {} ---", handle.key());
1470 let handle_lock = handle.value().lock().unwrap();
1471 let diags = handle_lock.diagnostics.lock().unwrap();
1472
1473 // Filter diagnostics for warnings and errors.
1474 let warnings: Vec<_> = diags.iter().filter(|d| d.level.eq("warning")).collect();
1475 let errors: Vec<_> = diags.iter().filter(|d| d.level.eq("error")).collect();
1476
1477 // Print warnings.
1478 if !warnings.is_empty() {
1479 println!("Warnings:");
1480 for diag in warnings {
1481 println!("print_shortened_output:{}", diag.message.trim());
1482 }
1483 } else {
1484 println!("No warnings.");
1485 }
1486
1487 // Print errors.
1488 if !errors.is_empty() {
1489 println!("Errors:");
1490 for diag in errors {
1491 println!("print_shortened_output: {}", diag.message.trim());
1492 }
1493 } else {
1494 println!("No errors.");
1495 }
1496 }
1497 }
1498
1499 pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
1500 // Check if the process is alive
1501 if !self.is_alive(pid) {
1502 eprintln!("Process with PID {} is not running.", pid);
1503 return Ok(false);
1504 }
1505
1506 #[cfg(unix)]
1507 {
1508 let signals = [
1509 Signal::SIGHUP,
1510 Signal::SIGINT,
1511 Signal::SIGQUIT,
1512 Signal::SIGABRT,
1513 Signal::SIGKILL,
1514 Signal::SIGALRM,
1515 Signal::SIGTERM,
1516 ];
1517 let mut killed = false;
1518 for (i, sig) in signals.iter().enumerate() {
1519 eprintln!("Attempt {}: sending {:?} to PID {}", i + 1, sig, pid);
1520 if let Err(e) = nix_kill(Pid::from_raw(pid as i32), *sig) {
1521 eprintln!("Failed to send {:?} to PID {}: {}", sig, pid, e);
1522 }
1523 std::thread::sleep(std::time::Duration::from_millis(500));
1524 if !self.is_alive(pid) {
1525 killed = true;
1526 break;
1527 }
1528 }
1529 Ok(killed)
1530 }
1531
1532 #[cfg(windows)]
1533 {
1534 eprintln!("Attempting to kill PID {} on Windows", pid);
1535 let output = std::process::Command::new("taskkill")
1536 .args(["/F", "/PID", &pid.to_string()])
1537 .output();
1538 match output {
1539 Ok(out) => {
1540 if out.status.success() {
1541 // Give a moment for the process to terminate
1542 std::thread::sleep(std::time::Duration::from_millis(500));
1543 Ok(!self.is_alive(pid))
1544 } else {
1545 eprintln!(
1546 "taskkill failed for PID {}: {}",
1547 pid,
1548 String::from_utf8_lossy(&out.stderr)
1549 );
1550 Ok(false)
1551 }
1552 }
1553 Err(e) => {
1554 eprintln!("Failed to execute taskkill for PID {}: {}", pid, e);
1555 Ok(false)
1556 }
1557 }
1558 }
1559 }
1560}
1561
1562// #[cfg(feature = "uses_async")]
1563// impl ProcessManager {
1564// pub async fn wait_for_processes(&self) {
1565// loop {
1566// {
1567// if self.processes.lock().unwrap().is_empty() {
1568// break;
1569// }
1570// }
1571// self.notifier.notified().await;
1572// }
1573// }
1574// }
1575
1576pub struct CursorGuard;
1577
1578impl Drop for CursorGuard {
1579 fn drop(&mut self) {
1580 #[cfg(feature = "tui")]
1581 {
1582 let mut stdout = std::io::stdout();
1583 let _ = crossterm::execute!(stdout, crossterm::cursor::Show);
1584 }
1585 }
1586}