os: no vcore checkpoint in WaitAny's scan while it holds mw_lock #1696
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1696
Loading…
Reference in a new issue
No description provided.
Delete branch "os-waitany-scan-no-checkpoint"
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?
What this fixes
Since #1353, the game occasionally freezes for about a quarter of a second (~270 ms). WaitAny's scan could give its virtual core up while holding
mw_lock; signalling threads then block on that lock, and the scanner waits for the 250 ms vcore escape.Why merge
Change
host/nn_os_sync.c:obj_nockpt(lookup without the checkpoint) andevent_peek(event_wait(e, 0, 1)'s try path) for every read undermw_lock.tests/test_multiwait_wake.py: runs the real scan against counting stubs in every wait mode and event state; allowlist check that nothing undermw_lock, or holding a lock the scan takes, can wait for a core.Tested
python -I -B tests/test_multiwait_wake.py --cxx g++(and clang++): fails on main atwait_any (holding mw_lock), passes here.HOS_MW_WAKE=allas control. Frames over 140 ms: 0 of 18 runs, against 8 of 29 with #1353 alone. No frame over 100 ms. CPU ms/frame −8 to −12% (Kokiri 14, Hyrule 4 pairs), fps unchanged.Not covered
🤖 Generated with Claude Code
Looks right to me. The analysis checks out:
obj()'s checkpoint can go intovc_yield->vc_release/vc_acquirewhile the scanner holdsmw_lock, and a signaller that holds a core then blocks inpoke_multiwaiton that SRW lock without giving the core back, so the scanner only gets going again at the 250 ms escape. With this change nothing undermw_lockcan wait for a core.obj_nockptcan still taketable_lockon a miss, but nobody holdingtable_lockcheckpoints.event_peektakese->lkinsidemw_lock, the same orderevent_wait's try path already used, andevent_signaldropse->lkbefore it pokes. The only place that gives the core up ishos_cv_sleep, which releases the lock beforehos_vcore_back.No starvation that I can see. WaitAny still checkpoints once at entry (
obj(multiwait)), the scan is bounded bynlinks, and if a kick lands during the scan it stays set onvc_kick. It gets honoured at the next sleep (the core is released) or at the next nn::os call. Wakeups behave as before: the scan reads exactly what it read before, minus the checkpoints. The change is platform-neutral, so Windows and Linux behave the same. The test is already inrun_all.py(--cxx {cxx}), and CI is green on both OSes.One thing to coordinate: draft #1543 (mikaba-eu) makes the same fix in
nn_os_sync.c. It splitsobj()intoobj()+obj_cached()and switchesholder_ready/wait_anyto it, and addstests/test_multiwait_checkpoint.py. Whichever lands second will conflict and should drop its copy. The test here covers more, so I'd keep this one.Nit: as far as I can tell this behaves exactly like
event_wait(e, 0, 1). Withtry_onlyset, the spin, the sleep andwake_lat_noteare all skipped already. So it doesn't change behaviour, it just makes the guarantee explicit (and easier for the test's call-graph check to see). Fine to keep. The comment could say it's equivalent today and that it exists so the scan never picks up a scheduling point ifevent_waitchanges later.