Slice C: discovery deadlock, shared join deadline, shutdown hardening — v0.10.1 - #169
Merged
Conversation
…red join deadline
Two hang fixes from the crash review (slice C):
- GetTests was joined before its queue was drained — the multiprocessing-documented
deadlock: the child cannot exit until its queue's feeder thread flushes to the pipe,
and the pipe blocks at ~64 KB (roughly 400-500 test modules) because nobody reads
until after join(). Run preparation then hung forever with every control disabled.
Reproduced empirically: 50 items exit instantly, 500+ never exit. collect(abort_event)
now owns the wait: drains each poll, honors the abort event (terminate + None), and
returns the sorted list. ControlWindow._build_runner uses it.
- PytestRunner.join(t) applied t per worker thread and then again to the runner thread,
so a wedged pool of N workers held the caller for (N+1) x t — closeEvent on the GUI
thread froze for minutes ("Not Responding", which users end with a kill). The timeout
is now one shared deadline across the started_event wait, every worker, and the
runner thread.
Regression tests: 5000-item discovery completes in seconds (deadlocked before), a
pre-set abort event terminates a wedged discovery promptly, and join(1.0) with three
busy workers returns in ~1 s, not 3+.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZ9TpT9VwBujBq1S3v2Jd
…tch — v0.10.1 - About.shutdown() quits and waits (bounded 10 s) the tab's data QThread; the main window's closeEvent calls it. Destroying a running QThread is a Qt fatal abort (0xC0000409, no Python traceback), and the About worker runs git — closing the app before its data arrived crashed at exit (reproduced during the review). - closeEvent: re-entrancy latch and refresh-timer stop. Its processEvents() calls could deliver a second close request (nested teardown: second dialog, second runner stop) and full _update_tick passes — including reconcile_process_count against a stopping runner — nested inside the teardown. - (With the previous commit) run preparation is now abort-aware at every blocking phase — PUT detection, prior-runner wind-down (bounded loop instead of an uninterruptible join(120)), discovery, and a last side-effect-free abort point before coverage cleanup / RESUME copying — and the prep thread's finished signal is emitted only after a shiboken6.isValid check, stopping the prepared runner instead of emitting into (or orphaning pytest processes behind) a destroyed window. New tests: About.shutdown leaves the thread finished and is idempotent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZ9TpT9VwBujBq1S3v2Jd
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #169 +/- ##
==========================================
- Coverage 85.90% 85.78% -0.13%
==========================================
Files 74 74
Lines 6224 6261 +37
==========================================
+ Hits 5347 5371 +24
- Misses 877 890 +13 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
Summary
Slice C of the crash review: the hangs that read as crashes, and the shutdown-time native aborts. Two of the fixed defects were reproduced empirically during the review.
GetTestsjoined before its queue was drainedGetTests.collect(abort_event)drains while the child runs, honors abort (terminate +None)PytestRunner.join(t)per-thread timeoutcloseEvent(GUI thread) for (N+1)×t — minutes of "Not Responding" ending in a user killQThreaddestroyed while running0xC0000409, no Python traceback. ReproducedAbout.shutdown()(quit + bounded 10 s wait), called fromcloseEventshiboken6.isValid, with the prepared runner stopped instead of orphaned when nobody is left to adopt itcloseEventre-entrancyprocessEvents()calls could deliver a second close (nested teardown: second dialog, second stop) and full_update_tickpasses — includingreconcile_process_countagainst a stopping runner_closinglatch + refresh timer stopped before teardownTests
test_collect_drains_large_result_sets_without_deadlock— 5000-item discovery completes in seconds (hung forever before)test_collect_aborts_wedged_discovery— pre-set abort terminates a wedged child promptlytest_join_timeout_is_a_shared_deadline—join(1.0)with 3 busy workers returns in ~1 s, not 3+test_about_shutdown_stops_data_thread— thread finished, idempotentFull suite: 501 passed.
ruffclean;tyunchanged (13 pre-existing).Behaviour changes
join(t)is now a true overall bound — callers that relied on the accidental per-thread grace get less total waiting (that laxness was the freeze).🤖 Generated with Claude Code
https://claude.ai/code/session_01KEZ9TpT9VwBujBq1S3v2Jd