From a4e0273ffa14d37db97a4c7fd5e03dce64417f80 Mon Sep 17 00:00:00 2001 From: xor <127287135+xormania@users.noreply.github.com> Date: Tue, 1 Sep 2026 21:28:01 -0400 Subject: [PATCH] ops: treat killed zombies as terminated in tests Process-group termination can leave dead descendants in zombie state until container PID 1 reaps them. Make the isolation assertions distinguish those non-runnable processes from surviving workers instead of relying on kill(pid, 0) alone. Source: original Co-Authored-By: OpenAI Codex --- ops/devlane/dispatch/tests/launch_support.py | 12 ++++++++++++ ops/devlane/task/tests/test_run.py | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/ops/devlane/dispatch/tests/launch_support.py b/ops/devlane/dispatch/tests/launch_support.py index 14a2df2..8042bdb 100644 --- a/ops/devlane/dispatch/tests/launch_support.py +++ b/ops/devlane/dispatch/tests/launch_support.py @@ -663,6 +663,18 @@ def sha256_file(path) -> str: def pid_is_alive(pid: int) -> bool: + # A group SIGKILL can leave a descendant as a zombie until the host's + # init process reaps it. ``kill(pid, 0)`` still succeeds for zombies, + # even though they cannot execute and therefore are not survivors of the + # isolation boundary. Check procfs first so the process-group assertion + # measures live workers rather than the reaping behaviour of PID 1 (which + # is notably delayed in some CI containers). + stat = Path(f"/proc/{pid}/stat") + try: + if stat.read_text(encoding="utf-8").split()[2] == "Z": + return False + except (FileNotFoundError, IndexError, OSError): + pass try: os.kill(pid, 0) except ProcessLookupError: diff --git a/ops/devlane/task/tests/test_run.py b/ops/devlane/task/tests/test_run.py index f3f48d6..c2925be 100644 --- a/ops/devlane/task/tests/test_run.py +++ b/ops/devlane/task/tests/test_run.py @@ -1307,6 +1307,14 @@ def _orphan_maker(self): @staticmethod def _alive(pid): + # kill(0) also reports zombies as present. A killed orphan can stay + # zombied until PID 1 reaps it in a container, but it is no longer a + # runnable descendant and must not make this isolation check flaky. + try: + if Path(f"/proc/{pid}/stat").read_text().split()[2] == "Z": + return False + except (FileNotFoundError, IndexError, OSError): + pass try: os.kill(pid, 0) return True