WIP: fix(vk): on Intel a pipeline cache's data is fetched with no build under way #190
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!190
Loading…
Reference in a new issue
No description provided.
Delete branch "Guep/Longshot:fix-intel-pcache-fetch"
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?
Why
On Intel GPUs under Windows the game closes with no message every few minutes: Intel's driver crashes (
igvk64.dll+0x79ddf3, an access violation) invkGetPipelineCacheDatawhen a pipeline cache's data is fetched while other threads build pipelines with a cache.What changed
gpu/vk/vk_draw.cpp,PcacheFetch/PcacheUsearound onestd::shared_timed_mutex: on Intel GPUs under Windows, each pipeline build with a cache (guarded_graphics_held/guarded_compute_held:create_cached's builds,graphics_with's other caches, the warm pass's computes) holds it shared, and eachvkGetPipelineCacheDatacall (pipeline_cache_save's worker,play_file_write,pcache_saved_now, the size read inpcache_init) holds it alone. Vulkan allows both at once; this is a driver bug.vkGetPipelineCacheDatacalls only: never across a file's write, and never across a wait for the builds. A builder counted into a play generation's users holds the lock only around each ofcreate_cached's builds, so a fetch that held it acrossplay_cache_save()'s wait for those users would wait for that builder for good (the deadlock HumanBean found in review; fixed in7569238a).main: what play compiles would then never be written to the play files. NotRexed/Hookshot#2456 ("pipeline cache saves apart from builds") took the same approach and was tested at 0 crashes in 10 runs on a UHD 770; it can no longer be opened.tests/test_pcache_fetch_lock.py(the lock cut out ofvk_draw.cpp, with threads: a fetch waits for a build and a build for a fetch, the background save gives up instead of stalling, a bounded queued fetch gives up on a build that does not end, nested fetches on one thread; and the source: the lock aroundvkGetPipelineCacheDataalone, never acrossplay_cache_save).tests/test_pipeline_play_cache.pynow compiles the real lock with the play caches, and its new caselock-usersruns a save while a build sits between two ofcreate_cached's builds (counted in, holding no lock: the save must finish without holding the lock while it waits), then a bounded save with a build stuck in the generation (kept for a later save, no hang).tests/pipeline_cache_save_test.cppgets a stand-in.docs/development.md, a row indocs/tested-configurations.md.Flags and settings
RX_VK_PCACHE_FETCH_LOCK(new)1: a cache's data is fetched (vkGetPipelineCacheData) only with no pipeline build under way;0: as before1on Intel GPUs under Windows,0elsewhere (Linux: only when set)igvk64.dll+0x14d102, NotRexed/Hookshot#2456)Measurements
main:
9a186f71(2026-11-13)this PR: ls/fix-intel-pcache-fetch @
308edb68, before the review's fix (7569238a, the lock around the fetches alone: not measured yet)Hardware: Lenovo 83DJ; Intel Arc Graphics (Core Ultra 7 155H iGPU, Meteor Lake), driver 32.0.101.9175; Core Ultra 7 155H; 32 GB; Windows 11 Pro (26300)
Scene: a save in Kokiri Forest, then the control bridge's teleport to Kakariko Village and to Death Mountain Trail, about 130 s in each; output 1920x1080, Render resolution 4, handheld profile
Method: 3 runs per side, interleaved with the other Intel PRs' builds (main, split, pcache, fadd, then again),
RX_VK_NO_PREDICT=1on every side. Per run and area: the median of thefps over the last 5 swindows (the first 4 after the teleport left out: the load) and of theGPU time ... ms a framelines inlongshot_log.txt; an area counts only with 5 windows or more. The table gives the median over the runs, n the runs that count, and the runs that ended in a device loss, with whereHow to reproduce: a save outside Link's house, the options above,
RX_VK_NO_PREDICT=1; teleport with the control bridge (cheats: teleport) to Kakariko Village, then Death Mountain Trail, about 130 s each; read the fps and GPU time lines oflongshot_log.txtper area9a186f71308edb68The GPU-time differences cannot come from this change, which touches only when a cache's data is fetched: they are run-to-run noise (3 runs a side, fewer where the device was lost), not a result. What the table shows is no cost in fps.
Testing
Tested: Windows 11, Intel Arc iGPU (Meteor Lake): the route above, 3 runs per side of about 9 minutes, interleaved with main and the two other Intel PRs' builds; the play files are on with this driver (
vk: pipeline cache: what play makes goes into play filesin every run's log), so the deadlock found in review was reachable in those runs and none froze. Noigvk64.dllcrash in Windows' Application log on either side: this series did not reproduce the crash on main, so it shows no cost rather than the fix. The earlier 300 s series indocs/tested-configurations.md(no crash in 12 runs, main 4 of 6) ran one build with this change, #189 and the 32-lane pin change together, so it cannot credit the fix to this change alone either: what shows this change does what it says is the game-free checks. The review's fix (7569238a) is checked game-free only so far.Not tested: the review's fix in the game (to run before
pr ready); other Intel GPUs on Longshot's build; Linux (the lock is off there: Mesa ANV has no such crash reported); the window's close with a build stuck in the driver (game-free only:lock-users).Needs other testers:
igvk64.dllcrash in the Windows Application log andpipeline cache savedlines inlongshot_log.txt(request to open)Screenshots
Nothing on screen.
Risk and rollback
On Intel, a build waits while a cache's data is fetched: the length of one
vkGetPipelineCacheDatacall (the files are written outside the lock), up to the cache's full size once it has grown past 64 MB. The background save only takes the lock at a moment no build holds it, except after 3 put-offs in a row, when it queues for up to 10 s and the builds that start meanwhile wait behind it for as long as the builds under way take. The forced saves (the prepare, Restart now) wait for the builds under way. At the window's close a build stuck in the driver costs that play file, not the close.RX_VK_PCACHE_FETCH_LOCK=0turns it all off; reverting the commit is enough.Reviewer notes
#40 (new pipelines in the game's own cache before their first draw) touches
graphics_withand the cache saves too; whichever lands second needs a rebase. The device losses seen in these runs are in compute programs that use the float-add loop (see the native float add PR).Issues
See NotRexed/Hookshot#999
Checklist
For everyone
type(scope): description).main. (onfbd42024; measured on9a186f71before the rebase)python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above). (run on7569238a: 448 passed, 10 failed, 72 skipped; mainfbd42024fails 9 on this Windows PC the same way: test_bake_portability, test_bug_report, test_check_third_party, test_fpx_sticky, test_portable_builder_home, test_portable_builder_startup, test_prepare_at_build, test_release, test_spawn_emit (all but test_prepare_at_build fail here too); plus test_frame_wait_bounds and test_jobs_wait_runs_queued, timing checks that ran out of time while a 12-job game build ran beside them, in code this PR does not touch: to run again on an idle PC before ticking)third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.docs/tested-configurations.mdfor each measured run. (the row there is the earlier 300 s series; the route series above still needs one)mainand passing here.tests/that reads changed source updated in this PR, not weakened.Good approach overall: the lock is Intel-on-Windows only,
RX_VK_PCACHE_FETCH_LOCKswitches it either way, the cost when off is a static bool check per build, and the fetch sites I found all take it (pipeline_cache_save's worker,play_file_write,pcache_saved_now,pipeline_cache_close,pipeline_cache_save_play,pcache_init). On the build side, everyvkCreate*Pipelinesthat passes a cache goes throughguarded_*_held:create_cached,graphics_with's other caches (predict's scratch cache too), and the warm pass's computes. The others ingpu/vkpassVK_NULL_HANDLE. FSR 4's hook is AMD only.vkMergePipelineCachesonly runs inpcache_init, before any builder exists. Putting the background save off instead of blocking, while keeping its count, is the right idea.Must change
1. The lock deadlocks against the play generation's
userswait.create_cachedcounts itself intog->usersbefore its firstcreate()and keeps that count across up to threecreate()calls.PcacheUseis taken and released around each call separately. So a builder can haveusers > 0while it holds no shared lock: before its first call, or between two calls. A fetcher can take the exclusive lock at that moment. Thetry_lockpoll in the background save is in fact most likely to win right there, in a gap between calls. Every fetcher here then callsplay_cache_save()with the lock still held, andplay_cache_save()waitswhile (g->users.load()) Sleep(1). The builder is blocked inlock_shared()and never decrementsusers. Both threads stop for good, and so does every later build: the replay thread's too, because it builds throughcreate_cached.pipeline_cache_save(false), every 300 presents once 32 pipelines are built): the game freezes in play.pipeline_cache_close(window close) andpipeline_cache_save_play: the close hangs.pipeline_cache_closeruns beforeprepare_stop(), so a background prepare's workers increate_cachedare enough to cause it.pcache_saved_now/pipeline_cache_save(true)(Restart now, the prepare): the same.Two things need it:
play_cachemust be in use (dev.vk13.pipelineCreationCacheControl, a required feature in Vulkan 1.3, andf13is the queried struct), and the lock must be on (Intel on Windows, or=1anywhere). The description says this GPU writes no play files, which may be why 12 runs didn't hit it. Please checkvk: pipeline cache: what play makes goes into play filesin those logs, because on a Vulkan 1.3 Intel driver I'd expect play files to be on. Possible fixes:PcacheUseacross the whole ofcreate_cached, from beforeusers.fetch_addto afterfetch_sub.play_cache_save()'s users wait: swap the generation and wait forusersfirst, then take the lock only inplay_file_write.The new test cuts only the lock out of
vk_draw.cpp, so it can't see this. Please add a case that runscreate_cachedagainstplay_cache_savewith the lock on.Should change
"The background save never makes a build wait" (Risk, and the comment on the poll) isn't true. Once
try_lockwins, the exclusive lock lasts the whole lambda. That covers both fetches ofpcache(up to its full size once it has grown 64 MB),PipelineCacheFile::writable/publish(disk I/O) and the play file's fetch and write. Meanwhile every build waits, the replay thread's draw-time asks included. Please hold the lock only around thevkGetPipelineCacheDatacalls, and do the file work outside it. Then fix the description.Shutdown with a stuck build.
pipeline_cache_closenow waits inlock()for every build in flight before it does anything, and it runs beforeprepare_stop(). A driver stuck inside a build (the caseprepare_left_workersand the watchdog exist for) used to cost only the save. Now it hangs the window close for good. Consider a bounded wait at close, as the background save does, and skip the save if the wait runs out.Starvation during heavy building. On Intel, with several prepare or GPL workers compiling back to back, a gap with no shared holder may not show up within 2 s at any save. The pipelines go unsaved until a forced save, and with point 3 that save may never come. Log how often it's put off (the
told < 4cap hides that), or fall back to one blocking attempt after N put-offs.The evidence. The tested-configurations row's build is "this PR and two other Intel changes (the pipeline cache fetch lock or the split-submission fix, …)". It doesn't say which build ran which of the 12 runs, so the crash fix can't be credited to this change from it. The PR's own route series didn't reproduce the crash on main. The test failing on main only shows that the symbols are missing. The GPU-time differences in the table (−17 to −34 %) can't come from this change: please call them run-to-run noise rather than a result.
Nits:
NotRexed/Hookshot#999) and in the new docs row (#999,#2564) should be full URLs (https://github.com/NotRexed/Hookshot/issues/999, https://github.com/NotRexed/Hookshot/pull/2564).pcache_saved_nowholds the exclusive lock whilepipeline_cache_save(true)waits onbusy. A background save polling at that moment adds up to 2 s to the forced save.Not checked: whether Intel's Windows driver really exposes
pipelineCreationCacheControl(no log here), and the program cache's and the GPL workers' locks beyond confirming that none is held across aguarded_*_heldcall. I didn't build or run anything.Verdict: request changes (WIP).
@ -22,6 +22,7 @@ otherwise.| 2026-11-12 | main 4dd972a8 + this change, `HOS_VIDEO_INPUT_LIMIT=0` (as main) against the default (16) | Linux (CachyOS, kernel 7.3.3), a private headless sway, scripted input, no sound device (`HOS_AUDIO=0`) | RTX 3090 24 GB, 620.70.06 | Ryzen 9 7900X, 30 GB (the PC was swapping in every run) | 2048x1152, default options, cap 60 | A new game from an empty data folder: the first movie (A after 11 s, about 820 pictures of it), then the second (9,254 pictures) to its end | Three runs each, interleaved. Pictures skipped, first movie: 244, 247, 251 without the limit; 8, 198, 1 with it. Second movie: 123, 700, 98 without; 0, 502, 0 with. In the second run of each side the PC swapped hard and frames were late (177 and 146 frames over 33 ms; 0 to 2 in the others): those skips are the late frames'. Waits of over 90 ms between two pictures in a movie's first 5 s: 44, 43, 43 and 20, 21, 18 without; 1, 0, 0 and 0, 9, 0 with. The second movie ends within 12 ms of its clock on both sides || 2026-11-13 | Hookshot fix/commit-limit 905cb5e1 (NotRexed/Hookshot#2972, ported here as fix/commit-limit), against Hookshot main e0e60920, alternated | Windows | RTX 4060 Ti, - | -, 31 GB (115 GB commit limit) | 3440x1440, muted, the commit rule idle (default `RX_VK_COMMIT_RESERVE_MB`) | Route run: Kokiri, Hyrule Field, Kakariko, 2 runs each | This change: 53 and 57 fps median, p99 40.5 and 36.1 ms, private commit at end 28.9 and 28.6 GB, no `commit limit near` line (the rule never fired). Main: 51 and 50 fps, p99 43.2 and 42.1 ms, 33.4 and 34.8 GB. Run-to-run variation, not the change: it costs nothing when idle || 2026-11-13 | as above, this change only | as above | as above | as above | as above, `RX_VK_COMMIT_RESERVE_MB=38397` (the rule forced, about 25 GB of the game's commit before it kicks in) | as above, 1 run | 54 fps median, p99 38.7 ms, max 881 ms (the run before, rule not firing: 58, 35.6, 881). 52 `commit limit near` lines, texture budget down by the shortfall each time (e.g. 3661 to 2812 MB), headroom 37.4 to 38.4 GB; private commit peak 26.7 GB (30.3 without the rule), VRAM blocks peak 9.0 GB (12.9). The 5 s wait at the limit itself not reached || 2026-11-13 | v2.1.0-rc.3-313-g9b6072c + this PR and two other Intel changes for NotRexed/Hookshot#999 (the pipeline cache fetch lock or the split-submission fix, and compute stages let go of the 32-lane pin as NotRexed/Hookshot#2564 does), the same patch rebased on main e0e6092 for the PR, built from source (llvm-mingw) | Windows 11 Pro (26300), 1920x1080, scripted input | Intel Arc Graphics (Core Ultra 7 155H iGPU, Meteor Lake), 32.0.101.9175 | Core Ultra 7 155H, 32 GB | Render resolution 4, handheld profile; `RX_VK_PASS_TIMING=2`, a screenshot every 60 frames; old/new alternated, the game closed normally | Kokiri Forest outside Link's house, standing, 300 s | No `igvk64.dll+0x79ddf3` crash in 12 runs (main 9b6072c without it: 4 of 6). The background save ran 3 times and was put off once. Device lost in compute programs in 4 of 12, as on plain main |"the pipeline cache fetch lock or the split-submission fix": which build ran which of the 12 runs? As written, the crash-free runs can't be credited to this change. Also, please write the GitHub references as full URLs.
@ -744,0 +771,4 @@return;}if (wait) pcache_fetch_mu.lock(), owns = true;else // (polled: a writer waiting in the lock would stall the builds behind it)The poll keeps the background save from queueing as a writer, but once it wins, the lock lasts the whole save: both
pcachefetches, the file's write and publish, and the play file. Every build waits meanwhile, the replay thread's included. Please hold it only around thevkGetPipelineCacheDatacalls. The PR's Risk section ('never makes a build wait') needs fixing too.@ -1239,2 +1302,3 @@}VkResult cached_graphics(const VkGraphicsPipelineCreateInfo* ci, VkPipeline* pl) {return create_cached(guarded_graphics, ci, pl);return create_cached(guarded_graphics_held, ci, pl);PcacheUseis scoped to onecreate()call, butcreate_cachedholdsg->usersacross up to three. Taking the shared lock once around the wholecreate_cached(users in, through users out) would remove the users/lock cycle withplay_cache_save.@ -9831,6 +9896,7 @@ std::string load_fallback_path() {void program_layout(Program& p) { ensure_layout(p); }std::vector<std::pair<uint64_t, uint64_t>> compute_variants_learned() { return compute_variants_read(); }void pipeline_cache_close() {PcacheFetch fetch(true);Same deadlock at window close: the lock is held into
play_cache_save()'s users wait. This also runs beforeprepare_stop(), so a background prepare's worker increate_cachedis enough. And with a driver stuck inside a build,lock()never returns, where before only the save waited.@ -9861,2 +9928,3 @@auto save = [force] {auto save = [force, built] {struct Done { ~Done() { busy = false; } } done;PcacheFetch fetch(force); // (Intel: no build under way while the caches' data is fetched)Deadlock: this keeps the exclusive lock through
play_cache_save()below, which waitswhile (g->users.load()) Sleep(1). A builder increate_cachedhas already counted itself intousersand is blocked inPcacheUse'slock_shared()(before its firstcreate()or between two of them), so it never gets to decrement. Thetry_lockpoll tends to win exactly in that gap. The result is a frozen game whenever the play caches are on (pipelineCreationCacheControl, Vulkan 1.3).@ -0,0 +105,4 @@lock = cut(draw, "std::shared_mutex pcache_fetch_mu;", "// ---- (longshot) What play makes goes into a cache of its own")# (every fetch of a cache's data is a PcacheFetch, every build with a cache a PcacheUse)save = cut(draw, "void pipeline_cache_save(bool force) {", "\n}\n")assert "PcacheFetch fetch(force);" in save and "if (!fetch.held())" in saveThe check cuts the lock alone, so it can't see the interaction with
create_cached'suserscount andplay_cache_save's wait, where the deadlock is. Please add a case that runs those two against each other with the lock on.308edb68b67569238aecThank you, the deadlock was real: this driver does write play files (
vk: pipeline cache: what play makes goes into play filesis in every run's log here), so the description's "this GPU writes no play generation files" was wrong, and the runs were lucky. Rebased onfbd42024; the fix is7569238a.1. Deadlock. I took your second option, since it removes the cycle for every caller rather than for
create_cachedalone: the fetch lock is now held around eachvkGetPipelineCacheDatacall only, never acrossplay_cache_save()'s wait for a generation's users (nor across a file's write).pipeline_cache_close,pipeline_cache_save_play,pcache_saved_nowand the save's worker no longer hold it aroundplay_cache_save().play_file_writetakes it twice, for the size and for the data; a cache that grew in between does not fit, is not written and is tried at the next save (a generation taken away no longer grows, so that only concernspcache).tests/test_pipeline_play_cache.pynow compiles the real lock with the play caches, and a new case,lock-users, runs a save while a build sits between two ofcreate_cached's builds (counted in, holding nothing): it checks the save holds no lock while it waits and that both finish.tests/test_pcache_fetch_lock.pychecks the source: noPcacheFetchinpipeline_cache_close,pipeline_cache_save_playorplay_cache_save.2. "Never makes a build wait". Done by the same change: the background save holds the lock for the fetches alone, the files are written outside it. The Risk section now says what a build can wait for.
3. Shutdown with a stuck build. The close waits at most 10 s, for the lock (queued,
try_lock_for) and for a generation's users, and leaves that play file unwritten rather than hang. Note that main's ownwhile (g->users.load()) Sleep(1)hung the close in that case too, lock or not; it is bounded now at the close.lock-users's second half checks it: a bounded save with a build stuck in the generation returns within its bound, keeps the generation, and a later save writes it.4. Starvation. Every put-off is logged with its count (the first 4, then every 16th), and after 3 in a row the next background save queues for up to 10 s instead of polling, so back-to-back builds cannot put it off for good.
5. Evidence. All 12 runs of the 300 s series ran one build with this change, #189 and the 32-lane pin change together: the row says so now, and the description says it cannot credit the crash fix to this change alone. The GPU-time differences are called noise.
6. Nits. Full URLs everywhere.
pcache_saved_nowno longer holds the lock acrosspipeline_cache_save(true).Still to do before
pr ready: the review's fix run in the game (it is checked game-free only), the test request, a row for the route series, a Linux build.tests/run_all.pyon7569238a: the same failures as main on this PC, plus two timing checks that timed out beside a game build (named in the checklist), to run again on an idle PC.Re-review at
7569238a. Code only: I didn't build it, run the tests or run the game.Earlier points
PcacheFetch) is held aroundvkGetPipelineCacheDataalone inplay_file_write(x2), the save'sfetchlambda,pcache_saved_nowandpcache_init. Nothing else is taken inside it. The shared side (PcacheUse) covers onlyguarded_*, which is the driver call pluslogfon a fault. The driver-fault gate, the program locks andcreate_cached'suserscount are all taken outside it. So no thread waits on anything while it holds the lock, and no thread holds the lock while it waits onusers.play_cache_save()'suserswait now runs beforeplay_file_writeand holds no fetch lock.pipeline_cache_close,pipeline_cache_save_playandpcache_saved_nowno longer hold it across a save. NoPcacheUseis nested on one thread, so the writer-preferringshared_timed_mutexcan't block a reader against itself.lock-userstests exactly the gap I described.lock().pcache_saved_nowreleases the lock beforepipeline_cache_save(true).The fetch still can't overlap a build. Every
vkCreate*Pipelinesthat passes a cache goes throughguarded_*_held:create_cachedandgraphics_withfor every cache, plus the warm pass'sguarded_compute_held(pcache, ...). The others ingpu/vkpassVK_NULL_HANDLE. This also holds on current main: the quick pipelines of #234 ask the caches throughgraphics_with, and their quick builds use no cache.RX_VK_TEST_SLOW_MS's sleep is outsidegraphics_with, so it doesn't hold the lock. Main's changes sincefbd42024don't touch the PR's hunks invk_draw.cpp(the closest one is 3 lines away), so I expect a clean rebase.New
A. The close's 10 s bound applies on every GPU, not only with the lock.
pipeline_cache_closepassesplay_cache_save(kFetchQueueMs), andbounded = queue && wait_ms >= 0doesn't look atpcache_fetch_lock_on(). So on NVIDIA, AMD and Linux too, the close now stops waiting for a generation's builder after 10 s. When that happens, it drops everything play made since the last save, where main waited. On AMD's proprietary driver one build can take 5-13 s (the reason for #234). A builder counted into the generation can do up to three, and a background prepare keeps building untilprepare_stop(), which runs after this. So that's a real loss on hardware the PR doesn't target. The description's "0: as before" isn't true for the close either. Please bound the users wait only when the lock is on, or else say so in the Flags table and in Risk.B. Should change: the bound doesn't cover a background save that's already waiting. A background save in poll mode calls
play_cache_save(kFetchPollMs, false). Withqueue == falseits users wait is unbounded, and it holdsplay_cache_save's staticmuthe whole time. If a build that is counted into the taken generation then sticks in the driver, that save never returns, and the close's bounded call blocks onmu. Main has the same hole, so this isn't a regression. But "the window's close waits at most 10 s" (docs/development.md, Risk) isn't true in that case.bounded = wait_ms >= 0(the poll's 2 s as the bound) would close it: the generation is kept for the next save anyway. Note that the close's worst case is also 10 s for the users wait plus up to 10 s for each ofplay_file_write's two fetches, not 10 s in all.C. Nit. In the background save, if the size fetch gets the lock but the data fetch (line 10071) doesn't,
put_offis already reset to 0 andbuiltisn't added back. That save is lost without a log line, and the next one waits for 32 more builds.Left before ready (from the description)
run_all.pypassing on an idle PC: the two timing checks rerun.main", but it's onfbd42024and main has moved 9 commits, including #234 invk_draw.cpp.Verdict: request changes, for A (a behaviour change on every GPU that isn't behind the switch) and B. The deadlock fix itself is right.
@ -843,3 +916,4 @@unsaved.push_back(g);}}const bool bounded = queue && wait_ms >= 0;With
queue == false(the background save's poll mode) the users wait stays unbounded, and it holdsmu. If a build counted into the taken generation sticks in the driver, that save never returns, and the close's bounded call then blocks onmu. It's the same on main, but the docs promise a 10 s close.bounded = wait_ms >= 0would bound it to the poll's 2 s, and the generation is kept for the next save anyway.@ -9906,2 +10003,2 @@try {if (play_cache.load()) play_cache_save();try { // (a bounded wait: a build stuck in the driver costs the play file, not the close)if (play_cache.load()) play_cache_save(kFetchQueueMs);This bound applies on every GPU:
play_cache_save'sboundeddoesn't checkpcache_fetch_lock_on(). On AMD's proprietary driver one build takes 5-13 s, and a builder counted in can do three. A background prepare is still building here too, sinceprepare_stop()runs after this. So a close there now drops what play made since the last save, where main waited. Please bound it only with the lock on, or say so in the Flags table ("0: as before") and in Risk.@ -9952,3 +10069,3 @@if (write) {auto data = PipelineCacheFile::writable(pcache_path, n);if (!data.empty() && vkGetPipelineCacheData(dev.device, pcache, &n, data.data()) == VK_SUCCESS && data.publish(n)) {if (!data.empty() && fetch(&n, data.data()) && data.publish(n)) {Nit: if this second fetch doesn't get the lock in poll mode,
put_offwas already reset andbuiltisn't added back. The save is lost without a log line, and the next one waits for 32 more builds.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.