fix(host): keep address regions reserved until discard completes #2823
Open
github-sync
wants to merge 2 commits from
github/pr/2823 into main
pull from: github/pr/2823
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2789
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2823
Loading…
Reference in a new issue
No description provided.
Delete branch "github/pr/2823"
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?
Summary
Freeing an address region made its address available before the old pages finished being discarded. Another thread could allocate that address and then have its new payload cleared by the old discard. Freeing the lowest region also left a stale boundary that continued to restrict heap growth.
Fixes #2799. Draft: the source-level failures are reproduced; this head has not been run in the game. This does not claim a reduction in physical RAM or explain the reported multi-GiB memory pressure.
Technical changes
File overlaps reviewed: #2558 changes lazy heap commitment, not this lifecycle. #2478 adds another mapped-range reader; when combined, that reader must also exclude retiring entries. Neither implementation is duplicated here.
Validation
Current head:
45dfb5612c67c73613082838b5976137fea58e74. The review followup removes the unmeasured worklist row, leaving #2799 as the incidence record. It changes no host code or tests. The source/test measurements below were collected atc12c0242862beb2c4d10268265112d783438625b, based on51da0464b4cf0c7f56b81ebd6bb309325835e175; those files remain byte-identical.Windows, Clang 23.1.5 / llvm-mingw: the integrated regression passed 45 assertions in each of five runs. The unchanged base failed 14 behavioral assertions with the same fixture, not a compiler/setup failure. Cases include delayed discard/reallocation, callback reentry, table growth, live-range queries, holes below the heap, repeated and empty finalization, and 10,000 allocation/free cycles. The test also caught and rejected an earlier private candidate that stranded empty reservations. Strict compilation uses
-Wall -Wextra -Werror.The complete Windows game-free suite on these exact four file contents, one worker, finished 437 passed, 26 failed, 33 skipped. It is not green. Clean-base controls for those 26 failures are not yet complete, so they are not all attributed to the environment or described as pre-existing. The focused causal regression passes; hosted results are listed below, and the remaining local baseline controls are pending. Skips include platform, shader-tool and generated-game prerequisites.
The exact committed source and test files now pass three Linux GCC 14 runs, three Clang 17 runs and three Clang AddressSanitizer + UndefinedBehaviorSanitizer runs, with leak detection enabled. Each candidate run passes all 45 assertions; the same base control still fails 14 behavioral assertions. These are source-extracted CPU/native-thread fixtures, not Linux gameplay. The complete base and fixed
hos_mem.calso pass strict syntax checking against the real host interface and retained local generic runtime headers; no linked game was produced. Full linked-game compilation and controlled AMD gameplay remain pending. No FPS or RAM-saving claim is made.Historical hosted checks on
c12c0242(not the new documentation-only head): both Windows source shards and Linux source shard 1/2 passed, as did the Windows Vulkan build, Linux AppImage, SPIR-V fixtures, workflow lint, title and Builder package. Linux source shard 2/2 failed intest_release_scripts.py(test_nightly_two_runs_at_once_end_at_the_newer_commit, first=A): the fake release workflow reported HTTP 404 and mismatched checksums after retries. The address-region regression was not the failing check. An unchanged-base Linux control of that release-script test passed all 33 unit cases in one local run; that does not establish why the hosted failure occurred or turn the CI run green. Exact-head source CI, failed job.Independent code review was received as COMMENTED, not approval. The requested in-game counters (frees, allocation skips while retirement is pending, and heap resizes enabled by a raised floor), followed by an uninstrumented run, remain unperformed; zero observed incidence must be reported as zero, not inferred from this fixture. Existing snapshot callers still need their own lifetime discipline, and the pre-existing heap-size check and subsequent heap mutation are not made a single transaction by this change.
Current-head hosted checks: Game-free source regressions, Linux AppImage, PR title, Windows builder. Their live conclusions are separate from local tests.
Hosted integration status observed at 18:09 UTC: the source jobs check synthetic merge
0b70dcc373b32c6f69ce659fcbf0a3cf221733c6(45dfb561plus main65a4d874), not standalone45dfb561. Linux 1/2 failstest_readme_menu_rows.pybecause the menu hasSkip openingbut the README table lacks it; Linux 2/2 failstest_program_cache_one_variant.pywith undeclareddisk_zstd,kZstdHeadandprogram_cache_payload. The address-region regression passes in the latter log. These are different failures from the older concurrent-nightly run. They remain unresolved integration failures; no green CI or clean-main reproduction is claimed. AppImage and Windows Vulkan builds passed; other checks were still pending.The exact-head allocator regression now also passes GCC, Clang and ASan/UBSan on Linux. The remaining hosted failure is test_release_scripts.py's concurrent nightly-publish case (first=A), with a mock release HTTP 404/checksum mismatch; the same unchanged-base test passed all 33 cases in one local control. I have not changed or weakened that test. Could a maintainer rerun the failed Linux source shard to check whether this is reproducible? The PR stays draft pending gameplay and CI.
Failed job: https://github.com/NotRexed/Hookshot/actions/runs/43249026786/job/131289446554
This is the part the game can actually see. The floor used to only go down, so a heap resize after the lowest region was freed got refused. Now it can succeed. That's the intent, and it's correct (holes below the heap are left out, as in the allocator). Please log it in the in-game run, so we know whether the game ever does this and that it behaves when it does.
Nit: once this PR merges, this row describes something already fixed. Either leave the row out and keep #2799 open until the in-game check is done, or point the row at this PR as the candidate fix, the way the other rows list theirs. Also, if the in-game counter shows the race never happens, S0 x 0.4 is generous for a race nobody has seen in the game.
Thanks, this is a small, careful fix and the logic checks out. Here's what I went through:
releasingentry as taken, so nobody else can get that address until the discard returns. After relocking, the entry is looked up again by address, which handles the table being reallocated or shifted in the meantime. The discard still runs outside the lock. It has to, because the Linux path asks the renderer whether the pages are imported.releasingentry and returns, so only one discard runs.DiscardVirtualMemory/MEM_RESETleave the contents undefined, and on Linux,MADV_REMOVEor the zeroing of imported pages wipes them. Either way the new owner's data was at risk, and both are covered. The large-pages andHOS_KEEP_FREED_PAGESearly returns are unchanged.rx_guest_importedis still asked at discard time. #2795 changed when the Vulkan side gives back a retired pool's import and doesn't touch this code.To take it out of draft (rule 3c, #2712): the bug needs to be shown in the game, not only in the test. A temporary log line is enough. Over a normal session, count the address-region frees, the allocations that had to skip a
releasingentry (the race actually being hit), and any heap resize that succeeds only because the floor went back up. Then run this head with the log removed and report no regressions, with the GPU, driver and OS named and a row in tested-configurations. If the race counter stays at zero over a long session, please say so. The change is still worth having as hardening, but then the worklist score should drop (see the inline note).CI: the only failure is the concurrent nightly case in
test_release_scripts.pyon ubuntu part 2/2. This diff doesn't touch the release scripts, andtest_address_regions.pypasses on both platforms, so I'd expect a rerun to clear it. There are no merge conflicts.Overlaps: #2478 adds another reader of the region table, and it will need to skip
releasingentries, as you noted. #2558 (lazy heap commit) now has to handle heap growth into space that used to belong to an address region. Since that space has already been discarded, it should just work. This PR is the smaller one, so I'd land it first and rebase both drafts onto it.The validation boundary remains explicit: no game run has established how often these paths are reached. The PR stays draft. The required temporary observations are frees, allocation skips while a range is retiring, and heap resizes admitted only by the raised floor, followed by an uninstrumented run. None of those counters has been measured yet, so the allocator fixture is not presented as a player fix or a RAM-saving result.
Removed the speculative worklist row in
45dfb561. The file now matches the PR base byte-for-byte; #2799 remains the incidence record and is unranked until a game run establishes reach/impact. This followup changes no host code or tests.