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