diff --git a/tests/tools/test_mcp_stability.py b/tests/tools/test_mcp_stability.py index c83dda463..576d053df 100644 --- a/tests/tools/test_mcp_stability.py +++ b/tests/tools/test_mcp_stability.py @@ -104,6 +104,45 @@ class TestStdioPidTracking: with _lock: assert fake_pid not in _stdio_pids + def test_kill_orphaned_uses_sigkill_when_available(self, monkeypatch): + """Unix-like platforms should keep using SIGKILL for orphan cleanup.""" + from tools.mcp_tool import _kill_orphaned_mcp_children, _stdio_pids, _lock + + fake_pid = 424242 + with _lock: + _stdio_pids.clear() + _stdio_pids.add(fake_pid) + + fake_sigkill = 9 + monkeypatch.setattr(signal, "SIGKILL", fake_sigkill, raising=False) + + with patch("tools.mcp_tool.os.kill") as mock_kill: + _kill_orphaned_mcp_children() + + mock_kill.assert_called_once_with(fake_pid, fake_sigkill) + + with _lock: + assert fake_pid not in _stdio_pids + + def test_kill_orphaned_falls_back_without_sigkill(self, monkeypatch): + """Windows-like signal modules without SIGKILL should fall back to SIGTERM.""" + from tools.mcp_tool import _kill_orphaned_mcp_children, _stdio_pids, _lock + + fake_pid = 434343 + with _lock: + _stdio_pids.clear() + _stdio_pids.add(fake_pid) + + monkeypatch.delattr(signal, "SIGKILL", raising=False) + + with patch("tools.mcp_tool.os.kill") as mock_kill: + _kill_orphaned_mcp_children() + + mock_kill.assert_called_once_with(fake_pid, signal.SIGTERM) + + with _lock: + assert fake_pid not in _stdio_pids + # --------------------------------------------------------------------------- # Fix 3: MCP reload timeout (cli.py) diff --git a/tools/mcp_tool.py b/tools/mcp_tool.py index 4040ed74e..035564c7b 100644 --- a/tools/mcp_tool.py +++ b/tools/mcp_tool.py @@ -2160,6 +2160,7 @@ def _kill_orphaned_mcp_children() -> None: Only kills PIDs tracked in ``_stdio_pids`` — never arbitrary children. """ import signal as _signal + kill_signal = getattr(_signal, "SIGKILL", _signal.SIGTERM) with _lock: pids = list(_stdio_pids) @@ -2167,7 +2168,7 @@ def _kill_orphaned_mcp_children() -> None: for pid in pids: try: - os.kill(pid, _signal.SIGKILL) + os.kill(pid, kill_signal) logger.debug("Force-killed orphaned MCP stdio process %d", pid) except (ProcessLookupError, PermissionError, OSError): pass # Already exited or inaccessible