new kamil adc
This commit is contained in:
@@ -23,6 +23,10 @@ logger = logging.getLogger(__name__)
|
||||
_LOG_MAX_BYTES = 8 * 1024 * 1024
|
||||
# Per-process force-kill deadline used on stop; each child gets its own window.
|
||||
_STOP_GRACE_SECONDS = 2.0
|
||||
# Cmdline marker for the Kamil ADC collector binary. The collector runs in its own
|
||||
# session, so it can outlive a force-killed acquisition producer while still holding
|
||||
# the L-Card E-502; we hunt it by name as a guaranteed device-release backstop.
|
||||
_KAMIL_COLLECTOR_MARKER = b"kamil_adc_collector"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -135,6 +139,9 @@ class ProcessSupervisor:
|
||||
"""Start required pipeline binaries and wait until they are ready."""
|
||||
if self.is_running():
|
||||
raise RuntimeError("Acquisition processes are already running")
|
||||
# Clean slate: an ADC collector orphaned by a prior crash/force-kill can
|
||||
# still hold the E-502 and make a fresh acquisition fail to open.
|
||||
self._kill_kamil_collectors()
|
||||
|
||||
logger.info(
|
||||
"Starting pipeline from config %s (allow_clean_orchestrator_exit=%s)",
|
||||
@@ -186,6 +193,7 @@ class ProcessSupervisor:
|
||||
def stop(self) -> None:
|
||||
"""Stop acquisition-side processes, keep processor process intact."""
|
||||
self._stop_processes(["sweep_orchestrator", "data_preprocessor"])
|
||||
self._kill_kamil_collectors()
|
||||
|
||||
def stop_orchestrator(self) -> None:
|
||||
"""Stop orchestrator process only."""
|
||||
@@ -198,6 +206,7 @@ class ProcessSupervisor:
|
||||
def stop_all(self) -> None:
|
||||
"""Stop all managed processes."""
|
||||
self._stop_processes(["sweep_orchestrator", "data_preprocessor", "data_processor"])
|
||||
self._kill_kamil_collectors()
|
||||
|
||||
def _spawn(self, name: str, command: list[str], *, allow_clean_exit: bool) -> None:
|
||||
"""Spawn one process unless same process is already alive.
|
||||
@@ -353,6 +362,32 @@ class ProcessSupervisor:
|
||||
except (ProcessLookupError, PermissionError):
|
||||
pass
|
||||
|
||||
def _kill_kamil_collectors(self) -> None:
|
||||
"""SIGKILL any Kamil ADC collector found by name — a guaranteed device backstop.
|
||||
|
||||
The collector survives a force-killed producer (own session) and can hang
|
||||
the E-502, so it is hunted by cmdline and its group killed hard, regardless
|
||||
of who its parent is. Best-effort and never raises.
|
||||
"""
|
||||
own_pid = os.getpid()
|
||||
try:
|
||||
pids = [int(entry.name) for entry in Path("/proc").iterdir() if entry.name.isdigit()]
|
||||
except OSError:
|
||||
return
|
||||
killed = 0
|
||||
for pid in pids:
|
||||
if pid <= 1 or pid == own_pid:
|
||||
continue
|
||||
try:
|
||||
cmdline = (Path("/proc") / str(pid) / "cmdline").read_bytes()
|
||||
except OSError:
|
||||
continue
|
||||
if _KAMIL_COLLECTOR_MARKER in cmdline:
|
||||
self._signal_group(pid, signal.SIGKILL)
|
||||
killed += 1
|
||||
if killed:
|
||||
logger.warning("Force-killed %d orphaned Kamil ADC collector process(es)", killed)
|
||||
|
||||
@staticmethod
|
||||
def _log_abnormal_stop_exit(process: ManagedProcess) -> None:
|
||||
"""Log a process that self-exited abnormally around stop time.
|
||||
@@ -523,6 +558,9 @@ class ProcessSupervisor:
|
||||
self._pidfile_path.write_text("", encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
# The ADC collector is a grandchild (spawned by the producer), not in the
|
||||
# pidfile, so reap it separately by name.
|
||||
self._kill_kamil_collectors()
|
||||
|
||||
def _is_stale_pipeline_pid(self, pid: int) -> bool:
|
||||
"""Return whether `pid` still runs one of our pipeline binaries/scripts.
|
||||
|
||||
Reference in New Issue
Block a user