fix(vk): preserve cache file reservations on macOS #476
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8015c9a
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
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/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!476
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/cache-files"
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?
Type: fix · Risk: low · Tested on: macOS 26.7.1, Apple M4 Max (game-free checks, arm64 and Rosetta x86_64; no game run) · Needs: none
Why
The renderer's two cache files do not compile on macOS.
gpu/vk/pipeline_cache_file.hreserves the pipeline cache'sdisk space with Linux's
posix_fallocate, andgpu/vk/vk_progcache.cppasks for read-ahead withPOSIX_FADV_WILLNEED;macOS has neither. So six of
main's game-free cache checks fail on a Mac, and no Mac renderer can build. The reservationitself has to stay: the driver writes the pipeline cache through a shared mapping, and on a sparse file a full disk is a
SIGBUSin the middle of a write instead of a save that fails cleanly. Part of the macOS port (#420).How to reproduce
Seen on: macOS 26.7.1, Apple M4 Max, APFS, Apple clang 21.0.0, arm64 and x86_64 under Rosetta (
main's game-freecache checks). Only macOS is affected: Windows and Linux have their own calls.
Settings and switches: defaults.
Steps: on a Mac, on
main, runpython -I -B tests/run_all.py pipeline_cache pipeline_play_cache pcache_header program_cache_cleanup program_cache_one_variant.Expected: the six cache checks compile and pass.
Actual: at
main@6c9edffe, on both routes:gpu/vk/pipeline_cache_file.h:274:13: error: use of undeclared identifier 'posix_fallocate'andcache_functions.inc:469:61: error: use of undeclared identifier 'POSIX_FADV_WILLNEED'; all six FAIL.What changed
On macOS the pipeline cache's temporary reserves its whole capacity with
fcntl(F_PREALLOCATE)before it gets itslength and is mapped, and the program cache's read hints use
fcntl(F_RDADVISE)for the same ranges. Both are Apple-onlybranches with
main's lines in#else; a failed reservation is the empty result the callers already handle.PipelineCacheFile::writable's order: create the empty temporary, reserve all of it or nothing, set itslength, then map it. The two callers' handling of an empty result is unchanged.
PipelineCacheFile::writableingpu/vk/pipeline_cache_file.h.fcntl(F_PREALLOCATE)(F_ALLOCATEALL, all ornothing;
F_PEOFPOSMODE). Only then doesftruncateset its length and is it mapped.vk_draw.cpp:839and:10352) already handle: thetemporary is removed and the published cache is left as it was. There is no sparse-file fallback.
program_cache_loadingpu/vk/vk_progcache.cpp. On macOS each record's two read hints (its first 4 KiB, its last8 KiB) use
fcntl(F_RDADVISE)for the same ranges as Linux'sposix_fadvise, line for line. Best effort, as before:the reads that follow do the I/O and check it either way.
F_RDAHEADwas not used, because it changes the file'sread-ahead policy rather than asking for these ranges.
#if defined(__APPLE__), and#elsekeepsmain's linesas they are.
tests/pipeline_cache_io_fault_test.cpp. On macOS its injectedENOSPCreaches theF_PREALLOCATEcall(through an
fcntlstand-in), so the existing native failure stages run there too. Linux'sposix_fallocatestand-in is unchanged.
tests/test_pipeline_cache_allocation.py(new; macOS only: its test class is skipped elsewhere withunittest.skipUnless(sys.platform == "darwin", …)). It compilespipeline_cache_file.h(--source-rootselectsanother tree) and checks, on a real file:
blocks of 512 bytes, against 0 for a control file sized with
ftruncatealone);temporaries are removed, and a retry succeeds.
docs/pipeline-cache-storage.mdsays what macOS reserves and hints, and what the new test checks.main's #321 moved the caches into the game folder'scache/. The temporary is created beside itsdestination (
create_temporary), so the reservation goes where the cache goes and nothing else here changes.5b929e7f(one commit). It ismacos/cache-files'98b6d7cfcherry-picked ontomainwithout aconflict (the same patch id), with the
Assisted-byandCo-Authored-Bytrailers of that first rebase added. Rebasedagain on
main6c9edffe(2026-11-14) without a conflict; main had changedvk_progcache.cpp(#370) away from itslines.
fcntl(2)andtruncate(2)manuals andsys/fcntl.h; nothing copied.Measurements
Verdict: fixed, a game-free proof (no timing claimed): on both routes 7 of 7 cache checks fail on
mainand 7 of 7pass here, after the rebase as before it, and the full suite changes only by those seven (538 passed against
main's531, 26 failed against 32).
Commits: main
6c9edffe5d(2026-11-14); this PRmac/cache-files@5b929e7fc1. Before the rebase: main5e072fc8, this PR587afb47.Hardware: Apple M4 Max (16 CPUs, 64 GiB), APFS, macOS 26.7.1 (25G241), Apple clang 21.0.0. Game-free: no GPU, no game
Scene: none (a portability fix; its proof is the tests, as CONTRIBUTING's
fixrow allows)Method: the six cache checks and the new test, compiled for arm64 and for x86_64 (run under Rosetta), against
main'ssource and against this PR. Before the rebase (main
5e072fc8, this PR587afb47): two interleaved rounds per route, PR,main, PR, main. After it (main
6c9edffe, this PR5b929e7f): one round per route, with the same results; main's arm64 sixfrom its baseline suite run. Then the full game-free suite on this PR, compared by test name with
main's on the sameMac.
How to reproduce:
python -I -B tests/run_all.py pipeline_cache pipeline_play_cache pcache_header program_cache_cleanup program_cache_one_varianton each side;python -I -B tests/test_pipeline_cache_allocation.py --cxx clang++ --source-root <a tree of main>for the new test againstmain's header. For Rosetta, pass a compiler that adds-arch x86_64.6c9edffe5b929e7ftest_pipeline_cache_file.pyposix_fallocatetest_pipeline_cache_save.pyposix_fallocatetest_pipeline_play_cache.pyposix_fallocatetest_pcache_header_read.pyposix_fallocatetest_program_cache_cleanup.pyPOSIX_FADV_WILLNEEDtest_program_cache_one_variant.pyPOSIX_FADV_WILLNEEDtest_pipeline_cache_allocation.py(new)posix_fallocateEach row is the same on arm64 and under Rosetta, in every round: 7 of 7 fail on
mainand 7 of 7 pass here (28 runsbefore the rebase, 14 after it, plus main's arm64 six in its suite run).
The new test also catches a missing reservation, not only
main's compile error. Before the rebase, with this PR'sheader and only the
F_PREALLOCATEcall removed (a throwaway copy, not committed), it compiled and failed on bothroutes:
Screenshots: none; nothing on screen.
Flags and settings
None.
Testing
Tested:
On an Apple M4 Max with macOS 26.7.1, game-free only, on arm64 and under Rosetta (x86_64), as above.
The full suite,
tests/run_all.py, 6 at once, on this PR, compared by test name withmainon the same Mac (therunner labels macOS
linux):test_pipeline_cache_allocation,absent on main, passes here;
test_pcache_header_read: FAIL on main, pass here;test_pipeline_cache_file: FAIL onmain, pass here;
test_pipeline_cache_save: FAIL on main, pass here;test_pipeline_play_cache: FAIL on main, passhere;
test_program_cache_cleanup: FAIL on main, pass here;test_program_cache_one_variant: FAIL on main, passhere.
requests, some are checks main added or changed since
5e072fc8):test_appimage_package_retry,test_appimage_player_build,test_collect_wide,test_control,test_cpm_discovery,test_crash_dialog,test_crash_record,test_env_on,test_game_layout,test_lift_cleanup_error,test_linux_preflight,test_longshot_ctl,test_low_memory_notice,test_make_game_cache,test_mem_gate,test_mem_gate_children,test_mirror_trim,test_oot_state_tap,test_pipeline_tour,test_prof_symbols,test_readback_late,test_report_run,test_split_giants,test_vk_pgo,test_vram_budget_row,test_walk_key.recorder_sync_test.exe-2026-11-14-180744.ips).
The Windows and Linux text after the rebase, by
unifdef -U__APPLE__of each changed C++ file againstmain@6c9edffe:gpu/vk/pipeline_cache_file.h,gpu/vk/vk_progcache.cppandtests/pipeline_cache_io_fault_test.cppidentical.
After the rebase onto main
aa95d35f, at7e561fbb(5b929e7fbefore the rebases ontoe090cc48andaa95d35f):Sweep3's checks of this head (the C/C++ it changes compiled, and its own tests run, on arm64 and x86_64 where they
apply) gave the same results as before;
python -I -B tests/run_all.pyon wave S1's merge11a5b4e7, which carriesthis head: 568/22/17 (passed/failed/skipped) against main aa95d35f's 543/33/17 on this Mac, no new failure
(
reports/2026-11-15-rebase-sweep-3.md).Not tested:
main's text, in the#elsebranches.main's renderer does not build on macOS yet (the macOS port's other pull requests,#420); this PR is the cache-file part.
ENOSPCis injected at theF_PREALLOCATEcall, andthe file-size limit is real. A filesystem that refuses
F_PREALLOCATEfails the save and keeps the old cache, bydesign.
-mmacosx-version-min=14.4, but it ran on 26.7.1 only; on macOS 14.4 on Apple silicon the check would be the sixcache checks and the new test, expecting all to pass.
F_RDADVISEis a hint, asposix_fadviseis; no timing is claimed.Must be tested: None: the unifdef comparison shows Windows and Linux compile
main's text byte for byte (unifdef -U__APPLE__ofpipeline_cache_file.h,vk_progcache.cppandpipeline_cache_io_fault_test.cppis identical tomain's), and the new test's class is skipped there. The macOS side is covered under Tested. The same comparison at therebased head
7e561fbbagainst mainaa95d35fgives the same result.Risks
What could break:
main's.F_PREALLOCATEcannot save the pipeline cache: the save fails cleanly and the oldcache stays. That is deliberate. A sparse mapping there could
SIGBUSmid-write on a full disk.Turning it off: revert the commit. There is no switch.
Compatibility: Nothing changes. Neither file is one of the translator stamp's (
nx2/translator_files.py), so noshader cache rebuilds. The cache formats are unchanged; saves, the options file and the vk ABI too.
Issues
See #420 (the macOS port).
Checklist
Everyone
type(scope): description) of the status line's type.main: onaa95d35f(#761, 2026-11-15), head7e561fbb, without a conflict; this PR's owncommits, old → new:
5b929e7f→7e561fbb; the commit names elsewhere in this description are from before therebase.
python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). On macOS: 538 passed, 17skipped, and 26 failures that also fail on main
6c9edffeon this Mac, named under Testing. After the rebase onto mainaa95d35f(head7e561fbb): wave S1's merge11a5b4e7, 568/22/17 against main aa95d35f's 543/33/17, no new failure.third_party/untouched (a new folder with aPROVENANCE.mdinstead).fcntl(2)and
truncate(2)manuals andsys/fcntl.h; nothing copied.When it applies
Assisted-by:/Co-Authored-By:trailers.mainand passing here.tests/that reads changed source updated in this PR, not weakened. (pipeline_cache_io_fault_test.cppinjects its failure at the new call.)
587afb479e5b929e7fc15b929e7fc1cb95beffe3cb95beffe37e561fbb53WIP: fix(vk): preserve cache file reservations on macOSto fix(vk): preserve cache file reservations on macOSFolded into #845 (feat(macos): run the host on macOS: build, runtime, control, crash records and the Vulkan loader)
Pull request closed