Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions tests/test_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -994,8 +994,50 @@ def test_worker_kill_reaps_the_whole_process_tree(tmp_path: Path) -> None:
"response pipe never reached EOF -- a grandchild still holds it; the worker tree "
"was not reaped"
)
# SECONDARY: the process half, asserted directly.
assert not _pid_alive(grandchild_pid), "the grandchild survived the worker kill"
# SECONDARY: the process half, asserted directly -- POLLED, not sampled once.
#
# THE ONE-SHOT FORM WAS A RACE AGAINST AN EVENTUALLY-CONSISTENT CONDITION, and the window
# exists on BOTH platforms by DIFFERENT mechanisms -- which matters, because the observed
# CI failures were on ubuntu and the first version of this comment explained only Windows.
#
# The primary above accepts pipe EOF, which arrives the instant the last holder of fd 1
# releases it -- i.e. DURING the grandchild's teardown. Then:
#
# POSIX (where the failures actually happened): `_pid_alive` is `os.kill(pid, 0)` at
# :925-931, False ONLY on ProcessLookupError. SIGKILL closes the fds at once, so the pipe
# EOFs and the primary passes -- but the pid lingers as a ZOMBIE until it is re-parented
# and reaped, and a zombie is still visible to `os.kill(pid, 0)`.
#
# WHY THIS TEST PASSES LOCALLY IS **UNATTRIBUTED**, and an earlier version of this comment
# claimed otherwise. It said the window "degrades on a loaded runner and not on an idle
# 20-core box", citing 10/10 local passes. THOSE PASSES WERE ON WINDOWS -- they exercised
# the win32 branch above, not this one, so they say nothing about the POSIX path at all.
# Comparing quiet-Windows against loaded-Linux and calling the difference LOAD leaves
# PLATFORM as an uncontrolled confound. The run that would separate them -- these tests on
# Linux, loaded and unloaded -- has not been done by anyone.
#
# Windows: `_pid_alive` answers through `GetExitCodeProcess`, which keeps reporting
# STILL_ACTIVE until the process object is signalled.
#
# Either way there is a real window in which the pipe has EOF'd and the pid still reads
# alive, and checking once inside it fails a test whose subject is fine.
#
# THE PRIMARY WAS ALREADY BOUNDED (8s) AND THIS WAS NOT -- the same assertion pair, one
# tolerant of scheduling and one not. On a loaded 4-CPU runner the window widens and only
# the intolerant half fires.
#
# POLL, DON'T SAMPLE-AND-HOPE is the house idiom, established in
# tests/test_connscale_cpu_probe.py for exactly this class: that test's comment records it
# "used to be `time.sleep(1.0)` then ONE `sample_proc()`" before the same defect was found.
# The deadline is generous because a false RED here costs a queue and a false GREEN costs
# nothing this test is for -- a surviving grandchild never exits, so no wait rescues it.
reap_deadline = time.monotonic() + 10.0
while _pid_alive(grandchild_pid) and time.monotonic() < reap_deadline:
time.sleep(0.05)
assert not _pid_alive(grandchild_pid), (
"the grandchild survived the worker kill (polled to a 10s deadline, so this is a "
"SURVIVING process rather than one still being reaped)"
)
finally:
if grandchild_pid is not None:
_best_effort_kill_pid(grandchild_pid)
Expand Down
Loading