WIP: test(control): wait until the bridge listens, and reap it when a quit check fails #457
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!457
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/control-quit"
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-free suite on Linux or macOS.
tests/test_control.py's quit case can hangfor its whole 180 s limit and leave a bridge process running after the suite ends. On this Mac under a loaded suite it
happened in about 1 run of 150. This PR fixes the test fixture; the game's bridge is unchanged. Part of the macOS port
(#420). What is measured: on an Apple M4 Max with macOS 26.7.1, two new runs make the race certain. Without the fix
both hang, 5 of 5 each, with the forked bridge left running; with it,
quit slowpasses 20 of 20 andquit failendswith 1 at once, 20 of 20, nothing left running.
tests/test_control.pypasses for arm64 and x86_64 (40 PASS lines each).And the whole suite is compared by test name with
main. Not measured: Windows (its run list is unchanged) and Linuxruns.
Depends on #456 (
mac/control-sockets): this branch carries its two commits,3e335a41andf1f1843d.Why
The quit case hangs the check, and leaks a process, whenever the bridge it starts is a little late to listen.
Problem
quit_caseintests/control_test.cforks: the child runs the bridge, and the parent waits until the socket's fileexists, then connects once (
CHECK(connect(...) == 0)).bindand listens afterwards (transport_openinhost/control.c). A connect betweenthe two is refused (
ECONNREFUSED, measured on macOS; Linux refuses it too [inference: not run here]).CHECKprints and callsexit(1). The child keeps serving and holds the parent's stdout and stderr, sotest_control.py'ssubprocess.run(..., capture_output=True, timeout=180)waits for those pipes until its limit.running on this Mac, with a listening socket and no client.
longshot_ctl.py launchretries until the bridge answers.
What changed
tests/control_test.c:hos_control_servehas returned (afterlisten),instead of for the socket's file;
CHECKends and reaps the forked bridge before the process exits (check_failed, POSIX only);quit slow(the test build's bridge waits 300 ms betweenbindandlisten) andquit fail(a checkfails once the child serves; it must end with 1 and leave nothing running).
host/control.c: a hook for the test build only, under#ifdef HOS_CONTROL_NO_GAME:hos_control_test_bound,called between
bindandlistenwhen set. The game never definesHOS_CONTROL_NO_GAME; outside those blocks the fileis byte for byte the control-sockets PR's.
tests/test_control.py: on Linux and macOS it also runsquit slow(must pass) andquit fail(must end with 1and the injected failure). Windows' run list is unchanged.
Rebase onto
5e072fc8:test_control.py's run list sits under #58's compile command; #58's command is kept as it is.Flags and settings
None.
Measurements
main:
5e072fc8c2adadba5ea610148be1c47800ba0cca(2026-11-14)this PR:
mac/control-quit@2695995219b146707cd56958824dad6133ec9f6eBaseline:
mac/control-sockets@f1f1843d, with this PR's hook and two runs but not its fix (maincannot build thecontrol fixture on macOS at all, so the race cannot be shown there).
Hardware: 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:
control_testbuilt astest_control.pybuilds it on macOS (native arm64), then run<folder> quit slowand<folder> quit failwith stdout and stderr on pipes: 5 times each without the fix, 20 times each with it, 20 s at mostper run (a run past 20 s is a hang). Then
tests/test_control.py --cc clang --arch arm64and--arch x86_64, and thecontrol checks through
tests/run_all.pyonmainand on this PR, interleaved, two runs each.How to reproduce:
python -I -B tests/test_control.py --cc clang --arch arm64(and--arch x86_64), with a shortTMPDIR.f1f1843d+ hook, no fix26959952quit slowconnect, its child serves onquit failtest_control.py --arch arm64/x86_64tests/run_all.py(run 1 / run 2)5e072fc826959952tests/test_control.pycontrol.cdoes not compile on macOS)tests/test_longshot_ctl.pytests/test_oot_state_tap.pyvsnprintfmacro, which the runner PR repairs)Earlier, on the old base (
ab92a95f, on the port's integration branch, the same fixture and hook): the originalquit case hung 8 times in 1,200 runs under load (7 of 900 arm64, 1 of 300 x86_64), each the refused
connect. With thefix, 0 hangs in 1,900 runs (arm64 and x86_64, idle and loaded). Not rerun here.
Testing
Tested: the above, and the whole suite,
tests/run_all.py, 6 at once, on the same Mac:tests/test_control.py, FAIL onmain(the bridge does not compile on macOSthere) → pass here in 12.4 s, with the quit runs. No control fixture was left running after the suite. (The
control-sockets PR's own suite run, without this fix, met the quit race under load and left one running.)
main: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. The port's other PRs fix them, apart from three thatare stale on
mainon every platform (test_framegen_resources,test_module_ids,test_pad_layout).Not tested:
check_failedonly prints and exits there, as before.Needs other testers:
tests/test_control.py. Expect the two new quit PASS lines and no hang (request to open).tests/test_control.py. Expect the same cases asmain(request to open).Screenshots
Nothing on screen.
Risk and rollback
HOS_CONTROL_NO_GAME) compiles.hanging it.
Reviewer notes
quit_caseandcheck_failedintests/control_test.c.listen, rename). That changes the game on every platform, and the real clients already retry.
26959952, this PR's own commit;3e335a41..f1f1843dis the control-sockets PR.Issues
See #420.
Checklist
For everyone
type(scope): description).main. (Branched from5e072fc8, on the control-sockets PR it depends on.)python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above). (526 passed,17 skipped, and 18 failures that also fail on
main, named under Testing.)requests are still to open.
testchange: the new runs hang without the fix and pass with it; the control checks onmainand 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. (The two new runs hang without the fix andpass with it; on
mainthe fixture does not build on macOS.)tests/that reads changed source updated in this PR, not weakened.host/control.cis only in the test build.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.