cli & replayer: Prevent Linux worker process orphaning with race-free PR_SET_PDEATHSIG - #305
cli & replayer: Prevent Linux worker process orphaning with race-free PR_SET_PDEATHSIG#305louzt wants to merge 7 commits into
Conversation
When Fossilize is forcefully terminated or crashes, the child replay processes currently become orphans and continue to compile shaders in the background, leading to excessive CPU/RAM usage. This adds PR_SET_PDEATHSIG to ensure child processes are terminated immediately when the parent process dies, matching the expected lifecycle of the application.
…PDEATHSIG and CTest coverage
…tion-test for CI robustness
…ns for multi-GPU filtering
…rctl return checks The predicate that backs --device-pci-vendor / --device-pci-device selection moves from an inline expression in VulkanDevice::init_device into a single static helper on VulkanDevice::pci_filter_matches. Both the CLI selection loop and the gpu_selection_test now exercise the same logic, eliminating the risk of the test drifting from the production path. Also defensively check the return value of prctl(PR_SET_PDEATHSIG, SIGKILL) in both Linux fork sites (cli/fossilize_replay_linux.hpp and fossilize_external_replayer_linux.hpp), aborting the child with _exit(EXIT_FAILURE) on syscall failure instead of continuing as if the binding succeeded. The gpu_selection_test gains a synthetic NVIDIA / AMD / Intel catalogue and seven cases covering vendor-only, vendor+device, mismatched-device, unset-filter, middle-slot match, last-entry match, and mixed-vendor rejection. Replaces the prior 21-line smoke test. All 8 ctest pass on this host (1.94 s).
|
This overall has a hint of LLM spam that I don't want to engage with, but the prctl idea makes sense. I merged the prctl part through a squashed down commit on master. For device filtering, that could be a different PR since it's completely unrelated, but the added test coverage is somewhat overzealous and not needed unless I ask for it. |
|
Thanks for pulling the To provide context on why I structured the PR this way, i really spent time debugging this background process leak on my local hybrid laptop setup (iGPU + dGPU) alongside the community reports/issues open i found useful later on that day, because this sintomps are kinda uncomfortable to deal with as user. From an outsider perspective trying to deliver this as complete as i can like a production ready fix for end users/or to ppl like you to help me solve it better, so bundling the kernel teardown, PCI device filters, and synthetic tests felt like the most thorough and respectful approach understanding what the Company behind means and that the collaborators had to read. Im are glad to help providing at least a good minimal triage/poc, so being open-minded about cutting or splitting scope is totally understandable, i was doubting about the proposal reach before even the first commit, so, keeping future contributions strictly atomic/short makes complete sense for upstream maintenance here and i will do that if found other time someday. Here was just matter of me solving my problem until i realize was a good PR op to help others on the road as OpenS and my person asks, i didnt knew u will reject my test because u dont ask for them or like those extra LOC i put around. If useful for others to trim ideas, ok, if not, ok with the trim. The squashed down commit, +10. Use what u really need on your edge. That said, get it. And sorry for being to -vvvv verbose on the PR body and here haha, just personality preferences (with and without LLM) i guess. Gonna continue outside. Very happy to see my laptop efficient on this edge case. Have a good day mate. Thanks for your time for review and merge. |
|
Sorry, didn't mean to sound dismissive. When you're the filter for this stuff long enough, it wears you down and mistakes are made. Thanks for the prctl fix at least. |
Pins shader compilation to a specific GPU on hybrid systems by matching PCI vendorID / deviceID during vkEnumeratePhysicalDevices. Falls back to --device-index, then gpus.front() when no filter matches. The prctl(PR_SET_PDEATHSIG) part of PR ValveSoftware#305 already landed as 3efdda8; this is the device-filter follow-up invited in the same review.
Background
When a Steam/Proton parent exits abruptly (SIGKILL, crash, game exit), the
forked
fossilize_replay/ExternalReplayerworker can end up reparentedto a subreaper (PID 1 in plain hosts,
pv-adverbinsidepressure-vessel/srt-bwrap) and keep compiling Vulkan SPIR-V in the background.Two upstream fixes already exist on this path:
close_fdsentinel +setpgidkillpg(SIGKILL)in the replayer master. Handles the case where thespawned worker shares the parent's process group.
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.This PR complements #96 by binding the worker to a kernel-level death
signal (
prctl(PR_SET_PDEATHSIG, SIGKILL)) so the cleanup also fires whenthe worker ends up in a different process group (the CLI tool path used
when
inherit_process_group=true, seecli/fossilize_replay.cpp). It alsoadds explicit
--device-pci-vendor/--device-pci-deviceselection sohybrid laptops can pin shader compilation to the target dGPU instead of
falling through to the iGPU.
Closes: #247, #264, #279, #302.
#288 (104 GB RAM growth) and #303 (status flag) are tracked separately
and are not in scope of this PR — see "Out of scope" below.
Relationship with PR #96
flowchart TD subgraph PR96["PR #96 (HansKristian-Work, 2020) - User-Space Group Cleanup"] P1["Worker spawned"] --> P2{"inherit_process_group?"} P2 -- "false (default)" --> P3["close_fd pipe + setpgid()"] P3 --> P4["epoll sentinel in master"] P4 --> P5{"Parent dies?"} P5 -- "Yes, gracefully" --> P6["killpg(SIGKILL) in user-space"] P5 -- "SIGKILL / OOM / crash" --> P7["Master code never runs, no killpg, worker orphaned to PID 1"] P2 -- "true (CLI path)" --> P8["Pipe and setpgid bypassed, no user-space cleanup at all"] end subgraph PR305["PR #305 (Louzt, 2026) - Kernel-Level Invariant"] Q1["Worker spawned"] --> Q2["prctl(PR_SET_PDEATHSIG, SIGKILL)"] Q2 --> Q3["getppid() race-free gate"] Q3 --> Q4["Kernel delivers SIGKILL on parent death, any pgroup or namespace"] end P7 -. "Orphaned to PID 1 or pv-adverb" .-> Q1 P8 -. "Same orphan path" .-> Q1PR #96 works while control returns to the replayer epoll loop. It cannot
cover three cases that PR #305 now does:
SIGKILL, Proton panic, OOM killer):the master code path is interrupted before
killpgis reached, so theworker outlives the parent.
inherit_process_group=true(CLI tool path, seecli/fossilize_replay.cpp): theclose_fdpipe is only created when!options.inherit_process_group. When the CLI inherits the processgroup, no user-space cleanup runs at all.
pressure-vessel/srt-bwrap): workersreparented to
pv-adverb(the container subreaper) escape the parent'sprocess group, so neither
killpgnorsetpgidreaches them.PR #305 binds each worker to a kernel death signal after
fork()insteadof relying on user-space event loops, which closes all three gaps.
Changes
Linux worker cleanup (
fossilize_replay_linux.hpp,fossilize_external_replayer_linux.hpp)After
fork()in both the CLI replayer master and theExternalReplayerworker path:
The
getppid()re-check is the race-free gate: betweenfork()returningand
prctltaking effect, the parent could already have died and beenreaped. If
getppid() != parent_pid, the parent is gone and we abortcleanly instead of running unparented.
Multi-GPU selection (
cli/device.cpp,cli/fossilize_replay.cpp,fossilize_external_replayer.cpp/hpp)New flags
--device-pci-vendorand--device-pci-device(hex, e.g.0x10defor NVIDIA). Selection is gated onvendorID; the optionaldeviceIDnarrows it to a specific GPU. If the filter is set but no GPUmatches,
LOGWwarns and falls back to default selection instead ofsilently picking the wrong device.
The predicate lives in
cli/device.hpp::VulkanDevice::pci_filter_matchesso both the CLI selection loop and the unit test exercise the same logic.
Verification
All 8 ctest pass (1.5 s on this host):
orphan-prevention-testforks an intermediate parent, kills it, andasserts the grandchild is reaped within 2 s (bounded poll, 200 × 10 ms).
gpu-selection-testrunspci_filter_matchesagainst a syntheticcatalogue of NVIDIA / AMD / Intel IDs and checks vendor-only,
vendor+device, mismatched device, and unset-filter cases.
The change is Linux-only; Windows behavior is unchanged and still
governed by Job Objects (#233).
Out of scope
kernel's
drm_release()path the same way every other Vulkan processdoes. Not benchmarked here.
prctlis Linux-only. macOS still uses the PR Terminate replayer processes when parent process (unexpectedly) dies. #96 process-groupcleanup.
#288(104 GB RAM growth): separate pathological pattern — looks likecompilation state / VM growth rather than orphaning. This PR does not
address it; a separate report with
ps/nvidia-smioutput would helptriage.
#303(progress flag): enhancement request, not a bug fix. Not in scopeof this PR.
measured in this PR and have been removed.