Repository navigation
fix(shutdown): wake and join websocket thread before widget teardown - #367
Merged
Merged
Conversation
1 of 4 tasks
RobeMartins
approved these changes
Oct 23, 2026
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.
Description
Stopping the service sometimes ends in
status=11/SEGVright afterFinal exit cleanup(2 of 7 stops on the .151 dev box, 2026-10-20). The cause is the websocket thread.ws.close()frees the socket while the websocket-client dispatcher is blocked in epoll withping_timeout(10 s), so the thread wakes up to 10 s later. It then runson_close, which emits Qt signals while sip is destroying the widget tree at interpreter exit. This PR wakes the dispatcher withshutdown(SHUT_RD), joins it before teardown, and silences the websocket callbacks once shutdown starts.Changes
_closingflag set inwb_disconnect;on_open,on_message,on_closeandreconnectreturn early once it is setwb_disconnectstops the klippy status poll, setskeep_running=Falseand shuts the raw socket's read side so the dispatcher leaves epoll, then joins it (falls back tows.close()on OSError)shutdown()joins UpdaterAsyncLoop for up to 2 son_quitlogs the threads still alive at final cleanupMotivation
Repro script (websocket-client 1.9.0, close during an idle connection): 12 of 30 runs left the thread alive past the join, 0 of 90 with this change. The
_closingguard follows KlipperScreen's KlippyWebsocketclosingflag and the USBManager_closingfix from #359. Qt still delivers signals that were already queued, so blocking new callbacks is the only reliable guard. PyQt6 destroys wrapped widgets in sip's atexit hook, after Python's thread join, so a daemon thread that is still running at that point can emit into freed objects.Tests
TestShutdown(dispatcher woken + poll stopped, close fallback, callbacks silent after disconnect, real-socket thread exits within the join over 8 runs) andtest_shutdown_joins_loop_threadFuture work