Enforce single instance Local Agent supervisor worker and dashboard

This commit is contained in:
A R R R Associates
2026-09-03 14:03:26 +05:30
parent 010d0969b4
commit 5dce7f16f9
7 changed files with 267 additions and 3 deletions
@@ -1,6 +1,9 @@
from __future__ import annotations
import argparse
import ctypes
from ctypes import wintypes
import hashlib
import json
import os
from pathlib import Path
@@ -25,6 +28,60 @@ DASHBOARD_URL = "http://127.0.0.1:8788"
WORKER_MODULE = "erp_local_agent.main"
RESTART_DELAY_SECONDS = 3
HEALTH_TIMEOUT_SECONDS = 60
ERROR_ALREADY_EXISTS = 183
def _supervisor_mutex_name() -> str:
normalized = str(INSTALL_ROOT.resolve()).casefold().encode("utf-8", errors="ignore")
suffix = hashlib.sha256(normalized).hexdigest()[:20]
return f"Local\\ARRR_ERP_Local_Agent_SUPERVISOR_{suffix}"
def _acquire_os_mutex():
if os.name != "nt":
return object()
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
kernel32.CreateMutexW.argtypes = (ctypes.c_void_p, wintypes.BOOL, wintypes.LPCWSTR)
kernel32.CreateMutexW.restype = wintypes.HANDLE
ctypes.set_last_error(0)
handle = kernel32.CreateMutexW(None, False, _supervisor_mutex_name())
if not handle:
raise ctypes.WinError(ctypes.get_last_error())
if ctypes.get_last_error() == ERROR_ALREADY_EXISTS:
kernel32.CloseHandle(handle)
return None
return handle
def _close_os_mutex(handle) -> None:
if os.name != "nt" or handle is None or not isinstance(handle, int):
return
try:
ctypes.WinDLL("kernel32", use_last_error=True).CloseHandle(handle)
except Exception:
pass
def _kill_duplicate_supervisors() -> None:
if os.name != "nt":
return
root = str(INSTALL_ROOT).replace("'", "''")
script = (
"$root='" + root + "';$self=" + str(os.getpid()) + ";"
"Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | "
"Where-Object { $_.ProcessId -ne $self -and $_.CommandLine -and "
"$_.CommandLine -match 'ERPAgentSupervisor\\.pyw' -and "
"$_.CommandLine -like ('*'+$root+'*') } | "
"ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
)
subprocess.run(
["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script],
cwd=str(INSTALL_ROOT),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
creationflags=_create_no_window(),
check=False,
)
def _create_no_window() -> int:
@@ -135,8 +192,14 @@ class Supervisor:
def __init__(self):
self.worker: subprocess.Popen | None = None
self.running = True
self._os_mutex = None
def acquire_lock(self) -> bool:
# The named mutex is atomic at the Windows kernel level and prevents
# the lock-file race that allowed multiple supervisors before 1.22.3.
self._os_mutex = _acquire_os_mutex()
if self._os_mutex is None:
return False
DATA_DIR.mkdir(parents=True, exist_ok=True)
if LOCK_FILE.exists():
try:
@@ -154,6 +217,8 @@ class Supervisor:
LOCK_FILE.unlink()
except Exception:
pass
_close_os_mutex(self._os_mutex)
self._os_mutex = None
def start_worker(self) -> None:
if self.worker is not None and self.worker.poll() is None:
@@ -368,6 +433,7 @@ class Supervisor:
return 0
try:
_kill_duplicate_supervisors()
_state("starting", "ERP Local Agent supervisor starting.")
self.start_worker()