Fix a livelock that can occur between SDL, libdecor-gtk, and Qt

The Qt UI continuously redrawing prevents libdecor-gtk's glib dispatch loop
from ever returning since it constantly posts new messages to draw again.

The result is that Moonlight randomly hangs (with the spinner still animating)
at the "Starting app..." phase of connection establishment and never proceeds
until the livelock is broken by loss of focus, hiding the window, or similar.

For posterity, the hanging stack:

QtWaylandClient::QWaylandWindow::waitForFrameSync (this=this@entry=..., timeout=timeout@entry=...) at qwaylandwindow.cpp:891
QtWaylandClient::QWaylandGLContext::swapBuffers (this=..., surface=...) at qwaylandglcontext.cpp:494
QRhiGles2::endFrame (this=..., swapChain=..., flags=...) at qrhigles2.cpp:2390
QRhi::endFrame (this=..., swapChain=..., flags=...) at qrhi.cpp:11600
QSGGuiThreadRenderLoop::renderWindow (this=..., window=...) at qsgrenderloop.cpp:714
QQuickWindow::event (this=..., event=...) at qquickwindow.cpp:1663
QCoreApplication::notifyInternal2 (receiver=..., event=...) at qcoreapplication.cpp:1126
QCoreApplication::sendEvent (receiver=..., event=...) at qcoreapplication.cpp:1571
QPlatformWindow::deliverUpdateRequest (this=...) at qplatformwindow.cpp:831
QObject::event (this=..., e=...) at qobject.cpp:1480
QCoreApplication::notifyInternal2 (receiver=..., event=...) at qcoreapplication.cpp:1126
QCoreApplication::sendEvent (receiver=..., event=...) at qcoreapplication.cpp:1571
QCoreApplicationPrivate::sendPostedEvents (receiver=..., event_type=..., data=...) at qcoreapplication.cpp:1926
postEventSourceDispatch (s=...) at qeventdispatcher_glib.cpp:248
g_main_dispatch (context=...) at gmain.c:3591
g_main_context_dispatch_unlocked (context=...) at gmain.c:4451
g_main_context_iterate_unlocked (context=context@entry=..., block=block@entry=..., dispatch=dispatch@entry=..., self=...) at gmain.c:4516
g_main_context_iteration (context=..., context@entry=..., may_block=may_block@entry=...) at gmain.c:4582
libdecor_plugin_gtk_dispatch (plugin=..., timeout=...) at libdecor-gtk.c:509
Wayland_PumpEvents (_this=...) at SDL_waylandevents.c:610
Wayland_GLES_LoadLibrary (_this=..., path=...) at SDL_waylandopengles.c:51
SDL_GL_LoadLibrary_REAL (path=...) at SDL_video.c:4735
SDL_RecreateWindow (window=..., flags=...) at SDL_video.c:2796
GLES2_CreateRenderer (renderer=..., window=..., create_props=...) at SDL_render_gles2.c:2329
SDL_CreateRendererWithProperties_REAL (props=props@entry=...) at SDL_render.c:1126
SDL_CreateRenderer_REAL (window=..., name=...) at SDL_render.c:1287
SDL_CreateRenderer_REAL (window=..., idx=..., flags=...) at sdl2_compat.c:5986
EGLRenderer::initialize (this=..., params=...) at eglvid.cpp:445
FFmpegVideoDecoder::initializeRendererInternal (this=..., renderer=..., params=...) at ffmpeg.cpp:323
FFmpegVideoDecoder::createFrontendRenderer (this=..., params=..., useAlternateFrontend=...) at ffmpeg.cpp:426
FFmpegVideoDecoder::completeInitialization (this=..., decoder=..., requiredFormat=..., params=..., testMode=..., useAlternateFrontend=...) at ffmpeg.cpp:486
FFmpegVideoDecoder::tryInitializeRenderer(AVCodec const*, AVPixelFormat, _DECODER_PARAMETERS*, AVCodecHWConfig const*, IFFmpegRenderer::InitFailureReason*, std::function<IFFmpegRenderer* ()>)
    (this=..., decoder=..., requiredFormat=..., params=..., hwConfig=..., failureReason=..., createRendererFunc=...) at ffmpeg.cpp:1200
FFmpegVideoDecoder::tryInitializeHwAccelDecoder (this=..., params=..., pass=..., terminallyFailedHardwareDecoders=...) at ffmpeg.cpp:1544
FFmpegVideoDecoder::initialize (this=..., params=...) at ffmpeg.cpp:1701
Session::chooseDecoder (vds=..., renderer=..., window=..., videoFormat=..., width=..., height=..., frameRate=..., enableVsync=..., enableFramePacing=..., testOnly=..., chosenDecoder=...) at session.cpp:322
Session::getDecoderAvailability (window=..., vds=..., videoFormat=..., width=..., height=..., frameRate=...) at session.cpp:498
Session::initialize (this=..., qtWindow=...) at session.cpp:755
This commit is contained in:
Cameron Gutman
2026-09-10 21:00:57 -05:00
parent 14c26d8c7d
commit e3fd29e4d7
+5 -13
View File
@@ -126,22 +126,9 @@ Item {
SystemProperties.waitForAsyncLoad()
// Kick off the stream
spinnerTimer.start()
streamLoader.active = true
}
Timer {
id: spinnerTimer
// Display the spinner appearance a bit to allow us to reach
// the code in Session.exec() that pumps the event loop.
// If we display it immediately, it will briefly hang in the
// middle of the animation on Windows, which looks very
// obviously broken.
interval: 100
onTriggered: stageSpinner.visible = true
}
Timer {
id: startSessionTimer
onTriggered: {
@@ -178,6 +165,11 @@ Item {
return;
}
// This spinner is shown only after session.initialize() has completed
// to prevent active animations from running during decoder probing,
// which causes re-entrant event loop livelocks with libdecor-gtk.
stageSpinner.visible = true
// Don't wait unless we have toasts to display
startSessionTimer.interval = 0