Treat zombie workers as terminated in tests
ci / svc-realcombo (pull_request) Successful in 1m51s
ci / test (pull_request) Successful in 6m4s
ci / test-pg (pull_request) Successful in 8m12s

Why: Gitea containers may leave SIGKILLed children as zombies until PID 1 reaps them, while kill -0 still reports success.

Evidence: Both timeout tests and the full AgentExecControllerTests suite pass; git diff --check passes.

Impact: Changes test liveness detection only; genuinely running child processes still fail the assertions.

Refs: #567
This commit is contained in:
2026-10-15 09:10:24 +00:00
parent 29e7cf185e
commit 3ffd641755
+23 -13
View File
@@ -41,6 +41,25 @@ def _canonical_snapshot(repo: Path) -> dict[str, str]:
return {"head": head, "tree": tree, "status": status, "index": index}
def _pid_alive(pid: int) -> bool:
"""Return true only for a running, non-zombie process."""
try:
text = Path(f"/proc/{pid}/stat").read_text(
encoding="utf-8", errors="replace"
)
except FileNotFoundError:
return False
rpar = text.rfind(")")
if rpar != -1 and rpar + 2 < len(text) and text[rpar + 2] == "Z":
return False
return (
subprocess.run(
["kill", "-0", str(pid)], check=False, capture_output=True
).returncode
== 0
)
def _write_config(
repo: Path,
*,
@@ -740,13 +759,8 @@ class AgentExecControllerTests(unittest.TestCase):
self.assertNotIn("idle timeout", data["reason"])
if child_pid_file.exists():
child = int(child_pid_file.read_text(encoding="utf-8").strip())
# Child must not still be alive.
still = subprocess.run(
["kill", "-0", str(child)],
check=False,
capture_output=True,
)
self.assertNotEqual(still.returncode, 0)
# A zombie is terminated even if a container PID 1 has not reaped it.
self.assertFalse(_pid_alive(child))
self._assert_canonical_untouched()
def test_idle_timeout_kills_silent_worker_before_total(self) -> None:
@@ -821,12 +835,8 @@ class AgentExecControllerTests(unittest.TestCase):
self.assertEqual(cleanup["reason"], "idle timeout")
self.assertTrue(child_pid_file.is_file())
child = int(child_pid_file.read_text(encoding="utf-8").strip())
still = subprocess.run(
["kill", "-0", str(child)],
check=False,
capture_output=True,
)
self.assertNotEqual(still.returncode, 0)
# A zombie is terminated even if a container PID 1 has not reaped it.
self.assertFalse(_pid_alive(child))
self._assert_canonical_untouched()
def test_progress_keeps_worker_alive_past_one_idle_interval(self) -> None: