frontend: Fix crash in application shutdown with a YouTube service dock active #13056
+214
−81
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
Fixes a crash in application shutdown with a YouTube service active by explicitly removing the dock before the main CEF browser instance used by the application is destroyed.
Motivation and Context
Currently all docks in the application are destroyed through the explicit destruction of their owner objects. This applies to most service-based docks that are owned by an
Authobject, which itself is explicitly destroyed by having its pointerresetduring shutdown, and user-created "extra" browser docks, which are explicitly destroyed viaClearExtraBrowserDocks.The global YouTube app dock which is created without an established service connection (but only when YouTube is selected as an output destination) is not handled by any code during shutdown.
Fixes #12920.
Investigation of the Crash
This will result in a crash via the following process:
OBSBasic::closeWindow- runs in one way or another (either triggered directly by aQActionor indirectly by the platform's terminate signalQWidget::deleteLater- called on theOBSBasicinstance, scheduling aDeferredDeleteon the active event loopOBSBasic::~OBSBasic- triggered by the actualdeletecall in the event handlerstd::default_delete<QList<std::shared_ptr<QDockWidget>>>- triggered by the destruction of the owningOBSBasicinstanceQDockWidget::~QDockWidget- triggered by the destruction of the owningQListYouTubeAppDock::~YouTubeAppDock- triggered by the destructor ofQDockWidgetQCefWidgetInternal::~QCefWidgetInternal- triggered by the destructor ofYouTubeAppDockQCefWidgetInternal::closeBrowser- triggered by the class's destructorQEventLoop::exec- runs a nested event loop, blocking continuation of thecloseBrowserfunction to wait 1s for CEF to signal successful shutdown[NSApp terminate:nil]- triggered by aQCocoaIntegration::Quitsignal that originates fromOBSBasic::closeWindowviaQMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection). This event is now handled by the nested event loop.QWindowSystemInterface::handleApplicationTermination()- triggered by theterminateAppKit call and emits theaboutToQuitsignalOBSBasic::~OBSBasic- triggered by the anonymous lambda connected to theaboutToQuitsignal and its explicitdeleteof theOBSBasicinstanceThe nested destructor call leads to destructor calls of child objects that have been deallocated already, thus the app crashes.
This issue is also another example of the inherent dangers of using
execin Qt (something the project relies on far too much):OBSBasic::closeWindow:OBSBasic::deleteLaterOBSApp::QuitQCefWidgetInternal::closeBrowserwill continue consuming all scheduled events in the loop, which will either immediately or very quickly be theQuitevent.deleteLaterevent, even though its event handler hasn't finished yetMitigation of the Crash
After an initial attempt to fix the crash by ensuring a specific order of events did not work when the application shutdown was initiated by AppKit's
terminateevent, the second commit was completely rewritten.The following changes are made to the application code:
[NSApp terminate]and thus creates the same sequence of events as if the app had been terminated by macOS (either because the OS shuts down/reboots or because it's been terminated via the Dock or Finder).In combination these changes reduce the number of different code paths taken during shutdown:
Either way a "close" event to the main window happens before the event loop is terminated and the application instance is torn down (either directly, or indirectly via Qt's "closeAllWindows" function in response to "terminate"). The order of events thus is always:
How Has This Been Tested?
Successfully shut down the app with open YouTube app docks via:
SIGABRT,SIGQUIT,SIGINT, andSIGTERMto the applicationTypes of changes
Checklist: