fix(control): wait for busy Windows pipes using the native error code #494
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8015c9a
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
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/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!494
Loading…
Reference in a new issue
No description provided.
Delete branch "monsterlag/Longshot:fix/windows-bridge-pipe-busy"
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?
Why
Back-to-back control clients on Windows can report "no game listens (Invalid argument)" while the bridge server is running. The CRT converts a busy named-pipe open into errno22 without retaining Win32 error231, so the client's intended busy retry never happens.
What changed
scripts/longshot_ctl.py: use typedCreateFileWfor Windows pipe opens and preserve the native error. Retry onlyERROR_PIPE_BUSYwithin the existing deadline; missing and denied pipes fail immediately. Transfer the handle to the CRT file object and close the appropriate owner on conversion failure, preserving the original exception.tests/test_windows_pipe_busy.py: five native test methods cover busy timeout/recovery, missing pipes, simulated access denial, handle-conversion failure and file-wrapper failure before/after descriptor ownership changes.docs/tested/files record the held-pipe regression comparisons; one more records the actual-game title interoperability check. POSIX transport and the JSON protocol are unchanged.Flags and settings
None. Connection timeout and CLI arguments are unchanged.
Measurements
main:
9c9d69ed09(2026-11-14)this PR: fix/windows-bridge-pipe-busy @
70c9af2731Hardware: Ryzen7 9800X3D, 96GiB RAM, Windows11 build26300, native Python3.14.7. Named-pipe fixtures use no GPU. The separate live check used RTX5090, driver617.42.
Scene: one held real Windows pipe, then rearming it; separate live check at the title menu in an independent headless game profile.
Method: focused checks as warm-up, then two sequential interleaved main/PR/main/PR runs of the same deterministic held-pipe timeout/recovery method against the actual production client modules. Report median failures; this is behavior evidence, not a speed claim. A separate raw probe compared CRT open with native CreateFileW on the same busy pipe.
How to reproduce: run
python -I -B tests/test_windows_pipe_busy.pyon Windows. For main, run the sametest_busy_timeout_and_recoveryfixture with main's client module. The fixture occupies the pipe, checks a bounded busy timeout, then rearms its server and verifies a connection succeeds.9c9d69ed70c9af27The measured source commit wasd0340ee9; later commits add only the five run records, with production/test files unchanged. Four consecutive fixed-client connections to the actual matched API22 game each returned a protocol1 status reply at the title menu (frames1339/1354/1367/1382, state=menu). Game and guard exited0. That is live interoperability evidence; it does not establish that the busy condition occurred in the game. The deterministic held-pipe fixture above is the before/after proof.
Testing
Tested: raw CRT/Win32 busy and missing probe; two native held-pipe regression pairs; all five new native methods; existing
test_oot_state_tap.pyandtest_longshot_ctl.pypass under the same toolchain as the original failure; independent read-only review of handle types, last-error capture and ownership/cleanup. Fullpython -I -B tests/run_all.py --jobs8onf0645813(the final commit adds only the separately validated live-run record): 535 passed, 2 failed, 34 skipped in161s. Both failures also occur on untouched main9c9d69ed in fresh focused checks:test_tested_configurations.py(newline assumption, fixed by PR493) andtest_walk_key.py(missing stand-in after Skip-title merged, already claimed by mooki). The originaltest_oot_state_tap.pyfailure passes here. Existing assertions/skips are unchanged; the new pipe suite is Windows-specific. Run-fragment validation andgit diff --checkpass.The independent title-menu check above used executable/renderer built together from main9c9d69ed and only this PR's client script. All23 protected player hashes remained unchanged. No binaries, game files or saves were transferred.
Not tested: a real ACL denial (access denied was simulated and asserts one attempt), high-contention/stress or nonfinite timeouts, Python versions besides3.14.7, live Linux/macOS transport. The POSIX branch is unchanged. The live game check covers status/quit, not gameplay input, audio or a save/checkpoint barrier. Native failures are tested with authored pipes; no security control was bypassed.
Needs other testers: None for this Windows connection correction.
Screenshots
Nothing on screen; diagnostic output is in Measurements.
Risk and rollback
Windows connection errors now retain their native code, so their text can differ from the old CRT error. The main risk is handle ownership; actual-pipe tests cover success and both conversion cleanup paths. Reverting this change restores the CRT open. There is no retry for missing pipes, access denial or arbitrary EINVAL.
Reviewer notes
Start at
open_windows_pipe.use_last_error=Trueand immediate capture preserve the error that the oldopen()path lost. Afteropen_osfhandlesucceeds the CRT owns the handle; afterfdopensucceeds the file object owns the descriptor. Cleanup suppression is confined to a descriptor close after file-wrapper construction has already failed, so that it cannot hide the original exception.Issues
Fixes #481
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.docs/tested/for each new measured run (format indocs/tested-configurations.md; no appended row or assembled view).mainand passing here.tests/that reads changed source updated in this PR, not weakened.WIP: fix(control): wait for busy Windows pipes using the native error codeto fix(control): wait for busy Windows pipes using the native error codeAdded the self-contained actual-game title interoperability record alongside the four authored-pipe regression records. Production and test code are unchanged; fragment validation passes after the data-only commit. The four live replies prove interoperability, while the deterministic held-pipe pairs remain the before/after busy-condition proof. Full-suite commit and remaining baseline failures are named explicitly.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.