Skip to content

Commit 431374e

Browse files
committed
feat: log agent exit code
1 parent d5086ce commit 431374e

File tree

2 files changed

+93
-4
lines changed

2 files changed

+93
-4
lines changed

src/isolate/connections/grpc/_base.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,28 @@ def find_free_port() -> Tuple[str, int]:
145145

146146
def abort_agent(self) -> None:
147147
if self._process is not None:
148+
return_code: int | None = None
148149
try:
149-
print("Terminating the agent process...")
150-
self._process.terminate()
151-
self._process.wait(timeout=PROCESS_SHUTDOWN_TIMEOUT_SECONDS)
152-
print("Agent process shutdown gracefully")
150+
if self._process.poll() is not None:
151+
# already finished
152+
return_code = self._process.returncode
153+
else:
154+
print("Terminating the agent process...")
155+
self._process.terminate()
156+
return_code = self._process.wait(
157+
timeout=PROCESS_SHUTDOWN_TIMEOUT_SECONDS
158+
)
159+
print("Agent process shutdown gracefully")
153160
except Exception as exc:
154161
print(f"Failed to shutdown the agent process gracefully: {exc}")
155162
self._process.kill()
163+
return_code = self._process.wait()
164+
165+
self.log(
166+
f"Isolate agent finished (exit code: {return_code})",
167+
level=LogLevel.INFO,
168+
source=LogSource.BRIDGE,
169+
)
156170
self._process = None
157171

158172
def is_alive(self) -> bool:
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from pathlib import Path
2+
from unittest.mock import Mock
3+
4+
from isolate.backends.local import LocalPythonEnvironment
5+
from isolate.connections import LocalPythonGRPC
6+
from isolate.logs import LogLevel, LogSource
7+
8+
9+
def make_connection(tmp_path: Path) -> LocalPythonGRPC:
10+
environment = LocalPythonEnvironment()
11+
return LocalPythonGRPC(environment, tmp_path)
12+
13+
14+
def test_abort_agent_logs_return_code_for_already_exited_process(tmp_path: Path) -> None:
15+
connection = make_connection(tmp_path)
16+
process = Mock()
17+
process.poll.return_value = 0
18+
process.returncode = 0
19+
connection._process = process
20+
21+
connection.log = Mock()
22+
connection.abort_agent()
23+
24+
process.terminate.assert_not_called()
25+
process.wait.assert_not_called()
26+
process.kill.assert_not_called()
27+
connection.log.assert_called_once_with(
28+
"Isolate agent finished (exit code: 0)",
29+
level=LogLevel.INFO,
30+
source=LogSource.BRIDGE,
31+
)
32+
assert connection._process is None
33+
34+
35+
def test_abort_agent_logs_return_code_for_graceful_termination(tmp_path: Path) -> None:
36+
connection = make_connection(tmp_path)
37+
process = Mock()
38+
process.poll.return_value = None
39+
process.wait.return_value = -15
40+
connection._process = process
41+
42+
connection.log = Mock()
43+
connection.abort_agent()
44+
45+
process.terminate.assert_called_once()
46+
process.wait.assert_called_once()
47+
process.kill.assert_not_called()
48+
connection.log.assert_called_once_with(
49+
"Isolate agent finished (exit code: -15)",
50+
level=LogLevel.INFO,
51+
source=LogSource.BRIDGE,
52+
)
53+
assert connection._process is None
54+
55+
56+
def test_abort_agent_logs_return_code_after_kill_fallback(tmp_path: Path) -> None:
57+
connection = make_connection(tmp_path)
58+
process = Mock()
59+
process.poll.return_value = None
60+
process.terminate.side_effect = RuntimeError("terminate failed")
61+
process.wait.return_value = -9
62+
connection._process = process
63+
64+
connection.log = Mock()
65+
connection.abort_agent()
66+
67+
process.terminate.assert_called_once()
68+
process.kill.assert_called_once()
69+
process.wait.assert_called_once()
70+
connection.log.assert_called_once_with(
71+
"Isolate agent finished (exit code: -9)",
72+
level=LogLevel.INFO,
73+
source=LogSource.BRIDGE,
74+
)
75+
assert connection._process is None

0 commit comments

Comments
 (0)