Repository navigation
Stop the video-switch freeze: async mpv stop, no sync reads per event - #36
Merged
Merged
Conversation
Switching away from a video wallpaper froze every plasmashell window
for ~200 ms (QSG_RENDER_TIMING). Two synchronous waits on the mpv core:
- ~MpvRender runs mpv_render_context_free on the Qt render thread
during the scenegraph sync (GUI thread blocked), and that call waits
for the core to tear its video output down.
- onMpvEvents answered every observed-property event with two
mpv_get_property calls ("idle-active", "pause") on the GUI thread,
which block while the core is busy, i.e. exactly during a teardown.
onMpvEvents now takes both values from the MPV_EVENT_PROPERTY_CHANGE
payload (both are observed as MPV_FORMAT_FLAG) and keeps the last seen
pair, so an event never calls back into the core.
MpvObject::stopAsync() detaches the owner from mpv's wakeup callback
(MpvHandle::detachOwner, the first half of beginShutdown) and issues an
async "stop". Mpv.qml exposes it as prepareDestroy(), and the handoff's
fade-out calls that on the outgoing backend and destroys it 400 ms
later: by then mpv has dropped its video output, and
mpv_render_context_free no longer waits. Backends without
prepareDestroy are destroyed right away, as before.
tst_mpvbackend covers stopAsync: it returns without waiting on the
core, an observed-property change after it no longer reaches the
object (with a control run showing the same change does without it),
and repeated / uninitialised calls are safe.
Stacked on "Keep the old wallpaper on screen until the new one has
drawn" (the fade-out that calls prepareDestroy).
The async mpv stop (Mpv.qml prepareDestroy) was only called from outgoingFade.onFinished in main.qml. The other places that destroy a backend still did it synchronously and brought back the render-thread stall on mpv_render_context_free. - main.qml: add backendLoader.retire(backend), which calls prepareDestroy() when the backend has one, then destroys it (delayed 400 ms in that case). outgoingFade.onFinished, Component.onDestruction, a fade cut short by dropOutgoing(), a backend that never drew, and an outgoing backend being replaced all go through it. - tests/qml/_stubs Mpv.qml: add stopAsync() and a stopAsyncCount recorder; prepareDestroy() calls it and would throw on the stub. Mpv row in the stubs README updated. - tst_main_handoff.qml: one test per destroy path, using a backend stand-in that records prepareDestroy() calls. - tst_backend_mpv.qml: prepareDestroy() reaches the player's stopAsync().
CaptSilver
approved these changes
Oct 13, 2026
CaptSilver
approved these changes
Oct 13, 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.
Depends on #35 (the handoff's fade-out is what calls
prepareDestroy). Until that one is merged, this PR's diff includes its commit.Problem
Switching away from a video wallpaper froze every plasmashell window for ~200 ms (
QSG_RENDER_TIMING, on a build based on an oldermain, 5c3d184; the teardown code below is the same on today'smain). There are two synchronous waits on the mpv core:~MpvRendercallsmpv_render_context_freeon the render thread during the sync (GUI thread blocked). That call waits for the core to tear down its video output.onMpvEventsanswered every observed-property event with twompv_get_propertycalls on the GUI thread. Those block while the core is busy, which is exactly the case during a teardown.Change
onMpvEventstakesidle-active/pausefrom theMPV_EVENT_PROPERTY_CHANGEpayload (both are observed asMPV_FORMAT_FLAG) and keeps the last pair.MpvObject::stopAsync()does two things: it detaches the owner (MpvHandle::detachOwner, the first half ofbeginShutdown) and issues an asyncstop.Mpv.qmlexposes it asprepareDestroy(). The fade-out calls that, then destroys the backend 400 ms later, whenmpv_render_context_freeno longer waits.Tests
tst_mpvbackendchecks that:stopAsyncdoesn't wait on the core;tools/scripts/preflight.shpasses (run inside afedora-toolbox:latestcontainer built fromDEPS_FEDORA,WEK_IN_CI=1). One note: on this laptop the fuzz step times out on the checked-in seedtests/fuzz_corpus/WPShaderCompile/seed/58392d50…(~33 s per compile under ASAN+UBSAN, identical on cleanmain), so I ran preflight with--no-fuzzand then all fuzz targets with the same settings except-timeout=60: no findings.Setup: Plasma 6 (Wayland), KWin, Intel Iris Xe (RPL-P), 2520x1680 @ 120 Hz laptop panel, Manjaro.