Skip to content

fix(agc): wait for overlapping storage flushes - #7757

Closed
MarcZX10 wants to merge 1 commit into
boykopovar:mainfrom
MarcZX10:codex/wait-for-storage-flush
Closed

MarcZX10 wants to merge 1 commit into
boykopovar:mainfrom
MarcZX10:codex/wait-for-storage-flush

Conversation

@MarcZX10

Copy link
Copy Markdown
Contributor

What

When FlushPending finds no pending image for a range, it now waits if an overlapping image is already being written back by another thread, then retries the pending lookup. The wait releases the pending-list mutex, and the GPU-lock holder avoids waiting, preventing a lock cycle. The flush completion path wakes readers after removing in-flight images. Adds a synchronized regression to agc_driver_storage_file_write for a reader racing an overlapping flush. Fixes #7744.

Tested

Linux x86_64, Debian 12. cmake --build build-scalar --target agc_driver_storage_file_write_tests --parallel 4 passed. ctest --test-dir build-scalar --output-on-failure --repeat until-fail:20 -R '^agc_driver_storage_file_write$' passed 20/20 using Mesa llvmpipe. This host lacks the emulator-required LZCNT, BMI1, BMI2, MOVBE, AVX2 and FMA features, so I temporarily bypassed only the host CPU startup guard in the local test build; the test itself ran against lavapipe, and the guard source was restored after the run. On the unmodified build, CTest exits at that guard before the test starts. python3 tools/check_conventions.py --base upstream/main passed. No game or homebrew run.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: agc: a host read on another thread does not wait for an image the queue-idle store is writing back #7744
  • Unblocks: none
  • GPUs tested: Mesa llvmpipe (LLVM 15.0.6), Mesa 22.3.6-1+deb12u2, Debian 12 x86_64
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 862 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 1368 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 4448 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 5480 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6108 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6187 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6192 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6197 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6202 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6209 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6218 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6249 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6280 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6292 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6457 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6479 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6539 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6682 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6683 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6718 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6730 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6772 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6820 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6848 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6946 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6947 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6948 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6960 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6973 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6987 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6989 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6995 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7002 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7048 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7085 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7154 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7254 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7310 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7332 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7427 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7514 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7533 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7579 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7604 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7642 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7799 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7800 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7870 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7912 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7966 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp

@MarcZX10

Copy link
Copy Markdown
Contributor Author

Manual follow-up to the overlap report: I reviewed the flagged Texture.cpp hunks. The reports are broad same-file comparisons because those PR heads conflict with main; none changes the new FlushPending in-flight wait/notification protocol. The closest interactions are #6615, which only changes the refreshing accessor used in FlushPending; #7514, which adds range-index bookkeeping to PendingList while retaining its erase API (used here to remove a flushed texture); and #6960/#7048/#7604, whose relevant hunks concern guest-completion/write-back or unwatch/trace paths. The other reported hunks are in separate texture helpers. I found no direct overlap or required dependency for this change.

@oneandonlydean oneandonlydean self-assigned this Oct 19, 2026
@github-actions github-actions Bot added the fix error Closes an issue (Fixes #N) label Oct 19, 2026
@savvaniss

Copy link
Copy Markdown
Contributor

Intel and AMD Windows data: agc_driver_storage_file_write fails on main and passes with this PR, on an Intel UHD 770 and an AMD RX 9070 XT.

Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, Release, every execution test built, ANYPS5_NO_SHADER_CACHE=1, serial CTest. Intel UHD Graphics 770 (8086:a780), driver 32.0.101.7088. AMD Radeon RX 9070 XT (1002:7550), driver 32.0.31041.1004, with VkCache emptied first. This head 346cc6ee4 merged into main 6b8228b2b. "Without" means this PR's test files on main 6b8228b2b, with none of its other changes.

Tests, ×3 UHD 770 RX 9070 XT
This head: agc_driver_storage_file_write pass pass
Without fails: file write: concurrent guest read returned before an overlapping image was stored same

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the agc libSceAgc* label Oct 19, 2026

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking up #7744. I ran 346cc6ee4 on Linux, and the host read path from the issue still reads the old bytes with this change.

Machine: Ubuntu 26.04, i7-8700, GCC 15.2, Release, BUILD_TESTING=ON, full build. NVIDIA GeForce RTX 3070 (driver 595.99.02) and lavapipe (Mesa 26.0.3) on the same host. PR merged onto main 6b8228b2b (clean). ANYPS5_NO_SHADER_CACHE=1, -j1 --timeout 30.

The check is the repro from #7744 as a test: produce the pending surface, start a thread that takes the GPU mutex and calls FlushAllPending("idle"), wait until FindPending on the surface returns null, then GuestMemory::Read the surface. 20 rounds per run; a round fails if the read sees zeros.

RTX 3070 lavapipe
main 5 of 5 runs fail, 20 of 20 reads stale in each 5 of 5 runs fail, 20 of 20 stale in each
this PR 10 of 10 runs fail, 20 of 20 reads stale in each 10 of 10 runs fail, 14 to 20 of 20 stale
agc_driver_storage_file_write, PR 3 of 3 + 30 of 30 pass same
only the PR's StorageFileWrite.cpp, on main 10 of 10 fail: concurrent guest read returned before an overlapping image was stored same

So the new case does fail without the fix, but it only covers a reader that calls FlushPending itself before GuestMemory::Read. A plain GuestMemory::Read goes through the flush hook (FlushForAccess in Recorder.cpp), and that path misses the wait twice:

  1. With no image left in pending.textures for the range (images == 0, the default HookFlushCpuBlocks() off), the hook calls PublishShadowsOnly and never reaches FlushPending. The image being written back is only in pending.flushing at that point, so this is the usual case for the race.
  2. Even with APS5_HOOK_FLUSH_CPU_BLOCKS=0, FlushPending still returns false after the new wait (flush stays empty), so the hook skips its SyncThrough and does not wait for the recorded store either.

When I tried the same FlushPending-only approach on #7744 it behaved the same way. It only passed after FlushPending returned true after waiting and the hook's images == 0 branch waited on pending.flushing too: 0 stale reads in 200 on both devices. That change is open as #7769 (with the StorageReadDuringIdleStore test that fails on this PR); happy to fold it into whichever lands.

agc_driver_storage_file_write passes on main too here (30 of 30 on each device, idle host), so it doesn't show the race without load.

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks @jotitadev, you're right, and I missed the hook path when I looked at this. With APS5_HOOK_FLUSH_CPU_BLOCKS unset, AccessKeptByCpu only counts images still in pending.textures, so an image another thread is storing gives images == 0 and the hook never reaches the new wait. And on the FlushPending branch the wait returns false, so the hook skips its SyncThrough.

Reproduced on an RTX 4090 (Linux) with your StorageReadDuringIdleStore: main 20 of 20 reads stale in 3 of 3 runs, this PR the same, #7769 0 of 20 in 5 of 5. #7769 with this PR's StorageFileWrite.cpp also passes 5 of 5, so it covers the direct FlushPending reader too. Folding that test into #7769, as you offered, sounds like the right way to keep both cases covered.

jotitadev added a commit to jotitadev/AnyPS5 that referenced this pull request Oct 19, 2026
Add the concurrent flush case from boykopovar#7757 to agc_driver_storage_file_write:
while one thread's FlushPending stores the surface under a held GPU
mutex, a second thread calls FlushPending and then GuestMemory::Read on
the same range, and the read must see the stored bytes. Only the test
from boykopovar#7757 is taken here; agc_driver_storage_read_during_idle_store
keeps covering the flush hook path.

Co-authored-by: MarcZX10 <67475268+MarcZX10@users.noreply.github.com>
@MarcZX10

Copy link
Copy Markdown
Contributor Author

You’re right: this PR’s direct FlushPending wait does not cover the default FlushForAccess hook path when AccessKeptByCpu finds no pending image. I checked #7769; its current head includes both that hook-path wait and the StorageFileWrite.cpp concurrent-flush regression from this PR. The hook-path regression is therefore represented in #7769, while this PR only covers readers that call FlushPending directly. Thanks for identifying the gap and carrying the broader test into #7769.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks @MarcZX10. Closing this as covered: #7769 has merged (535587c), and it includes your concurrent FlushPending test from this PR in StorageFileWrite.cpp (ba72cb7, co-authored by you), so the direct-reader case stays covered on main next to the flush-hook case from agc_driver_storage_read_during_idle_store. Your report and test were what made that fix complete.

@MarcZX10

Copy link
Copy Markdown
Contributor Author

Thanks for confirming the direct-reader test is included in merged #7769 alongside the flush-hook coverage. I appreciate the clarification and the credit; that closes the remaining status question on this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* fix error Closes an issue (Fixes #N) needs rebase Conflicts with main, rebase needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

agc: a host read on another thread does not wait for an image the queue-idle store is writing back

4 participants