fix(gui): let the socket layer through, and guard the views it drives - #869
Merged
Merged
Conversation
amule-project#865 stopped amulegui connecting. It guarded CamuleRemoteGuiApp::OnNotifyEvent on the dialog, which is right for the notifications that update the GUI and wrong for the ones that carry the EC socket: MuleNotify has two classes, and DoNotifyAlways() exists precisely because its callers must run whatever the GUI is doing. CoreNotify_LibSocketConnect and friends are how an asio callback reaches the main thread, and amulegui has no dialog until an EC connection has been made -- so dropping them meant the connection could never complete and every attempt ended at the 15 s watchdog. The distinction now travels with the event. HandleNotificationAlways() tags the CMuleGUIEvent it queues, Clone() carries the tag, and both dispatchers run a tagged event regardless of the dialog. The monolithic one gets the same treatment: it has always dropped socket notifications when amuledlg was null, which is far less exposed there (the dialog exists before networking starts) but is wrong in the same way during shutdown. Two GUI notifications are queued through DoNotifyAlways() as well -- DownloadCtrlSort and DownloadCtrlDoItemSelectionChanged -- so the exemption reached them too and the original crash came straight back. Both read theApp->amuledlg->m_transferwnd, which is the 0x3c0 in the reports. They now test the dialog themselves; the nine socket handlers on that path touch no GUI at all, checked one by one rather than assumed. That is still only one of the two ways a view is reached with no dialog. The remote GUI's container layer calls into the views directly from EC replies, bypassing MuleNotify entirely, and a reply can land while the dialog is gone for the same reason: it is destroyed when the link drops and rebuilt by Startup() after the next connect, and replies in flight are not drained. An audit of the whole tree found 26 such calls across 11 reply-driven handlers in 8 classes -- HandlePacket, ProcessUpdate, ProcessItemUpdate, ProcessItemUpdatePartfile, CreateItem, DeleteItem, ApplySearchProgress. Each is guarded so the container still updates its model and only the view call is skipped; the model has to keep tracking the daemon whether or not anyone is looking at it. Where a flag already gated the work (batchDownloadList, batchSharedList) the test folded into it rather than nesting another branch. The views themselves are not exposed. A control is a child of the dialog and cannot outlive it, so the derefs in DownloadListCtrl, SearchListCtrl, SharedFilesCtrl, GenericClientListCtrl and FriendListCtrl are safe, and the wxDataView port did not introduce this. Nor are the dialogs, the tray icon (RemoveSystray() runs before the dialog is destroyed) or the core sources.
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.
Repairs a regression I introduced in #865, and then fixes the rest of the bug that PR was aiming at.
What #865 broke
It guarded
CamuleRemoteGuiApp::OnNotifyEventon the dialog. That is right for notifications that update the GUI and wrong for the ones carrying the EC socket:MuleNotifyhas two classes, andDoNotifyAlways()exists precisely because its callers must run whatever the GUI is doing.CoreNotify_LibSocketConnectand friends are how an asio callback reaches the main thread, and amulegui has no dialog until an EC connection has been made — so dropping them meant the connection could never complete, and every attempt ended at the 15-second watchdog.The distinction now travels with the event:
HandleNotificationAlways()tags theCMuleGUIEvent,Clone()carries the tag, and both dispatchers run a tagged event regardless of the dialog. The monolithic dispatcher gets the same treatment — it has always dropped socket notifications whenamuledlgwas null, far less exposed there since the dialog exists before networking starts, but wrong in the same way during shutdown.The exemption wasn't enough on its own
Two GUI notifications are queued through
DoNotifyAlways()as well —DownloadCtrlSortandDownloadCtrlDoItemSelectionChanged— so the exemption reached them and the original crash came straight back. Both readtheApp->amuledlg->m_transferwnd, which is the0x3c0in the crash reports. They now test the dialog themselves. The nine socket handlers sharing that path touch no GUI at all — checked individually rather than assumed, which is the mistake that produced this iteration.The other half of the bug
Guarding the notification path was never going to be sufficient. The remote GUI's container layer calls into the views directly from EC replies, bypassing
MuleNotifyentirely, and a reply can land while the dialog is gone for the same reason: it is destroyed when the link drops and rebuilt byStartup()after the next connect, with replies in flight not drained.An audit of the tree found 26 such calls across 11 reply-driven handlers in 8 classes —
HandlePacket,ProcessUpdate,ProcessItemUpdate,ProcessItemUpdatePartfile,CreateItem,DeleteItem,ApplySearchProgress.CKnownFilesRem::ProcessItemUpdatePartfilecallsm_transferwnd->downloadlistctrl->UpdateItem(), so a crash there is indistinguishable from the notification one in a report — same offset, same signature.Each is guarded so the container still updates its model and only the view call is skipped; the model has to keep tracking the daemon whether or not anyone is looking at it. Where a flag already gated the work (
batchDownloadList,batchSharedList) the test folded into it rather than nesting another branch.What the audit cleared
Not exposed, with reasons rather than assumption:
DownloadListCtrl,SearchListCtrl,SharedFilesCtrl,GenericClientListCtrl,FriendListCtrl,SearchListModel. A control is a child of the dialog and cannot outlive it. The port did not introduce this class of bug.MuleTrayIcon—DlgShutDown()runsRemoveSystray()before the dialog is destroyed.PrefsUnifiedDlg,CatDialog,ChatWnd,TransferWnd) — user-interaction entry points; the dialog exists by construction.BaseClient.cpp,amule.cpp) — monolithic only, where the dialog is built before networking starts.Guard detection was function-scoped rather than a line window, which is what separated 223 raw matches from the 26 real ones.
Verification
Builds clean on macOS (monolithic + amulegui + amuled), no warnings from the touched files; clang-format and both clang-tidy tiers clean over the diff.
Connecting and quitting have each failed once during this work, so both want exercising against a live core, along with a core restart mid-transfer — that is when
ProcessUpdateandProcessItemUpdatePartfileare busiest.