linux: the process affinity mask is the process's, not the asking thread's #1430
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!1430
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/linux-process-affinity"
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
On Linux,
GetProcessAffinityMask(host/posix/win32_posix.c) returnedsched_getaffinity(0): the calling thread's mask, since Linux has no process-level mask apart from its threads'.hos_thread_pinuses it to decide whether the reserved core is available for the Presentation Thread (render_thread_on_reserved, 2026-11-08). By then the asking game thread has itself been pinned off the reserved core, so the "process" mask had no reserved core in it, the test fell through to the else branch, and the Presentation Thread was pinned with every other game thread. The feature never applied on Linux, and itsthreads: Presentation Thread on the reserved corelog line never appeared.The mask is now captured once in a constructor, before
main, when nothing is pinned yet, which matches what Windows reports.Seen
Ryzen 9 9950X3D (16 cores, 32 logical processors), v2.0.11 Linux build, default switches. Every game thread including
Presentation ThreadhadCpus_allowed_list 0-14,16-30; the reserved core (15+31) held only themkw_vk displaythread. The log had nothreads:line.With
HOS_PIN='Presentation=80008000'as a stand-in for the fix, the Presentation Thread ran on15,31next tomkw_vk display, the layout the pin intends.Checked
clang -fsyntax-only -Wall -Wextraon the file: clean.0x8, the captured mask still0xffffffff.HOS_PINrun above exercised the same final affinity.Both copies of win32_posix.c (the game's and hookshot_vk.so's) get the constructor; only the game's copy feeds
hos_thread_pin, the .so's servesGetSystemInfo.🤖 Generated with Claude Code
Looks right to me. sched_getaffinity(0) is per-thread on Linux (pid 0 means the calling tid), so once hos_thread_pin has moved the asking thread off the reserved core, proc & ~reserved can never contain it and the Presentation Thread falls through to the else branch. Capturing the mask once in a constructor, before any thread is pinned, gives hos_thread_pin the same "process" mask Windows reports from GetProcessAffinityMask. It still honours a taskset or cgroup restriction applied at launch. The only other caller is GetSystemInfo (dwActiveProcessorMask), which gets the launch mask too, also what Windows does. The constructor in the hookshot_vk.so copy is harmless, as you say.
Nit: the lazy fallback (
if (!process_affinity) init_process_affinity()) re-reads the calling thread's mask, which is the old bug, just only when the constructor failed. That's so unlikely that I'd leave it, or default the mask to all CPUs instead. A thread-safety fix isn't needed either, since the write is idempotent.No test covers this, which is fair, since it needs real affinity and a pinned thread. No CI checks are reported on the branch. I didn't build it.