Repair Local Agent supervisor and add update progress UI
This commit is contained in:
@@ -1,14 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
import hashlib
|
||||
import os
|
||||
import atexit
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
@@ -16,8 +14,6 @@ from tkinter import Tk, messagebox
|
||||
|
||||
|
||||
INSTALL_ROOT = Path(__file__).resolve().parent
|
||||
DATA_DIR = INSTALL_ROOT / "data"
|
||||
SUPERVISOR_LOCK = DATA_DIR / "supervisor.lock"
|
||||
SUPERVISOR_SCRIPT = INSTALL_ROOT / "ERPAgentSupervisor.pyw"
|
||||
URL = "http://127.0.0.1:8788"
|
||||
ERROR_ALREADY_EXISTS = 183
|
||||
@@ -69,7 +65,7 @@ def _focus_existing_dashboard() -> None:
|
||||
buf = ctypes.create_unicode_buffer(length + 1)
|
||||
user32.GetWindowTextW(hwnd, buf, length + 1)
|
||||
if "ERP Local Agent Dashboard" in buf.value:
|
||||
user32.ShowWindow(hwnd, 9) # SW_RESTORE
|
||||
user32.ShowWindow(hwnd, 9)
|
||||
user32.SetForegroundWindow(hwnd)
|
||||
return False
|
||||
return True
|
||||
@@ -93,42 +89,6 @@ def _popup(title: str, message: str, error: bool = False) -> None:
|
||||
root.destroy()
|
||||
|
||||
|
||||
def _pid_alive(pid: int) -> bool:
|
||||
if pid <= 0:
|
||||
return False
|
||||
if os.name == "nt":
|
||||
try:
|
||||
import ctypes
|
||||
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
||||
STILL_ACTIVE = 259
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, int(pid))
|
||||
if not handle:
|
||||
return False
|
||||
try:
|
||||
exit_code = ctypes.c_ulong()
|
||||
if not kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)):
|
||||
return False
|
||||
return exit_code.value == STILL_ACTIVE
|
||||
finally:
|
||||
kernel32.CloseHandle(handle)
|
||||
except Exception:
|
||||
return False
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def _supervisor_running() -> bool:
|
||||
try:
|
||||
if not SUPERVISOR_LOCK.exists():
|
||||
return False
|
||||
return _pid_alive(int(SUPERVISOR_LOCK.read_text(encoding="utf-8").strip()))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _pythonw() -> Path:
|
||||
pyw = INSTALL_ROOT / ".venv" / "Scripts" / "pythonw.exe"
|
||||
if pyw.exists():
|
||||
@@ -139,18 +99,30 @@ def _pythonw() -> Path:
|
||||
raise RuntimeError("ERP Local Agent Python environment was not found.")
|
||||
|
||||
|
||||
def _ready(timeout: float = 1.5) -> bool:
|
||||
try:
|
||||
with urllib.request.urlopen(URL + "/api/status", timeout=timeout) as response:
|
||||
return int(getattr(response, "status", 200)) < 500
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _ensure_supervisor() -> None:
|
||||
if _supervisor_running():
|
||||
# If the dashboard is already healthy, never start another supervisor or worker.
|
||||
if _ready():
|
||||
return
|
||||
|
||||
# The Scheduled Task is the canonical owner/launcher. Starting it repeatedly
|
||||
# is safe because the task is configured with MultipleInstances=IgnoreNew,
|
||||
# and the supervisor itself also owns a machine-wide Global mutex.
|
||||
started_by_task = False
|
||||
if os.name == "nt":
|
||||
result = subprocess.run(
|
||||
["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command",
|
||||
"Start-ScheduledTask -TaskName 'ERP Local Agent' -ErrorAction Stop"],
|
||||
[
|
||||
"powershell.exe",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-Command",
|
||||
"Start-ScheduledTask -TaskName 'ERP Local Agent' -ErrorAction Stop",
|
||||
],
|
||||
cwd=str(INSTALL_ROOT),
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
@@ -160,8 +132,7 @@ def _ensure_supervisor() -> None:
|
||||
started_by_task = result.returncode == 0
|
||||
|
||||
if not started_by_task:
|
||||
# Recovery fallback for a workstation where the scheduled task was not
|
||||
# installed yet. The Global mutex still prevents a second supervisor.
|
||||
# Recovery fallback. The Supervisor Global mutex makes repeated launches harmless.
|
||||
subprocess.Popen(
|
||||
[str(_pythonw()), str(SUPERVISOR_SCRIPT), "--background"],
|
||||
cwd=str(INSTALL_ROOT),
|
||||
@@ -171,23 +142,8 @@ def _ensure_supervisor() -> None:
|
||||
close_fds=True,
|
||||
)
|
||||
|
||||
deadline = time.time() + 20
|
||||
while time.time() < deadline:
|
||||
if _supervisor_running():
|
||||
return
|
||||
time.sleep(0.5)
|
||||
raise RuntimeError("ERP Local Agent supervisor did not start.")
|
||||
|
||||
|
||||
def _ready(timeout: float = 1.5) -> bool:
|
||||
try:
|
||||
with urllib.request.urlopen(URL + "/api/status", timeout=timeout) as response:
|
||||
return int(getattr(response, "status", 200)) < 500
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _wait_ready(seconds: int = 75) -> bool:
|
||||
def _wait_ready(seconds: int = 90) -> bool:
|
||||
deadline = time.time() + seconds
|
||||
while time.time() < deadline:
|
||||
if _ready():
|
||||
@@ -202,6 +158,7 @@ def main() -> None:
|
||||
_focus_existing_dashboard()
|
||||
return
|
||||
atexit.register(_close_dashboard_mutex, dashboard_mutex)
|
||||
|
||||
try:
|
||||
_ensure_supervisor()
|
||||
except Exception as exc:
|
||||
@@ -211,8 +168,9 @@ def main() -> None:
|
||||
if not _wait_ready():
|
||||
_popup(
|
||||
"ERP Local Agent",
|
||||
"The Local Agent supervisor is running, but the dashboard did not become ready.\n\n"
|
||||
f"Check logs under:\n{INSTALL_ROOT / 'logs'}",
|
||||
"The Local Agent did not become ready.\n\n"
|
||||
"Check the 'ERP Local Agent' Scheduled Task and the logs under:\n"
|
||||
f"{INSTALL_ROOT / 'logs'}",
|
||||
error=True,
|
||||
)
|
||||
return
|
||||
@@ -252,9 +210,9 @@ def main() -> None:
|
||||
pass
|
||||
outage_seen = False
|
||||
was_ready = current
|
||||
time.sleep(1.5)
|
||||
time.sleep(1.0)
|
||||
except Exception:
|
||||
time.sleep(2)
|
||||
time.sleep(1.5)
|
||||
|
||||
webview.start(monitor)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user