WIP: fix(runtime): support control bridge Unix sockets on macOS #456
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!456
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/control-sockets"
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 is for: anyone running the game's control bridge (
HOS_CONTROL) on a Mac, and the game-free check thatdrives it.
host/control.cuses Linux-only socket calls (SOCK_CLOEXEC,SOCK_NONBLOCK,accept4,SO_PEERCRED), so itdoes not compile on macOS. This PR gives the bridge's Unix socket a macOS path, part of the macOS port (#420).
What is measured: on an Apple M4 Max with macOS 26.7.1,
tests/test_control.pyfails onmain(the compile) andpasses here, two interleaved runs each, and passes for arm64 and for x86_64 under Rosetta (38 PASS lines each). The
non-Apple text of
host/control.cis byte for bytemain's. And the whole suite is compared by test name withmain.Not measured: Windows and Linux runs, macOS 14.4, an Intel Mac, a client of another user, a game run.
Why
host/control.c's transport is a Unix socket on Linux, written with Linux's socket flags,accept4andSO_PEERCRED.None exists on macOS, and macOS also differs in what a full listen queue and a closed peer look like.
What changed
host/posix/darwin_control_sockets.cand.h(new): the socket calls on macOS, with public APIs only (socket,accept,fcntl,getpeereid):fcntl, checked, and closed on failure;ECONNREFUSEDin bothcases, so a datagram probe (
EPROTOTYPEon a live stream socket) decides, without joining its queue. An unknownanswer leaves the file alone;
SOMAXCONN, so a burst of clients gets the existing busy reply; eight active clients stay thelimit;
SIGPIPEtells a half-close from a peer gone, so replies to ahalf-closed client are still sent.
host/control.c: small#if defined(__APPLE__)branches at those calls, andsun_lenwith the exact addresslength. The path-length check, the private folder checks and mode 0600 are unchanged. Removing the Apple branches gives
main's file byte for byte.tests/test_control.py: on macOS, it also builds and runstests/control_darwin_test.c: path bounds (103 and 104bytes), a live socket kept and a stale one replaced, a full live queue not taken for stale, the UID refusal, the
descriptor flags, an exec that must not inherit them, and
EPIPEwithoutSIGPIPE.--arch arm64or--arch x86_64builds both fixtures for that target and runs them through
arch.tests/control_test.cis unchanged.tests/test_oot_state_tap.py(added onmainby #58) buildshost/control.cinto its own fixture: on macOS it nowlinks
darwin_control_sockets.cas well, astest_control.pydoes. Its Windows and Linux commands are unchanged.docs/control.md: the macOS transport and its tests.host/hos_relaunch.c,host/discord_rpc.c,nx2/build.py(the build-flags PRcompiles every
host/posix/darwin_*.con macOS, this file included) andthird_party/.Rebase onto
5e072fc8.main's #58 changed the same lines oftests/test_control.pyanddocs/control.md:test_control.py: both argument sets are kept (--source-rootfrom #58,--archfrom this PR). The macOS build takes-I <source root>/hostand the bridge's sources from--source-roottoo. The Windows and Linux build is #58's,unchanged.
docs/control.md: both paragraphs are kept.Flags and settings
No runtime switch.
HOS_CONTROLstays off unless set. The test driver gains one option:tests/test_control.py --arch arm64/x86_64arch(Rosetta for x86_64)Measurements
main:
5e072fc8c2adadba5ea610148be1c47800ba0cca(2026-11-14)this PR:
mac/control-sockets@f1f1843db3c687b6b1e4017ef525690b324fd887Hardware: Apple M4 Max, 16 cores, 64 GiB, macOS 26.7.1 (25G241); Apple clang 21.0.0. No GPU.
Scene: none. Game-free checks.
Method:
tests/test_control.py,tests/test_longshot_ctl.pyandtests/test_oot_state_tap.pythroughtests/run_all.pyon
mainand on this PR, interleaved, two runs each.tests/test_control.py --cc clang --arch arm64, then--arch x86_64.host/control.cwith its Apple branches removed, compared withmain's. The whole suite on this PR,compared by test name with a
mainrun on the same Mac.How to reproduce:
python -I -B tests/test_control.py --cc clang --arch arm64(and--arch x86_64), with a shortTMPDIR.5e072fc8f1f1843dtests/test_control.py(run 1 / run 2)host/control.c:2014,SOCK_CLOEXECundeclaredtests/test_longshot_ctl.pytests/test_oot_state_tap.pyvsnprintfmacro, thencontrol.c's socket callsvsnprintfmacro onlytest_control.py --arch arm64/--arch x86_64host/control.cwithout its Apple branchesmain'sTesting
Tested: the above, and the whole suite,
tests/run_all.py, 6 at once, on the same Mac:By test name nothing changed, and the same 19 checks fail on both. But
tests/test_control.pyfailed here foranother reason than on
main: every case passed, the macOS ones included, and then itsquitcase hit thefixture's start-up race and ran into the 180 s limit (
TimeoutExpired: … 'quit'). The Mac's load was about 30 atthe time. The race is in
tests/control_test.c, not in the bridge: its parent connects once the socket's fileexists, before the bridge listens, and its forked bridge then serves on. It is the next PR's fix
(
mac/control-quit). That forked bridge was found still running after the suite, with its listening socket, noclient and the suite's pipes, sampled and ended.
test_control.pypassed in the four other runs of this head (twothrough
run_all.py, one per architecture), and in two at its first commit,3e335a41, whose test files are the same.The other 18 failures are
main's on this Mac, fixed by the port's other PRs or stale onmain:test_appimage_package_retry,test_collect_wide,test_framegen_resources,test_game_layout,test_low_memory_notice,test_mem_gate,test_mem_gate_children,test_mirror_trim,test_module_ids,test_oot_state_tap,test_pad_layout,test_pcache_header_read,test_pipeline_cache_file,test_pipeline_cache_save,test_pipeline_play_cache,test_prof_symbols,test_program_cache_cleanup,test_program_cache_one_variant.Crash reports: three from
MTLCompilerServiceduring the run; no game-free check uses Metal [inference: otherwork on this Mac].
tests/test_oot_state_tap.pywith the runner PR as well: on a scratch tree of this PR plus that PR's fixture repair(not a branch), it passes on this Mac.
Not tested:
main's, and their test commands are #58's.different one), and a game run.
fcntl: it is not atomic. Nothing on macOS execs while thebridge runs today:
host/hos_relaunch.creads/proc/self/cmdlineand returns at lines 79-82 before itsexecve. AmacOS relaunch must close inherited descriptors (for example
POSIX_SPAWN_CLOEXEC_DEFAULT) or stop the bridge first.The renderer's child launch already closes them (
posix_spawn_file_actions_addclosefrom_np,gpu/vk/vk_menu.cpp:2115).Needs other testers:
tests/test_control.pyand the whole suite. Expect the named pipe's cases and the same results asmain(request to open).tests/test_control.py --arch <its arch>, expecting the same 38 PASS lines (request toopen).
Screenshots
Nothing on screen.
Risk and rollback
oldest supported macOS should run them.
HOS_CONTROLis set. Rollback: revert the PR; Windows and Linux are the same either way.Reviewer notes
host/posix/darwin_control_sockets.c, thencontrol.c's Apple branches, thentests/control_darwin_test.c.Written from Apple's SDK headers and the
socket(2),fcntl(2),accept(2),listen(2),connect(2),getpeereid(3),poll(2)andsend(2)pages.f1f1843dis new since the first version (cef1193a): #58'stest_oot_state_tap.pybuildscontrol.cand met thisPR's helper.
host/discord_rpc.cis a socket client of its own and not part of this PR.test_control.pybuilds on macOS, and its quit case can then hang under load (above), as it can onLinux [inference: not run there]. The control-quit PR, stacked on this one, fixes that fixture; best merged right
after it.
3e335a41andf1f1843d: the whole branch is this PR's own (it depends on no other PR).Issues
See #420.
Checklist
For everyone
type(scope): description).main. (Branched from5e072fc8; #58's conflicts resolved as above.)python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above). Everyfailure also fails on
main, buttest_control.py's fails here at the fixture's quit race (under Testing), whichmac/control-quitfixes.requests are still to open.
fix: the test fails onmainand passes here, two interleaved runs each.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.mainand passing here.tests/that reads changed source updated in this PR, not weakened.main's byte for byte.View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.