Found by CodeRabbit while reviewing #1540; the code is on main from #1467, not in that PR's diff.
RunHandle._wait_for_acceptance (python/simpler/worker.py) sets the completion flags outside any try/finally:
assert run_id is not None
try:
self._worker._wait_run_handle_accepted(run_id)
except BaseException:
self._accept_wait_in_progress = False
with self._cv:
self._cv.notify_all()
raise
self._launch_accepted = True
self._accept_wait_in_progress = False
with self._cv:
self._cv.notify_all()
An async BaseException landing between the native call returning and those assigns — the GIL-reacquire window this file's own wait() docstring already reasons about — leaves _accept_wait_in_progress True forever.
That flag is load-bearing: wait() loops on it before it ever sets _terminal, so both wait() and any later _wait_for_acceptance() on the handle block indefinitely, including the unconditional handle.wait() drain in Worker.close().
Unlike the irreducible windows documented elsewhere in the file, this one is avoidable:
try:
self._worker._wait_run_handle_accepted(run_id)
self._launch_accepted = True
finally:
self._accept_wait_in_progress = False
with self._cv:
self._cv.notify_all()
Severity: a hang rather than wrong data, and it needs an async exception in a narrow window, so low frequency — but the failure mode is an unkillable close().
Found by CodeRabbit while reviewing #1540; the code is on
mainfrom #1467, not in that PR's diff.RunHandle._wait_for_acceptance(python/simpler/worker.py) sets the completion flags outside anytry/finally:An async
BaseExceptionlanding between the native call returning and those assigns — the GIL-reacquire window this file's ownwait()docstring already reasons about — leaves_accept_wait_in_progressTrueforever.That flag is load-bearing:
wait()loops on it before it ever sets_terminal, so bothwait()and any later_wait_for_acceptance()on the handle block indefinitely, including the unconditionalhandle.wait()drain inWorker.close().Unlike the irreducible windows documented elsewhere in the file, this one is avoidable:
Severity: a hang rather than wrong data, and it needs an async exception in a narrow window, so low frequency — but the failure mode is an unkillable
close().