fix(vk): a shader preparation the GPU driver stops answering in goes on or ends, and says why (#2107) #2195
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2195
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/prepare-hang-guard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Since v2.1.0, some builds stop for good while preparing shaders (#2107). #2186 made the slow-but-working case visible: the pipeline steps now log their progress every 10 s. A run where the GPU driver itself stops answering still waits forever, though. This makes that run go on, or end with a reason, so the game's first start doesn't hang in the same place.
This replaces #2123. It's the same work, rebased onto #2186, with hckrman101's review there addressed, and with the status lines #2186 now covers taken out.
What happens
One player's log (an RX 580) shows a likely cause:
guarded_graphicscatches the driver's crash by jumping out of the compile, and that leaves the compile unfinished inside the driver. #1823's process dump of issue 996 shows what follows on AMD: later builds of that program wait inside the driver forever.predict_run, which runs at build time since #2032. Unlikeprecompile_run, it had no stall guard.precompile_runalready avoids that, because of a known NVIDIA fault (621.14, RTX 4070 Ti Super).Changes
predict_run(gpu/vk/vk_draw.cpp)precompile_rundoes.RX_VK_PREDICT_STALL_S, 0 waits for all), it goes on and leaves the stuck workers in the driver. Their state lives on the heap, as inprecompile_run. This happens:driver_faults());precompile_run's guard does. So only the last stragglers are given up, never the bulk. A slow driver that still works keeps its pipelines, at the game's own start too. A real hang before then is the watchdog's to end.predict_runandprecompile_runrecord when they leave workers in the driver (prepare_left_workers()).A watchdog for the whole
--prepare-shadersrun (gpu/vk/vk_main.cpp), covering every part on every GPU:What it's waiting on. Every 30 s that nothing has moved, it logs the part, its count, and what it's waiting on: "the GPU driver or the disk", "the GPU driver, which faulted (above)", or "the GPU driver, which still has pipelines it did not finish". It logs this even while #2186's 10-second lines keep the log busy, because those repeat the same count during a hang. A driver fault it caught gets a line of its own as soon as it happens.
The shader programs' count. That step logs no count of its own, so the watchdog writes one after 30 s of a quiet log. It writes nothing of its own while the pipeline steps are moving, since #2186's lines cover them.
Stops when nothing moves for 5 minutes (
RX_VK_PREPARE_STALL_S, 0 never stops). It then:STOPPED: ... the GPU driver stopped respondinghookshot_prepare_skipped, so the game's own starts don't hang in the same place (what wasn't prepared is built when first drawn)TerminateProcess, notexit(), which would unload a driver that can still hold workersThe 5-minute limit is long on purpose: reading the caches and deciding the predictions report no count for a while on a slow disk or CPU.
The pipeline cache's save, the last step:
vkGetPipelineCacheDatacan wait on them. A run that gets through it ends at once withTerminateProcess(after@@done shaders).PREPARE_SILENCE(15 minutes with no line) still ends a save that never finishes.The part, its count and the time are read under one lock.
Elsewhere
make_game.pymaps exit status 4 to "Left to the game's first start: the GPU driver stopped responding". Before, players waited outPREPARE_SILENCE. #2155 then cleans up the leftover process, if any, before an update.driver_faults(): the fault guards count what they catch. These are the same four guard blocks #1823 changes, so whichever lands second needs a mechanical rebase. After #1823, the count would follow itskDriverFaultpath.Tested
tests/test_prepare_stall.py(new) compilespredict_runand the watchdog from the source with a stand-in driver. All 14 cases pass:predict_run:prepare_endmake_game.py: its message for exit status 4vk_main.cppandvk_draw.cppcompile with the Windows toolchain's flags (llvm-mingw,-fms-extensions).test_prepare_cache_save,test_precompile_compute_guard,test_linux_driver_fault_guard.python -I -B tests/run_all.pyon Windows: 341 passed, 3 failed.test_recorder_sync,test_fpx_stickyandtest_precompile_compute_guardfail the same way on an untouched copy of main on this PC (the last needs MSVC's libraries). The Linux-only checks,test_linux_driver_fault_guardamong them, are left to CI.🤖 Generated with Claude Code
Thanks for carrying this over onto #2186. I checked it against my review of #2123, and all of it is addressed:
-D_CRT_SECURE_NO_WARNINGSis on the compile line, and both windows-2022 parts are green.prepare_left_workers()is set by bothpredict_runandprecompile_runwhen they go on. In that case, or after a fault, the save is stopped at the limit. A clean save only reports, and its lines stop after 300 s, soPREPARE_SILENCEstill applies. Runs that get through end viaprepare_end(TerminateProcess /_Exit) after@@done shadersinstead ofexit().pipeline_cache_savewrites to.tmpand renames, so a stop in the middle can't leave a torn cache file.next >= keys.size()), asprecompile_rundoes. The slow-program case is in the test.PrepareSeenis read under one lock.Moving the "waiting on ..." line to every 30 s of no movement, instead of 30 s of a quiet log, is the right call now that #2186's 10 s lines repeat the same count during a hang. The watchdog only starts in the
prepare_only()branch, so it can't end a real game start. The detached predict workers only touchPreand their own stack (program_cache_borrowholds no lock while it runsfn), so leaving them behind is safe.#1823: the overlap is the same four guard blocks plus the two test stubs, and it's mechanical. Your note there (the AMD retry recovers before
fault_quarantine().end(...)runs, so the program isn't quarantined) is a good catch for #1823 to look at. Either order works.CI: the red Linux, SPIR-V and AppImage jobs are the runners failing to resolve the Ubuntu apt mirrors (
Temporary failure resolving 'azure.archive.ubuntu.com'), so clang, spirv-val and glslang never got installed. The only failures are the checks that need those tools.test_prepare_stall,test_linux_driver_fault_guardandtest_prepare_cache_savepass on Linux even in that run. A re-run should go green.Two nits inline, neither blocking. Approving.
Nit:
faults_beforeis read after the 64 workers have started, so a fault in their first few milliseconds is counted as "before", and that run falls back to the tail-only rule. Reading it just above thethreads.emplace_backloop would close that window.Nit:
faults > 0also counts a fault the AMD retry recovered from ("recovered with optimization disabled", the exact line in the #2107 RX 580 log). On that card every run is "risky": a save that's just slow for 5 minutes gets stopped with exit 4 and leaves the skip file. That's probably rare enough not to matter, and it does keep the TerminateProcess ending, which is what you want. It might still be worth a word in the comment, or a longer cap for the save than for the other parts.Good catch, fixed.
faults_beforeis now read just above thethreads.emplace_backloop, so a fault in the workers' first milliseconds counts as this run's.Agreed, I did both. A risky save now gets twice the parts' limit (600 s by default) before it's stopped. The "still writing" and "STOPPED" lines say 600 s. The header comment now says why: a fault that AMD's retry recovered from ("recovered with optimization disabled") still counts, so on that card every run is risky.
test_prepare_stall'ssave-leftcase now checks that the save isn't stopped at the parts' 4 s. It's stopped at 8 s instead.Thanks for the review. I rebased onto main and fixed both nits (replies inline).
The rebase over #1823 and #2042:
*pl = VK_NULL_HANDLE;and this PR'snote_driver_fault();.predict_runusesenv_on("RX_VK_NO_PREDICT").test_prepare_stall's predict fixture has a stand-in for it.test_prepare_stallpasses,vk_main.cppandvk_draw.cppcompile cleanly, and the rest of the suite shows no new failures. The earlier CI failures were the runners' DNS (the apt mirrors), as you said, not this change.github-sync referenced this pull request2026-11-12 06:22:54 +00:00