fix(system_tray): dispatch tray updates asynchronously to avoid blocking callers (#4199)#5045
Closed
moimart wants to merge 1 commit intoLizardByte:masterfrom
Closed
fix(system_tray): dispatch tray updates asynchronously to avoid blocking callers (#4199)#5045moimart wants to merge 1 commit intoLizardByte:masterfrom
moimart wants to merge 1 commit intoLizardByte:masterfrom
Conversation
de87207 to
33c85ec
Compare
…ing callers (LizardByte#4199) update_tray_*() functions call tray_update(), which on Linux blocks the caller inside pthread_cond_wait until the tray library's GTK main-loop callback runs. That callback calls libayatana-appindicator and libnotify synchronously. If the desktop notification daemon is unresponsive — a common failure mode on Wayland compositors (swaync/dunst/Quickshell) during desktop transitions, lock screens, or daemon restarts — the callback blocks indefinitely and every caller of update_tray_* blocks with it. One of those callers is stream::session::join(), which arms a 10-second NVENC-deadlock watchdog. When the tray update doesn't return in time, the watchdog fires debug_trap() and the entire sunshine process dies with SIGTRAP. Reported as LizardByte#4199. Fix: spawn a detached worker thread in each update_tray_* entry point. The worker serializes on tray_async_mutex and runs the original update body. The caller returns immediately. If the notification daemon is hung, the worker stays blocked until the daemon recovers or the process exits, but session teardown — and all other callers of update_tray_* — complete promptly. Also replaces the 'static std::string msg = std::format(...)' pattern with persistent string buffers living in a Meyers-singleton state object; the prior static-local-initialization idiom captured only the first app_name on first call and then reused it forever, a latent bug unrelated to the SIGTRAP but worth fixing while we're here. Fixes LizardByte#4199
33c85ec to
57af5ff
Compare
|
❌ The last analysis has failed. |
Member
|
Did you test the current branch to see if it's fixed? Because there were significant changes to the tray library. Other's reported it's now working for them. Second, tray is being migrated in #4907 |
Author
Ohh. Didn't realize about that one.. then indeed ignore this. Thanks! |
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.
Summary
update_tray_*()functions insrc/system_tray.cppcalltray_update(), which on Linux blocks the caller insidepthread_cond_waituntil the tray library's GTK main-loop callback runs. That callback callslibayatana-appindicatorandlibnotifysynchronously. If the active desktop notification daemon is unresponsive — a common failure mode on Wayland compositors (swaync/dunst/Quickshell) during desktop transitions, lock screens, or daemon restarts — the callback blocks indefinitely and every caller ofupdate_tray_*blocks with it.One of those callers is
stream::session::join(), which arms a 10-second NVENC-deadlock watchdog. When the tray update doesn't return in time, the watchdog firesdebug_trap()and the entire sunshine process dies withSIGTRAP. Reported as #4199.Before: `session::join() → update_tray_pausing() → tray_update() → pthread_cond_wait → tray_update_internal → notify_notification_show → GDBus blocked on a hung notification daemon → 10 s → debug_trap() → process dies.**
After:
session::join() → update_tray_pausing() → spawn detached worker → return. session teardown completes. The worker stays blocked until the daemon recovers; if the process exits first, the thread is cleaned up by the OS.Change
run_tray_async(fn)that spawns a detachedstd::thread, takestray_async_mutex, and runsfn. Catches exceptions so a runaway tray/notify library call can't kill the worker. No-ops cleanly iftray_initializedis false at post time or at worker start time (race window).tray_async_mutexserializes mutation of the sharedtraystruct across workers.std::stringbuffers (playing_msg_buf,pausing_msg_buf,stopped_msg_buf) replace thestatic std::string msg = std::format(...)pattern. The old pattern only evaluated the format expression the first time each function ran, permanently capturing the firstapp_name; this also fixes that latent bug.update_tray_*body moves verbatim into a lambda passed torun_tray_async.What stays the same
src/system_tray.h). No call-site changes insrc/stream.cpp,src/main.cpp,src/nvhttp.cpp,src/process.cpp.tray_thread_worker+process_tray_eventsusingtray_loop(1)) is untouched — fix(tray): use the blocking event loop to avoid wasting power #4457's power optimization is preserved.session::join()NVENC-deadlock watchdog is untouched — it exists for a separate, legitimate NVIDIA-driver issue, and remains the intended failsafe. The fix just prevents a non-critical tray subsystem from tripping it.tray_update()on those platforms isn't known to block, but routing through the async worker costs essentially nothing and eliminates any possibility of blocking the caller.Trade-offs / caveats
tray_async_mutex+tray_update's internal cond var. Memory impact is bounded by stream state transitions per session.std::threadconstruction can throw on resource exhaustion; this is caught and logged (warning) and the update is dropped. Dropping a tray icon update is preferable to surfacing the error on session teardown.Test plan
stream::session::joindue to deadlock with system tray/notification thread on Wayland #4199 locally on CachyOS / Hyprland (Wayland, Quickshell-based notification daemon) with the upstreamv2025.924.154138tag: cluster ofSIGTRAPcrashes on Moonlight disconnect,coredumpctlshowedraise→pthread_oncein the tray handler path.Fixes #4199