mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-09-01 16:51:53 +03:00
fix(runtime): scope Windows process termination
This commit is contained in:
@@ -308,26 +308,18 @@ class ManagedProcessRuntime(Generic[_StartOptionsT]):
|
|||||||
return self._wait_for_exit(pid, 2)
|
return self._wait_for_exit(pid, 2)
|
||||||
|
|
||||||
def _terminate_windows(self, pid: int, *, timeout_s: int) -> bool:
|
def _terminate_windows(self, pid: int, *, timeout_s: int) -> bool:
|
||||||
ctrl_break = getattr(signal, "CTRL_BREAK_EVENT", None)
|
# ``os.kill(pid, CTRL_BREAK_EVENT)`` delegates to
|
||||||
if ctrl_break is not None:
|
# GenerateConsoleCtrlEvent. That API targets a console process group,
|
||||||
ctrl_break_sent = False
|
# not an individual process, and can interrupt the caller when a
|
||||||
try:
|
# detached/no-window child has no addressable console group. Keep
|
||||||
os.kill(pid, ctrl_break)
|
# termination scoped to the recorded PID tree instead.
|
||||||
except ProcessLookupError:
|
|
||||||
return True
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
ctrl_break_sent = True
|
|
||||||
if ctrl_break_sent and self._wait_for_exit(pid, timeout_s):
|
|
||||||
return True
|
|
||||||
self._subprocess_run(
|
self._subprocess_run(
|
||||||
["taskkill", "/PID", str(pid), "/T"],
|
["taskkill", "/PID", str(pid), "/T"],
|
||||||
check=False,
|
check=False,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
if self._wait_for_exit(pid, 2):
|
if self._wait_for_exit(pid, timeout_s):
|
||||||
return True
|
return True
|
||||||
self._subprocess_run(
|
self._subprocess_run(
|
||||||
["taskkill", "/PID", str(pid), "/T", "/F"],
|
["taskkill", "/PID", str(pid), "/T", "/F"],
|
||||||
|
|||||||
@@ -564,7 +564,7 @@ def test_stop_succeeds_when_process_exits_at_timeout_boundary(tmp_path, monkeypa
|
|||||||
assert result.status.running is False
|
assert result.status.running is False
|
||||||
|
|
||||||
|
|
||||||
def test_terminate_windows_falls_back_when_ctrl_break_is_rejected(tmp_path, monkeypatch):
|
def test_terminate_windows_targets_only_the_recorded_process_tree(tmp_path, monkeypatch):
|
||||||
taskkill_calls: list[dict] = []
|
taskkill_calls: list[dict] = []
|
||||||
wait_timeouts: list[int | float] = []
|
wait_timeouts: list[int | float] = []
|
||||||
|
|
||||||
@@ -578,12 +578,10 @@ def test_terminate_windows_falls_back_when_ctrl_break_is_rejected(tmp_path, monk
|
|||||||
sleep=lambda _seconds: None,
|
sleep=lambda _seconds: None,
|
||||||
)
|
)
|
||||||
|
|
||||||
monkeypatch.setattr(signal, "CTRL_BREAK_EVENT", 1, raising=False)
|
monkeypatch.setattr(
|
||||||
|
"nanobot.process_runtime.os.kill",
|
||||||
def fake_kill(_pid, _signal):
|
lambda *_args: pytest.fail("Windows termination must not broadcast a console event"),
|
||||||
raise OSError(87, "The parameter is incorrect")
|
)
|
||||||
|
|
||||||
monkeypatch.setattr("nanobot.process_runtime.os.kill", fake_kill)
|
|
||||||
|
|
||||||
def fake_wait_for_exit(_pid, _timeout_s):
|
def fake_wait_for_exit(_pid, _timeout_s):
|
||||||
wait_timeouts.append(_timeout_s)
|
wait_timeouts.append(_timeout_s)
|
||||||
@@ -593,7 +591,7 @@ def test_terminate_windows_falls_back_when_ctrl_break_is_rejected(tmp_path, monk
|
|||||||
monkeypatch.setattr(runtime, "_wait_for_exit", fake_wait_for_exit)
|
monkeypatch.setattr(runtime, "_wait_for_exit", fake_wait_for_exit)
|
||||||
|
|
||||||
assert runtime._terminate_windows(12345, timeout_s=20) is True
|
assert runtime._terminate_windows(12345, timeout_s=20) is True
|
||||||
assert wait_timeouts == [2]
|
assert wait_timeouts == [20]
|
||||||
assert taskkill_calls == [
|
assert taskkill_calls == [
|
||||||
{
|
{
|
||||||
"command": ["taskkill", "/PID", "12345", "/T"],
|
"command": ["taskkill", "/PID", "12345", "/T"],
|
||||||
|
|||||||
Reference in New Issue
Block a user