Repository navigation
HOLD (owner area #32): fix(net): stop and join pw-reaper threads at interpreter exit - #53
Open
wikithoughts wants to merge 1 commit into
Open
wikithoughts wants to merge 1 commit into
wikithoughts wants to merge 1 commit into
Conversation
A pw-reaper daemon thread that is mid-print (its failed-pass log line) when the interpreter finalizes holds stdout's buffer lock, and CPython aborts with "Fatal Python error: _enter_buffered_busy". This is the same hazard #48 fixed for the pw-baseline thread. store.py now tracks live Stores in a WeakSet that survives importlib.reload, and a single atexit hook sets each _stop and joins every reaper under one shared 5 s deadline. tests/test_reaper_shutdown.py runs a child interpreter whose reaper is blocked in print() at exit, both plain and after a module reload. It aborts on the old code on 3.10-3.13 and exits 0 with the fix. A third test checks that a stuck reaper cannot wedge exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
passiveworkers/net/store.py(+29 lines)Storeadds itself to a module-levelweakref.WeakSet. The set survivesimportlib.reload, which the tests do constantly.atexithook, registered once even across reloads, sets each Store's_stopand then joins everypw-reaperthread under one shared 5 s deadline._FetchWatchdog.close()cancels and joins the timer._store_jobs.py,ledger.pyand the other_store_*.pyfiles are untouched.tests/test_reaper_shutdown.py(new) runs a real child interpreter with a temp SQLite DB and no network.print(), holding stdout's buffer lock, when the child's main thread returns.[plain]and[after-module-reload]: the child must exit 0 with noFatal Python error.test_a_stuck_reaper_cannot_wedge_interpreter_exit: a pass that never returns must not hang exit.Finding addressed
Backlog item from the #48 follow-up: "pw-reaper threads in
_store_jobs.pycarry the same 3.10 shutdown hazard as the baseline thread".Fatal Python error: _enter_buffered_busy: could not acquire lock for <_io.BufferedWriter name='<stdout>'> at interpreter shutdown, possibly due to daemon threads.test (3.10)in CI run 38124448503.Evidence that the test catches the hazard
main(a48f06a)[plain]and[after-module-reload]FAIL with the exact_enter_buffered_busyfatal error, rc -6mainmain, standalone repro[after-module-reload]FAIL. An adversarial review found this, and the reload guard was addedThe stuck-reaper test also passes on old code, because a daemon thread never blocked exit there. It guards the new join's bound, not the crash.
Verify (final content, Python 3.12.3)
ruff check . && pytest tests/ -q --cov=passiveworkers --cov-fail-under=75(declared gate)python -m py_compile $(git ls-files '*.py')pyright(blocking forpassiveworkers/net)No UI was touched, so no browser check was needed.
Unverified
print()within the child's 0.3 s sleep. On an extremely slow runner it could pass falsely on old code, but it cannot fail falsely on new code.🤖 Generated with Claude Code