fix(gui): delete the calculation worker on the GUI thread to stop a freeze - #15
Merged
Merged
Conversation
…reeze When a generated signal finished calculating, its worker deleted itself on its own thread (finished -> deleteLater). The worker's destructor holds a Qt signal-slot mutex while it waits for the Python GIL, and the GUI thread, holding the GIL while it refreshed the signal tree and plot, could wait for that same mutex. Both threads then waited forever: the app froze, and the test suite hung intermittently in test_calculated_signal_gui.py. The worker is no longer deleted on its own thread. _cleanup_calculation drops the last reference once the thread has stopped, so it is deleted on the GUI thread. A new test checks the worker is deleted on the GUI thread, for a finished and a failed calculation; both fail with the old wiring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
The app could freeze when a generated signal finished calculating, and the test suite hung intermittently in
tests/test_calculated_signal_gui.py(it stalled a pre-commit run for over 10 minutes).When a calculation finished, its worker deleted itself on its own thread (
finished → deleteLater). The worker's destructor holds a Qt signal-slot mutex while it waits for the Python GIL. At the same moment the GUI thread, holding the GIL, refreshes the signal tree and plot, and those Qt calls can wait for the same mutex (Qt shares a small pool of these mutexes by object address). Both threads then wait forever.Native stack dumps of a frozen process confirmed it: the GUI thread was waiting on a Qt mutex in
QTreeView::expandToDepth(or inQObject::disconnectfrom a plot resize), while the worker thread sat inQObject::~QObject→PyGILState_Ensure.Changes
gui/main_window.py: the worker is no longer deleted on its own thread._cleanup_calculationdrops the last reference after the thread has stopped, so the worker is deleted on the GUI thread, which already holds the GIL.tests/test_calculated_signal_gui.py: new testtest_calculation_worker_is_deleted_on_the_gui_thread, for a finished and a failed calculation.Testing
deleteLaterconnections restored, both new cases fail (the worker is deleted on the worker thread).Protected files
None. Only the generated-signal calculation code in
gui/main_window.pychanged; the Load + Decode wiring is untouched.Not changed: the load worker's cleanup uses a similar pattern, but its window for this race is much narrower and it is in the protected load path.
🤖 Generated with Claude Code