diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 8670b5a780..633deac29b 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -157,8 +157,18 @@ def _request_gateway_self_restart(pid: int) -> bool: return True -def find_gateway_pids(exclude_pids: set | None = None) -> list: - """Find PIDs of running gateway processes for the current Hermes profile.""" +def find_gateway_pids(exclude_pids: set | None = None, all_profiles: bool = False) -> list: + """Find PIDs of running gateway processes. + + Args: + exclude_pids: PIDs to exclude from the result (e.g. service-managed + PIDs that should not be killed during a stale-process sweep). + all_profiles: When ``True``, return gateway PIDs across **all** + profiles (the pre-7923 global behaviour). ``hermes update`` + needs this because a code update affects every profile. + When ``False`` (default), only PIDs belonging to the current + Hermes profile are returned. + """ _exclude = exclude_pids or set() pids = [pid for pid in _get_service_pids() if pid not in _exclude] patterns = [ @@ -202,7 +212,7 @@ def find_gateway_pids(exclude_pids: set | None = None) -> list: current_cmd = line[len("CommandLine="):] elif line.startswith("ProcessId="): pid_str = line[len("ProcessId="):] - if any(p in current_cmd for p in patterns) and _matches_current_profile(current_cmd): + if any(p in current_cmd for p in patterns) and (all_profiles or _matches_current_profile(current_cmd)): try: pid = int(pid_str) if pid != os.getpid() and pid not in pids and pid not in _exclude: @@ -243,23 +253,26 @@ def find_gateway_pids(exclude_pids: set | None = None) -> list: continue if pid == os.getpid() or pid in pids or pid in _exclude: continue - if any(pattern in command for pattern in patterns) and _matches_current_profile(command): + if any(pattern in command for pattern in patterns) and (all_profiles or _matches_current_profile(command)): pids.append(pid) - except Exception: + except (OSError, subprocess.TimeoutExpired): pass return pids -def kill_gateway_processes(force: bool = False, exclude_pids: set | None = None) -> int: +def kill_gateway_processes(force: bool = False, exclude_pids: set | None = None, + all_profiles: bool = False) -> int: """Kill any running gateway processes. Returns count killed. Args: force: Use the platform's force-kill mechanism instead of graceful terminate. exclude_pids: PIDs to skip (e.g. service-managed PIDs that were just restarted and should not be killed). + all_profiles: When ``True``, kill across all profiles. Passed + through to :func:`find_gateway_pids`. """ - pids = find_gateway_pids(exclude_pids=exclude_pids) + pids = find_gateway_pids(exclude_pids=exclude_pids, all_profiles=all_profiles) killed = 0 for pid in pids: @@ -2597,7 +2610,7 @@ def gateway_command(args): service_available = True except subprocess.CalledProcessError: pass - killed = kill_gateway_processes() + killed = kill_gateway_processes(all_profiles=True) total = killed + (1 if service_available else 0) if total: print(f"✓ Stopped {total} gateway process(es) across all profiles") diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 4b7dd600b3..df87f93555 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -3876,7 +3876,7 @@ def cmd_update(args): # Exclude PIDs that belong to just-restarted services so we don't # immediately kill the process that systemd/launchd just spawned. service_pids = _get_service_pids() - manual_pids = find_gateway_pids(exclude_pids=service_pids) + manual_pids = find_gateway_pids(exclude_pids=service_pids, all_profiles=True) for pid in manual_pids: try: os.kill(pid, _signal.SIGTERM) diff --git a/tests/hermes_cli/test_gateway.py b/tests/hermes_cli/test_gateway.py index 955449547c..fd88a26c6a 100644 --- a/tests/hermes_cli/test_gateway.py +++ b/tests/hermes_cli/test_gateway.py @@ -260,7 +260,7 @@ class TestWaitForGatewayExit: def test_kill_gateway_processes_force_uses_helper(self, monkeypatch): calls = [] - monkeypatch.setattr(gateway, "find_gateway_pids", lambda exclude_pids=None: [11, 22]) + monkeypatch.setattr(gateway, "find_gateway_pids", lambda exclude_pids=None, all_profiles=False: [11, 22]) monkeypatch.setattr(gateway, "terminate_pid", lambda pid, force=False: calls.append((pid, force))) killed = gateway.kill_gateway_processes(force=True) diff --git a/tests/hermes_cli/test_gateway_service.py b/tests/hermes_cli/test_gateway_service.py index 482fb4ea5d..cba3a8192f 100644 --- a/tests/hermes_cli/test_gateway_service.py +++ b/tests/hermes_cli/test_gateway_service.py @@ -130,7 +130,7 @@ class TestGatewayStopCleanup: monkeypatch.setattr( gateway_cli, "kill_gateway_processes", - lambda force=False: kill_calls.append(force) or 2, + lambda force=False, all_profiles=False: kill_calls.append(force) or 2, ) gateway_cli.gateway_command(SimpleNamespace(gateway_command="stop")) @@ -156,7 +156,7 @@ class TestGatewayStopCleanup: monkeypatch.setattr( gateway_cli, "kill_gateway_processes", - lambda force=False: kill_calls.append(force) or 2, + lambda force=False, all_profiles=False: kill_calls.append(force) or 2, ) gateway_cli.gateway_command(SimpleNamespace(gateway_command="stop", **{"all": True})) diff --git a/tests/hermes_cli/test_update_gateway_restart.py b/tests/hermes_cli/test_update_gateway_restart.py index 1460f00eae..822b22742d 100644 --- a/tests/hermes_cli/test_update_gateway_restart.py +++ b/tests/hermes_cli/test_update_gateway_restart.py @@ -549,7 +549,7 @@ class TestServicePidExclusion: gateway_cli, "_get_service_pids", return_value={SERVICE_PID} ), patch.object( gateway_cli, "find_gateway_pids", - side_effect=lambda exclude_pids=None: ( + side_effect=lambda exclude_pids=None, all_profiles=False: ( [SERVICE_PID] if not exclude_pids else [p for p in [SERVICE_PID] if p not in exclude_pids] ), @@ -592,7 +592,7 @@ class TestServicePidExclusion: gateway_cli, "_get_service_pids", return_value={SERVICE_PID} ), patch.object( gateway_cli, "find_gateway_pids", - side_effect=lambda exclude_pids=None: ( + side_effect=lambda exclude_pids=None, all_profiles=False: ( [SERVICE_PID] if not exclude_pids else [p for p in [SERVICE_PID] if p not in exclude_pids] ), @@ -631,7 +631,7 @@ class TestServicePidExclusion: launchctl_loaded=True, ) - def fake_find(exclude_pids=None): + def fake_find(exclude_pids=None, all_profiles=False): _exclude = exclude_pids or set() return [p for p in [SERVICE_PID, MANUAL_PID] if p not in _exclude]