fix(vk): keep programs the driver faulted on out of the driver (#996) #1823
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!1823
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/driver-fault-quarantine"
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?
Fixes #1740
Fixes #1734
Related to #996: the freeze on closing the pause menu or map, with the music still playing.
The freeze
On AMD Windows (driver 0x800161), the game freezes when closing the pause menu or the map in Kokiri Forest and Hyrule Market, with the music still playing. There is no TDR and no device lost.
I took a full process dump of the freeze with Task Manager (RX 6800 XT, v2.0.13, closing the pause menu in Hyrule Market). I then read the thread stacks with the symbols in
hookshot.exe/hookshot_vk.dll:sead::MainThreadevent_wait(waiting for the frame)Presentation Threadmkwvk::draw→targets→jobs_wait, for program 6eaa646d07ea4e43's pipelinemkw_vk workergraphics_pipelinejob →build_graphics→guarded_graphics→amdvlk64.dll,SleepConditionVariableCS, program 6eaa646d07ea4e43precompile_run→build_graphics→amdvlk64.dll,SleepConditionVariableCS, program 6eaa646d07ea4e43I found the program keys by following the
Program&on each stack.Both programs are among the ones every AMD log reports at start as
graphics pipeline for 5fa56485a6b07619 / 6eaa646d07ea4e43 / 4e5ce183a33b0855 not made (-13). That-13is the__exceptinguarded_graphics, which catches an access violation inside AMD's compiler. Those two stuck precompile threads are also theprecompile: N of N+2 done, nothing finished for 30 sof every AMD start.What happens: the caught fault leaves that program's compile unfinished inside the driver. Every later
vkCreateGraphicsPipelinesof the same program, with any state, waits inside the driver for a condition variable that is never signalled. The game runs until it needs a new pipeline of one of those programs (closing the pause menu, some areas), and then the frame never finishes.The fix
gpu/vk/driver_faults.h(new, Vulkan-free) andbuild_graphics:FAIL_ON_PIPELINE_COMPILE_REQUIRED) is never made to wait: a build of its program under way answers it as a compile that is required. This gate is on for AMD (vendor0x1002) by default, where the hang was seen. Other drivers build a program's state variants side by side, as before, and only the quarantine applies to them.hookshot_driver_faults.txtbeside the executable, as<vendor> <driver version> <key>lines. On later runs with the same vendor and driver those programs are never asked of the driver, the precompile included. A driver update tries them again. If the file cannot be written (a read-only folder), the log says so once and the quarantine lasts for that run.-77, as the compute guards already did. A driver's ownVK_ERROR_UNKNOWNis no longer taken for a fault. The Windows guards keep #1766's log line with the exception code.*pl = VK_NULL_HANDLE. Whatever the driver wrote before it faulted is never published or destroyed; for example,fallback_compute_pipelines()destroys every non-null output on an error.RX_VK_DRIVER_FAULTS=0turns all of this off;=2turns the gate on for every driver.With #1747: its AMD retry without the optimizer followed
VK_ERROR_UNKNOWN, which until now also meant a fault. It still runs after a fault, now inside the gate. The program is quarantined only if the retry faults too.With #1772: the gate wraps
graphics_with. That can reach the driver up to three times through the play caches, all inside the gate.create_cachedreturns the guard's result unchanged, so a fault still reaches the quarantine.Results (RX 6800 XT, driver 0x800161, Windows 11)
The builder's precompile runs on the player's GPU, so the build log shows the effect directly:
6625 of 6627 done, nothing finished for 30 sIn game, before the fix, it froze on closing the pause menu in Hyrule Market, and in another session also on a menu. After the fix, in two sessions with the pause menu and the map opened and closed many times (over 200 menu-frame samples in the logs, Hyrule Market included), it never froze and there were no driver faults after start. A later session with the output clearing ran as a first run (
hookshot_driver_faults.txtremoved). The driver faulted three times (exception 0xc0000005), and the precompile still completed in 2.6 s with no thread left in the driver. Pausing in Hyrule Market did not freeze either. This was tested on a v2.0.13 build with the same change backported; this branch is rebased on currentmainand merges cleanly.Tests
tests/test_driver_faults.pycompiles the realdriver_faults.hand runs it with threads:kBusy;vk_draw.cppthat everyguarded_graphicscall ofbuild_graphics(including #1747's retry) sits between the gate'sbeginandend, and that the guards stay self-contained fortest_precompile_compute_guard.pyandtest_linux_driver_fault_guard.py.tests/test_linux_driver_fault_guard.pynow expects-77for a graphics fault.tests/test_linux_driver_fault_guard.pyandtests/test_precompile_compute_guard.py: their fake drivers write a sentinel handle before they fault. The existing checks then assert that it is neither published nor destroyed. These run on Linux and on an MSVC-target build (CI).test_driver_faults.pyalso covers the non-serial mode (builds side by side, never waiting nor busy, a fault still quarantined), and checks that every fault branch clears the output, and that every driver call ofbuild_graphics(graphics_withincluded) is inside the gate.run_all.pyon Windows, rebased on currentmain: 291 passed. The 6 failures fail the same way on a cleanmainhere (local environment).Not covered
-77,failedonce); only their guards' output clearing is new.Heads-up: this conflicts with main now (a lot landed today). Could you merge main into the branch (or rebase) and resolve the conflicts? Thanks!
One adjacent fault-containment question from a source audit: the Windows and POSIX guards return the caught-fault result but leave
*plas the driver last wrote it.fallback_compute_pipelines()destroys every non-null entry inmadeon an error. A fault after writing an output could therefore send an untrusted handle back intovkDestroyPipeline.Would clearing the output specifically on the caught-fault branches fit this work? Normal Vulkan error returns should retain their ordinary output/cleanup contract. A focused regression could have the authored driver stub write a sentinel and then raise the exception/signal, checking that the caller never destroys or publishes it.
This is a source-derived question, not a reproduced real-driver crash or a claim that the current quarantine change causes one. Sharing here because the guards are already in your scope; I have not started a competing implementation.
@hckrman101 Rebased on current
main(51de681). Nothing in the fix changed, but two merges since touched the same code:-77.graphics_with, through the play caches):build_graphics' gate now wrapsgraphics_with. That can reach the driver up to three times (generation cache,pcache, compile), all inside the gate, andcreate_cachedreturns the guard's result unchanged, so a fault still reaches the quarantine. #1747's AMD retry is still inside the gate too.@Arkex-ai Good catch, and it fits here. All four guards (Windows SEH and Linux signal, graphics and compute) now set
*pl = VK_NULL_HANDLEon the caught-fault branch only; a normal Vulkan error return keeps its usual contract. For the regression: the fake drivers intest_precompile_compute_guard.pyandtest_linux_driver_fault_guard.pynow write a sentinel handle (0xdead) before they fault. Their existing checks then assert that it is neither published nor destroyed (out == 0,compute_pipeline == 0 && destroys == 0,pl == VK_NULL_HANDLE).test_driver_faults.pyalso checks that every fault branch clears the output.Played again (RX 6800 XT, driver 0x800161, this change on a v2.0.13 build). I removed
hookshot_driver_faults.txtfirst so the start was a first run:exception 0xc0000005, #1766's line);Then, in Hyrule Market, I opened and closed the pause menu and the map with no freeze and no further driver fault.
Side note: #1766's description mentions a start that hung on an RTX 4070 Ti after a precompile worker's build faulted inside the driver. That looks like the same mechanism as here, so the quarantine may help on NVIDIA too.
Full
run_all.pyon Windows: 270 passed. The 8 failures fail the same way on a cleanmainhere (local environment). The two guard tests run only on Linux and on an MSVC-target build, so CI will run them.Nice find with the dump; the quarantine plus the gate is a clean way to stop the freeze, and the clearing of
*plon all four caught-fault branches covers Arkex-ai's point. I read the diff and the test and the logic holds:begin/endare paired with no early return between them inbuild_graphics, the-77change keeps #1747's retry (VK_ERROR_UNKNOWN || kDriverFault), and the cold probe never blocks.One question, not a blocker: the gate is on by default for every vendor, and it serializes all builds of one program (
building_is keyed byp.keyalone, not by key plus state). Before, NVIDIA/Intel/Linux drivers compiled several state variants of the same program in parallel (precompile threads + worker), and a non-cold build on the presentation thread now also waits for a precompile thread that happens to be building the same program. The AMD hang needs the serialization, but other drivers don't. Did you see any change in precompile time or a frame stall on a non-AMD GPU? If it is measurable, you could limit the gate to the vendor/driver where it is needed (or only serialize once a program has faulted), and keepRX_VK_DRIVER_FAULTSas the override. The quarantine itself is vendor-neutral and cheap.Small notes:
action_required, waiting for approval), so the two guard tests and the new one are untested by CI so far.hookshot_driver_faults.txtgoes beside the executable; if that folder is read-only (Program Files)driver_fault_notefails silently, so the quarantine then only lasts for the run. That is fine, maybe worth one log line (nit).@hckrman101 Thanks, good point. I went with your first suggestion. New commit on top: dd68029 (rebased on current
main).0x1002). NVIDIA, Intel and the Linux drivers build a program's state variants side by side again, exactly as before this PR. The quarantine stays on for every vendor, as you suggested.RX_VK_DRIVER_FAULTS=2turns the gate on for every driver,=0still turns everything off. #1766's description mentions a start that hung on an RTX 4070 Ti after a driver fault, so someone on NVIDIA can try it without a rebuild.hookshot_driver_faults.txtcan't be written (read-only folder), the log now says so once, and the quarantine lasts for that run.test_driver_faults.pynow also covers the non-serial mode. Builds of one program go into the driver side by side, never wait nor getkBusy, and a faulted program is still kept out. Fullrun_all.pyon Windows: 291 passed; the 6 failures fail the same way on a cleanmainhere.Played on a build of current
mainwith both commits (RX 6800 XT):Also from earlier play: a new program (
86068cde6ce03d5d) faulted in the driver mid-game, not at start. It was caught, quarantined and written to the file, and the game carried on without freezing.Thanks for the follow-up, this answers my question.
0x1002) or withRX_VK_DRIVER_FAULTS=2, and other drivers build a program's variants side by side again; the quarantine stays vendor-neutral. In non-serial modebeginnever waits or answerskBusy, andendonly erases a key that was never inserted, so nothing changes for NVIDIA/Intel apart from the quarantine. The newnot_serial()test covers that.*pl), and it is still in place.tests/test_log_batch.pyon windows-2022 (GetTickCount64() - t0 < 200, a timing check). This PR doesn't touch logging, so I'd treat it as a flaky runner.test_driver_faults.pyandtest_precompile_compute_guard.pypass on Windows, and the Ubuntu job (with the Linux guard test) is green.One small optional note inline. Good to go from my side.
@ -1014,1 +1015,4 @@// (hookshot) The programs the driver faulted on (driver_faults.h): not asked of it again in this run, nor in later runs on the// same driver (hookshot_driver_faults.txt beside the executable). One program's builds go to the driver one at a time on AMD,// where a build behind a faulted one hung (issue 996); other drivers keep building a program's variants in parallel.Nit, optional:
vendorID == 0x1002also turns the gate on for RADV on Linux, and RADV is what most Linux AMD players run. The hang was seen with amdvlk64 on Windows. If you want to narrow it,VkPhysicalDeviceDriverProperties::driverID == VK_DRIVER_ID_AMD_PROPRIETARY(plusAMD_OPEN_SOURCEfor AMDVLK on Linux) would leave RADV out. The cost there is small anyway (only variants of one program are serialized), so fine to leave as is.@hckrman101 Thanks for the approval. Rebased on current
main(d7a7306), now3dc9b5d. The only conflict was indocs/development.md: theRX_VK_DRIVER_FAULTSrow and #1992'sRX_VK_TASK_MESH_FALLBACKrow landed in the same spot, so I kept both. The code is unchanged.On the RADV note: I'll leave the gate on
vendorID == 0x1002for now. As you said, the cost is small (only one program's variants are serialized), and I'd rather not narrow it todriverIDwithout a RADV report either way. If it shows up in Linux profiles, it's a one-line follow-up.CI note: after the rebase,
windows-2022, part 1/2failed intests/test_recorder_sync.py(the defer/thread lifecycle checks against the fake driver). This PR doesn't touchvk_work.h,vk_recorder.hor anything else that test builds. Locally, on the rebased branch, every lifecycle, handoff and allocation-failure check passes, with the same result as currentmain. It looks like a timing flake on the runner. I can't re-run jobs on this repo, so could someone with access re-run the failed job? Thanks.Posted twice by mistake; see my next review (at
3dc9b5d).@ -1014,1 +1015,4 @@// (hookshot) The programs the driver faulted on (driver_faults.h): not asked of it again in this run, nor in later runs on the// same driver (hookshot_driver_faults.txt beside the executable). One program's builds go to the driver one at a time on AMD,// where a build behind a faulted one hung (issue 996); other drivers keep building a program's variants in parallel.Nit, optional:
vendorID == 0x1002also turns the gate on for RADV on Linux, and RADV is what most Linux AMD players run. The hang was seen with amdvlk64 on Windows. If you want to narrow it,VkPhysicalDeviceDriverProperties::driverID == VK_DRIVER_ID_AMD_PROPRIETARY(plusAMD_OPEN_SOURCEfor AMDVLK on Linux) would leave RADV out. The cost there is small anyway (only variants of one program are serialized), so fine to leave as is.Re-checked after the rebase onto
d7a7306. I compared both commits file by file with the version I approved (dd68029). The added and removed lines are the same in every file, so the fix and the tests are unchanged. The only change is thedocs/development.mdconflict, where both theRX_VK_DRIVER_FAULTSandRX_VK_TASK_MESH_FALLBACKrows are kept. Fine.Keeping the gate on
vendorID == 0x1002for now is fine with me, as you said. The RADV cost is small, and narrowing it todriverIDcan follow if Linux profiles show it.CI: everything is green except
windows-2022, part 1/2, which fails intests/test_recorder_sync.py. This PR doesn't touch anything that test builds, and main passes it at the same base (d7a7306), so it looks like a runner flake. It needs a re-run by someone with access before merge.Overlap: #2195 (which replaces #2123) changes the same four fault-guard blocks, to count the faults it catches. Its description already says that whichever lands second needs a mechanical rebase, and that after this one its count would follow the
kDriverFaultpath. So the two don't conflict in design, only in text.Still good to go from my side. (The review just above, at dd68029, was a duplicate of my previous one posted by mistake; this one is the current one.)
github-sync referenced this pull request2026-11-12 06:22:54 +00:00