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