gamescope +pfhdr7 — linger no longer dies of its own capture teardown (luxus's fix, overlay#9) #212

Merged
enricobuehler merged 1 commits from worktree-gamescope-linger-pw-destroy-race into main 2026-08-13 22:15:41 +00:00
Owner

Ports the fix from luxus/punktfunk-overlay#9 into our gamescope patch set as patch 0009 (his authorship kept).

The bug

On client disconnect the host keeps the headless gamescope lingering so a reconnect resumes the same session — and gamescope could SIGSEGV in exactly that window, so the kept display was dead and reconnect silently got a fresh compositor with the game lost. Journal signature: linger line → coredump → kept display was dead — recreating.

Root cause (luxus's diagnosis, verified against our tree): stream_handle_remove_buffer — and the stale-push path in dispatch_nudge — destroyed idle capture buffers on the PipeWire thread. Dropping the last CVulkanTexture reference there calls into the Vulkan driver while steamcompmgr can still be inside vulkan_screenshot on another buffer of the same 4-buffer pool; on NVIDIA the race lands as a SIGSEGV in CVulkanCmdBuffer::insertBarrier. It fires at stream end, which is why it selectively killed linger.

Our 0007 does not cover this — same file, different bug (the user_data use-after-free on renegotiation). Crash #1 in the issue's coredump table (~CVulkanDevice during exit) is 0006's already-fixed static-destruction bug, not this race.

The fix

The PipeWire thread only queues the corpse (bury_buffer, mutex-guarded); steamcompmgr reaps on every vblank, including while the stream is paused — the linger state itself. Field-proven on the reporter's NVIDIA host: 4 coredumps in one evening (BG3, 4K60 HDR, --pipewire-composite-cursor), zero after; disconnect/reconnect confirmed live (2026-08-13) to reuse the lingered session.

Ours differs from the overlay's original only by the meson.build banner hunk: +pfhdr6+pfhdr7, PKGBUILD 3.16.25.pfhdr7-1. No new capability — same rule as pfhdr5/6: "reconnect lost my game" triage has to read a box's exposure off its banner, and every host probe is >=. Known residual, deliberately untouched: add_buffer's error path still deletes on the PW thread (reachable only when an add fails mid-renegotiation, which no field coredump shows) — the patch stays byte-identical with what was proven on-glass.

Verification

  • Full 0001..0009 series applies onto the bare 5fb8dce4 pin with plain git am (the build script's exact invocation, no -3, no fuzz).
  • Full build in the fc44 CI image (punktfunk-fedora44-rpm, rpm.yml's exact dep recipe): green, banner 3.16.25-20-g40fe8b5+pfhdr7 (gcc 16.1.1).
  • After 0009, destroy_buffer has exactly two callers — pipewire_reap_dead_buffers (vblank) and pipewire_destroy_buffer (copy-completion) — both on the compositor thread.
  • Nix, deb, rpm and sysext all glob patches/*.patch and read the level off the banner, so no other packaging file moves.

Note: #210 also touches packaging/gamescope/ files — worst case is a trivial comment-block merge in the PKGBUILD, whichever lands second.

Ports the fix from [luxus/punktfunk-overlay#9](https://github.com/luxus/punktfunk-overlay/issues/9) into our gamescope patch set as **patch 0009** (his authorship kept). ## The bug On client disconnect the host keeps the headless gamescope **lingering** so a reconnect resumes the same session — and gamescope could SIGSEGV in exactly that window, so the kept display was dead and reconnect silently got a fresh compositor with the game lost. Journal signature: linger line → coredump → `kept display was dead — recreating`. Root cause (luxus's diagnosis, verified against our tree): `stream_handle_remove_buffer` — and the stale-push path in `dispatch_nudge` — destroyed idle capture buffers on the **PipeWire thread**. Dropping the last `CVulkanTexture` reference there calls into the Vulkan driver while steamcompmgr can still be inside `vulkan_screenshot` on another buffer of the same 4-buffer pool; on NVIDIA the race lands as a SIGSEGV in `CVulkanCmdBuffer::insertBarrier`. It fires at stream **end**, which is why it selectively killed linger. Our 0007 does **not** cover this — same file, different bug (the `user_data` use-after-free on renegotiation). Crash #1 in the issue's coredump table (`~CVulkanDevice` during `exit`) is 0006's already-fixed static-destruction bug, not this race. ## The fix The PipeWire thread only queues the corpse (`bury_buffer`, mutex-guarded); steamcompmgr reaps on every vblank, **including while the stream is paused** — the linger state itself. Field-proven on the reporter's NVIDIA host: 4 coredumps in one evening (BG3, 4K60 HDR, `--pipewire-composite-cursor`), zero after; disconnect/reconnect confirmed live (2026-08-13) to reuse the lingered session. Ours differs from the overlay's original only by the `meson.build` banner hunk: **`+pfhdr6` → `+pfhdr7`**, PKGBUILD `3.16.25.pfhdr7-1`. No new capability — same rule as pfhdr5/6: "reconnect lost my game" triage has to read a box's exposure off its banner, and every host probe is `>=`. Known residual, deliberately untouched: `add_buffer`'s error path still deletes on the PW thread (reachable only when an add *fails* mid-renegotiation, which no field coredump shows) — the patch stays byte-identical with what was proven on-glass. ## Verification - Full `0001..0009` series applies onto the bare `5fb8dce4` pin with plain `git am` (the build script's exact invocation, no `-3`, no fuzz). - Full build in the fc44 CI image (`punktfunk-fedora44-rpm`, rpm.yml's exact dep recipe): green, banner `3.16.25-20-g40fe8b5+pfhdr7 (gcc 16.1.1)`. - After 0009, `destroy_buffer` has exactly two callers — `pipewire_reap_dead_buffers` (vblank) and `pipewire_destroy_buffer` (copy-completion) — both on the compositor thread. - Nix, deb, rpm and sysext all glob `patches/*.patch` and read the level off the banner, so no other packaging file moves. Note: #210 also touches `packaging/gamescope/` files — worst case is a trivial comment-block merge in the PKGBUILD, whichever lands second.
enricobuehler added 1 commit 2026-08-13 22:14:49 +00:00
fix(packaging/gamescope): +pfhdr7 — linger no longer dies of its own capture teardown
ci / rust-arm64 (pull_request) Successful in 2m5s
ci / bun-nix (pull_request) Successful in 5m28s
ci / web (pull_request) Successful in 7m2s
ci / docs-site (pull_request) Successful in 8m0s
ci / rust (pull_request) Successful in 16m28s
0f64551c56
Patch 0009, reported, written and proven live by luxus (punktfunk-overlay#9): when the capture
consumer leaves, stream_handle_remove_buffer — and the stale-push path in dispatch_nudge —
destroyed idle buffers on the PipeWire thread. Dropping the last CVulkanTexture reference there
calls into the Vulkan driver (vkDestroyImage / FreeMemory / dmabuf fds) while steamcompmgr can
still be inside vulkan_screenshot on another buffer of the same 4-buffer pool; on NVIDIA the race
lands as a SIGSEGV in CVulkanCmdBuffer::insertBarrier. The timing is what made it selectively
lethal: it fires at stream END — exactly the window where the host keeps the headless display
lingering for a reconnect. So the kept display was already dead (journal: linger line → coredump →
"kept display was dead — recreating") and the "resumed" session was a fresh compositor with the
game lost.

The fix queues the corpses (bury_buffer, mutex-guarded) and steamcompmgr reaps them on every
vblank, including while the stream is only paused — the linger state itself. Field-proven on the
reporter's NVIDIA host: 4 coredumps in one evening of BG3 at 4K60 HDR with --pipewire-composite-
cursor (the heaviest paint path we ship), zero after; disconnect/reconnect confirmed live to reuse
the lingered session (2026-08-13). Three of the four stacks are this race; the fourth
(~CVulkanDevice during exit) is patch 0006's already-fixed static-destruction bug — do not
re-diagnose it as part of this.

Ours differs from the overlay's original only by the meson.build banner hunk: +pfhdr6 → +pfhdr7,
PKGBUILD 3.16.25.pfhdr7-1. No new capability — same rule as pfhdr5/6: "reconnect lost my game"
triage has to read a box's exposure off its banner, and every probe is >=. Known residual,
deliberately untouched: add_buffer's error path still deletes on the PW thread. By the later
`goto error`s a texture may be attached, so the race is reachable there in theory — but only when
an add FAILS mid-renegotiation, which no field coredump shows; the patch stays byte-identical with
what was proven on-glass.

Verified: the full 0001..0009 series applies onto the bare 5fb8dce4 pin with plain `git am` (the
build script's own invocation, no -3, no fuzz) and with `git am -3`; the fc44 CI image
(punktfunk-fedora44-rpm) builds the result with rpm.yml's exact dep recipe to a binary whose
banner reads `3.16.25-20-g40fe8b5+pfhdr7 (gcc 16.1.1)`. After 0009, destroy_buffer has exactly
two callers left —
pipewire_reap_dead_buffers (steamcompmgr vblank) and pipewire_destroy_buffer (steamcompmgr's
copy-completion path) — both on the compositor thread. Nix, deb, sysext and rpm all glob
patches/*.patch and read the level off the banner, so no other packaging file moves.
enricobuehler merged commit 4caf2b76e8 into main 2026-08-13 22:15:41 +00:00
enricobuehler deleted branch worktree-gamescope-linger-pw-destroy-race 2026-08-13 22:15:43 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#212