Repository navigation
fix(vcores): no core held while OpenFile builds the button prompts (#1929) - #2301
Conversation
…otRexed#1929) The first open of a button-prompt file builds it inside host_path (prompts.c), 251-357 ms on Linux, and other threads that open it meanwhile wait there for the build. OpenFile gave the virtual core up only around CreateFileA, so the opener kept its core through the build, and four threads waiting for that core ran without one (the escape) within 50 ms. The core now goes before host_path, with the same away/back pair. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Corroboration for the host-hold class, from our Linux runs. We independently captured a vcore held across a host wait in Two details worth checking in the final diff:
We have a different local patch in 🤖 Generated with opencode using DeepSeek V4.1 Flash. |
|
Thanks for reading it. Both checks, from the branch head (3ca9b79), Nothing of the game runs between the away and the back. I read every call between line 332 (
None of these is handed the guest context. I searched Every return takes the core back. There are five ways out after line 332 and five One thing I left as it is on main, so that it is no surprise: Agreed on the scope: it is a first-start path, not the cue. Our numbers say the same. In the issue's steps with the textures already built, no escape stood behind the build, and the change does not move the count: 6, 2, 3 escapes per run with it against 7, 8, 2 without (3 runs each, 250 ms). Only with the cache key deleted before the start does it show (30 s runs, 250 ms, 3 each): 1, 6, 1 without the change, 1, 1, 1 with it. How those runs were made, so you can compare: |
ghost
left a comment
There was a problem hiding this comment.
Looks right to me. I read OpenFile at 3ca9b79 end to end: there are five ways out after hos_vcore_away() (unknown mount, write to a read-only mount, HOS_NO_MOVIES, a blanked icon, and the one right after CreateFileA that both not-found and success go through), and each one takes the core back exactly once. host_path drops mount_lock before it returns, so the thread never waits for a core while holding the lock. Keeping the release out of hos_prompts_romfs for that reason is the right call.
Nothing between the away and the back calls into guest code. The only guest memory touched is the path string, which is only read (and host_path copies it once), and the handle is written to guest memory after the core is back. That answers both of CIB's checks. As you say, the Mount/Unmount thread that waits for mount_lock with its core held during a build was already there on main and isn't made worse here.
The test compiles the real function and covers every exit plus the no-core caller. It's picked up by run_all.py through the file name, with RUN_ALL_ARGS for the compiler.
On the siblings: no files overlap with #2302 (hos_thread.c/hos.h) or #2322 (nn_video.c), so they can land in any order. They also work together: with #2302, hos_vcore_away() returns 1 for a thread that escaped, and since every exit here pairs it with hos_vcore_back(), an escaped thread that opens a file takes a core again on its way out, which is what #2302 wants.
CI: only the title check has run. The Linux, Windows and source-regression workflows are waiting for a maintainer to approve the run, so they need to go green before merge.
|
Thanks for reading it end to end, and for checking it against #2302. Understood on CI: the three workflows wait for a maintainer's approval; I'll watch them and fix anything that comes up red. |
|
Since this pull request and #2042 met on main (merged two minutes apart), |
When the button-prompt textures have to be written again (a first start, an update that changes them, another controller
face),
OpenFilebuilds them insidehost_pathwhile keeping its virtual core: over 350 ms on Linux, and the threadswaiting for that core escaped and ran without one (#1929). This gives the core up for that build too, as
OpenFilealready does for
CreateFileA.What changed
host/nn_fs.cOpenFile: the existinghos_vcore_away()/hos_vcore_back()pair now starts beforehost_pathinstead of just before
CreateFileA.host_pathreacheshos_mods_romfs->hos_prompts_romfs, which builds theprompt font, textures or layouts on their first open (
write_patched: a 244 MB file read and written whenprompts/Common.keyno longer matches). Threads that open the same file during the build wait for it there(
while (state[which] == 1) Sleep(1)), and now do that without a core too. The early returns beforeCreateFileAtake the core back before they return. There is still one release and one take per open, as before. Everything
between
host_pathandCreateFileAis host code (redirects, the overlay check, a log line); no guest code runswithout the core.
hos_prompts_romfsinstead:host_pathholdsmount_lock(shared) there, andwaiting for a core while holding it could stall a thread that holds a core and wants the lock (Mount, Unmount). That
is the reason
hos_cv_sleeplets go of its lock before it takes a core again.tests/test_vcore_fs_open.py(new): compiles the actualOpenFilewith stand-ins that record whether the thread holdsits core in
host_pathand inCreateFileA, and checks every way out (opened, unknown mount, write to a mount thatis not writable,
HOS_NO_MOVIES, a blanked icon, not found), plus a thread that holds no core. It fails on main(
host_pathruns with the core held) and passes with this change, with g++ and clang++.How it was found and tested
Linux (Arch), RTX 4090, NVIDIA 615.78.08, headless, fresh profile. The game was built with this patch on main 07b0359
(nothing since then touches
nn_fs.c,mods.corprompts.c). A local diagnostic build that logs every holder's hoststack at each escape found the holder here:
(game) > OpenFile > host_path > hos_mods_romfs > hos_prompts_romfs > build_textures > write_patched > fread / fwrite.ARes Loading 0 had held core 5 for 357 ms by the last sample, asked to give it up, and four threads escaped within 42 ms
(ModuleSystemWorker5, GeneralFormatAsync_, ARes Loading 2, PreActorModelCompose).
The build runs only when
prompts/Common.keyno longer matches; otherwise the game reads the cached file and nothingwaits. So each run below removed
Common.keyfirst and ran 30 s with no input; the escape was set to 250 ms(
HOS_VCORE_ESCAPE_US/_EQ_US) so that waits over 250 ms show as escapes. Three runs each, interleaved, load 8-75(other builds on the machine):
The one escape left in every run is the same in both: PreActorModelCompose waiting for sead::MainThread (in the
diagnostic runs, sead::MainThread was in game code at that moment). The build made other threads escape in one of the
three runs on main (in the other two nothing escaped behind it), and in none with the change. At the 2 s default nothing
escaped in either arm (three runs each), and the longest core wait in the first 5 s was about the same (main 877, 515,
482 ms; this change 480, 537, 462 ms): the build is not the longest wait at start-up.
tests/run_all.pyon this branch's head (3ca9b79, on main 49f75dc): 363 passed, 2 failed, 10 skipped. The twofailures are the ones this machine has on every branch:
test_updater_asset_digest.py(only the .NET 10 SDK here, thefixture needs .NET 8) and
test_wayland_libdecor_hint.py(clang 23's-Wunused-but-set-globalunder-Werror).Not tested
Windows, AMD, the Steam Deck. On Windows the prompt files are built by the same code, so the same wait should happen
there, but it was not measured.
Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com