65 lines
2.0 KiB
Python
65 lines
2.0 KiB
Python
from __future__ import annotations
|
|
|
|
import threading
|
|
from typing import List
|
|
|
|
# Import at runtime since this module is part of util package and used broadly
|
|
from ci.util.running_process import RunningProcess
|
|
|
|
|
|
class RunningProcessManager:
|
|
"""Thread-safe registry of currently running processes for diagnostics."""
|
|
|
|
def __init__(self) -> None:
|
|
self._lock = threading.RLock()
|
|
self._processes: list[RunningProcess] = []
|
|
|
|
def register(self, proc: RunningProcess) -> None:
|
|
with self._lock:
|
|
if proc not in self._processes:
|
|
self._processes.append(proc)
|
|
|
|
def unregister(self, proc: RunningProcess) -> None:
|
|
with self._lock:
|
|
try:
|
|
self._processes.remove(proc)
|
|
except ValueError:
|
|
pass
|
|
|
|
def list_active(self) -> list[RunningProcess]:
|
|
with self._lock:
|
|
return [p for p in self._processes if not p.finished]
|
|
|
|
def dump_active(self) -> None:
|
|
active: list[RunningProcess] = self.list_active()
|
|
if not active:
|
|
print("\nNO ACTIVE SUBPROCESSES DETECTED - MAIN PROCESS LIKELY HUNG")
|
|
return
|
|
|
|
print("\nSTUCK SUBPROCESS COMMANDS:")
|
|
import time
|
|
|
|
now = time.time()
|
|
for idx, p in enumerate(active, 1):
|
|
pid: int | None = None
|
|
try:
|
|
if p.proc is not None:
|
|
pid = p.proc.pid
|
|
except Exception:
|
|
pid = None
|
|
|
|
start = p.start_time
|
|
last_out = p.time_last_stdout_line()
|
|
duration_str = f"{(now - start):.1f}s" if start is not None else "?"
|
|
since_out_str = (
|
|
f"{(now - last_out):.1f}s" if last_out is not None else "no-output"
|
|
)
|
|
|
|
print(
|
|
f" {idx}. cmd={p.command} pid={pid} duration={duration_str} last_output={since_out_str}"
|
|
)
|
|
|
|
|
|
# Global singleton instance for convenient access
|
|
RunningProcessManagerSingleton = RunningProcessManager()
|