Skip to content

fix(vcores): hos_vcore_back ends an escape too, so no inner block takes a core early - #2387

Merged
hckrman101 merged 1 commit into
NotRexed:mainfrom
mooki000:fix/vcore-escape-unpaired-back
Nov 11, 2026
Merged

hckrman101 merged 1 commit into
NotRexed:mainfrom
mooki000:fix/vcore-escape-unpaired-back

Conversation

@mooki000

Copy link
Copy Markdown
Contributor

What this is for: this makes the virtual-core escape fix of #2302 (issue #1929) safe against one kind of future change. @hckrman101 asked for it as an optional point in his review (comment). Nothing that runs today changes, because no call in the code reaches the case: it is there so that a later hos_vcore_back() without its hos_vcore_away() cannot leave an escape flag up. What is measured: the test with g++ and clang++, on this branch and against main's host/hos_thread.c, and the whole of tests/run_all.py on Arch Linux. Not measured: no game run, because no call reaches the changed case. Windows was not tried.

Problem

  • A guest thread that waits too long for a virtual core runs without one (an escape, vc_acquire), and vc_escaped is set. Since fix(vcores): an escaped thread takes a core again after its next block (#1929) #2302 the thread's next hos_vcore_away() answers 1 and clears the flag, so the hos_vcore_back() paired with it takes a core again.
  • hos_vcore_back() itself leaves the flag alone. Today every back follows an away that answered 1, except the two at a thread's start (hos_boot_main_thread, thread_main). No escape can be up there yet: the thread is new and zeroed, and that back is its first wait for a core. So the case below cannot happen now.
  • Suppose a later change calls back without its away on a thread that has escaped. The thread would take a core and keep the flag. Its next outer block would give the core up as usual. A block nested inside that one would then answer 1 and take a core in the middle of the outer block, which is the opposite of what the nesting rule is for.

What changed

  • host/hos_thread.c hos_vcore_back: clears vc_escaped before vc_acquire, which sets it again if the thread escapes again. So after any back, the flag is up only when the thread is again running without a core. The comment on the struct field says this now.
  • Why back and not vc_take (the other place the review named): a thread waiting in the queue gets its core from vc_grant. vc_grant runs on the thread that gives the core up, so a clear in vc_take limited to t == cur would miss that path.
  • tests/test_vcore_escape.py has two new cases.
    • An escaped thread calls back without an away, then nests two blocks. The inner away must answer 0, and no core may be taken inside the outer block.
    • The same kind of back escapes again while both cores are held. The thread must still take a core after its next block.

How it was tested

Arch Linux, kernel 7.2.15, g++ 16.2.1, clang++ 23.1.3, 2026-11-11. Branch head b52842d on main b744b1e.

  • The test on this branch, with g++ and with clang++: ok.
  • The same test against main's host/hos_thread.c (b744b1e), with both compilers:
FAIL: after a back without an away, a block inside a block takes no core after it
FAIL: no core is taken inside the outer block after a back without an away
  • The second case checks where the clear goes. With the clear placed after vc_acquire instead, the flag of an escape that has just happened is wiped, and 8 checks fail with both compilers.
  • tests/test_invalidate_vcore.py, which cuts hos_vcore_away and hos_vcore_back out of the source, passes with both compilers.
  • tests/run_all.py: 373 passed, 4 failed, 10 skipped. Two of the failures fail on this machine whatever the change: test_updater_asset_digest.py (only the .NET 10 SDK is installed here, and the fixture needs net8.0) and test_wayland_libdecor_hint.py (clang 23's -Wunused-but-set-global under -Werror). The other two, test_vcore_fs_open.py and test_vcore_video_acquire.py, fail on main itself until test: test_vcore_fs_open and test_vcore_video_acquire build again with env_on #2380 lands: since fix: switches that behave as players expect, and dead ones removed #2042 they no longer compile.

Not tested

  • No game run. The changed line runs at every back, but no call today has a flag up there, so I expect no difference in the game. That is an inference from the code, not a measurement.
  • Windows.

Risks

  • One store to the thread's own field at each back. The flag is touched only by its own thread, as before.

Closes #2386.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com

…es a core early

A guest thread that waited too long for a virtual core runs without one
(an escape, vc_acquire) and has vc_escaped set; since NotRexed#2302 its next
hos_vcore_away answers 1 and clears the flag, so the matching
hos_vcore_back takes a core again. hos_vcore_back itself left the flag
alone. Every back today follows an away that answered 1, except the two at
a thread's start, where no escape can be up yet, so this is not reachable
now. A later back without its away on an escaped thread would take a core
and leave the flag up; the thread's next outer block would release the
core, and an inner block inside it would then answer 1 and take a core in
the middle of the outer block. hos_vcore_back now clears the flag before
vc_acquire, which sets it again if the thread escapes again.

tests/test_vcore_escape.py: a back without an away on an escaped thread,
then a block inside a block (the inner away answers 0 and no core is taken
inside the outer block); and such a back that escapes again (the next
block still takes a core).

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mooki000

Copy link
Copy Markdown
Contributor Author

Rebased onto main b29967bf so the workflows run here: they had waited for an approval on the old head, and main's game-free tests are green again since #2380. The patch is unchanged (git range-diff: the commit is equal), and its test passes on the new base.

@hckrman101 hckrman101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for following up on the #2302 nit. Clearing vc_escaped in hos_vcore_back before vc_acquire is the right spot: vc_acquire sets the flag again if the thread escapes again, so after any back the flag is up only while the thread really is running without a core. Your reason for not putting it in vc_take holds too, since vc_grant runs on the releasing thread.

What I checked:

  • Races: vc_escaped is still written only by its own thread (vc_acquire on t, hos_vcore_away, now hos_vcore_back). vc_grant/vc_release never touch it, so the extra store at each back adds no cross-thread access.
  • Nesting stays balanced: the flag can only be up while vc_held == 0, so the outermost away still answers 1 and clears it, inner ones answer 0, and only the outer back takes a core. The paired callers (hos_thread_wait, hos_cv_sleep, the other if (away) hos_vcore_back() sites, the #2301/#2322 users) behave exactly as before. The two unpaired backs at thread start see a zeroed flag, so nothing changes there today, as the description says.
  • vc_yield goes through vc_release/vc_acquire directly rather than back, so a re-escape there still leaves the flag up until the next block, as intended.
  • The two new test cases cover the right things, and the second one (re-escape inside back) guards against moving the clear after vc_acquire.

CI: test_vcore_escape.py and test_invalidate_vcore.py pass on Ubuntu and Windows. The two red jobs aren't from this PR: Windows part 1/2 fails in test_native_astc_texture_count.py (scanf deprecated under -Werror with clang on Windows), and appimage timed out fetching system packages.

Looks good to me.

@mooki000

Copy link
Copy Markdown
Contributor Author

Thanks for reviewing the flag ownership and re-escape cases. The unrelated Windows ASTC fixture failure is tracked in #2397; no further scheduler change is needed for that CI failure.

@hckrman101
hckrman101 merged commit bce1476 into NotRexed:main Nov 11, 2026
10 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vcores: hos_vcore_back leaves the escape flag up (not reachable today; review point from #2302)

2 participants