fix(issue-17): review R1 round 2 - an exited, unreaped wrapper is not a live owner
kill(pid, 0) succeeds for a zombie. A caller that captures the wrapper's
output and calls communicate() after TERM leaves the wrapper unreaped while
the finalizer holds its pipes, so the owner check passed until the deadline
and the job was still closed (codex, round 2; reproduced with the real
registry). _alive now asks `ps -o stat=` (POSIX, no /proc) once kill(0)
succeeds and treats state Z as gone, as tests/conftest.py::_pid_alive does;
no ps answer keeps kill(0)'s verdict. The owner is looked at every 0.5 s.
Tests: TERM + communicate() with no wait() for both graces (stand-ins) and
for the timeout grace with the real registry, old vs new; a zombie is not
alive. All four fail on c5cebe6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c5cebe6c32
commit
518bedac5d
2 files changed
+90
-3
No files matched your search
@@ -120,13 +120,21 @@ class OwnerGone(Exception):
|
||||
|
||||
|
||||
def _alive(pid: int) -> bool:
|
||||
"""A running process. An exited one its caller has not reaped yet is a
|
||||
zombie that still answers kill(0) (review R1, round 2): `ps -o stat=`
|
||||
(POSIX, no /proc) tells it apart, as tests/conftest.py::_pid_alive does."""
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except ProcessLookupError:
|
||||
return False
|
||||
except PermissionError:
|
||||
return True
|
||||
return True
|
||||
try:
|
||||
state = subprocess.run(["ps", "-o", "stat=", "-p", str(pid)], capture_output=True, text=True,
|
||||
timeout=10).stdout.strip()
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return True # no ps answer: kill(0) said it exists
|
||||
return bool(state) and not state.startswith("Z")
|
||||
|
||||
|
||||
def _check_owner(a: argparse.Namespace) -> None:
|
||||
@@ -135,14 +143,14 @@ def _check_owner(a: argparse.Namespace) -> None:
|
||||
|
||||
|
||||
def _sleep(a: argparse.Namespace, seconds: float) -> None:
|
||||
"""time.sleep that looks at the wrapper every 0.2 s."""
|
||||
"""time.sleep that looks at the wrapper every 0.5 s."""
|
||||
end = time.time() + seconds
|
||||
while True:
|
||||
_check_owner(a)
|
||||
left = end - time.time()
|
||||
if left <= 0:
|
||||
return
|
||||
time.sleep(min(left, 0.2))
|
||||
time.sleep(min(left, 0.5))
|
||||
|
||||
|
||||
def _tail(path: str, n: int) -> List[str]:
|
||||
|
||||
Reference in new issue
Block a user