WIP: fix(runtime): handle windowless Windows stop requests #102
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
needs measurements
question
testing wanted
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!102
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:fix/windows-graceful-stop"
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
A windowless Windows game has no game window to receive a polite close request, so a tester had to force it and the co-op relay saw a silent disappearance instead of a leave. This WIP adds a bounded normal-close attempt for delivered window and console events, without claiming that quitting finishes a save.
What changed
host/windows_stop.c: a hidden top-level window receivesWM_CLOSEand confirmedWM_ENDSESSION; console handlers accept Ctrl+C, Ctrl+Break and OS-ending events. They request work once, not perform renderer, save, network or log work in the console handler. Repeated events do not extend the deadline.rx_close_asked()andhos_window_closed_exit(). The message thread limits event-triggered close to four seconds; if the process remains alive, it terminates its own process with exit code 124. That is an incomplete, forced stop, not a successful graceful shutdown.host/display.c: installs this route only forHOS_HEADLESS, after renderer startup. Creating/freeing the renderer's temporary preparation console resets console handlers; installing earlier would lose the handler. Control quit remains the startup route.host/control.candhost/hos_relaunch.c: share the normal-close tail and recordstop: normal close work returned; save drain unsupportedbefore the log flush and exit. It means the callbacks returned, not that guest saves or every cache write succeeded.tests/test_windows_stop.pydrives the production callbacks against game-free Win32 stand-ins, including a stuck worker, message flooding, cancellation and startup errors. The existing Windows startup fixture also checks installation and its failure path.docs/development.mdanddocs/control.md: distinguish control quit, console events, window messages, Linux signals and force; document the startup limit, deadline and unsupported save draining.The hidden window is never shown or activated and is not the renderer's surface. A message-only window was rejected: it cannot be enumerated and does not receive broadcasts. A console handler alone would not address the reported
taskkillroute or Windows' user32/logoff caveat. Actual plain-taskkill delivery is still untested.Flags and settings
HOS_HEADLESSHOS_CONTROLNo new setting is added. The four-second deadline applies only to the new Windows-event route; control quit retains its existing behavior.
Measurements
main:
f41db065cf(pinned base, 2026-11-12)this PR: move/fix/windows-graceful-stop @
830b2c5908Base whole suite: 509 passed, 0 failed, 19 skipped (shared base result).
Head whole suite: 510 passed, 0 failed, 19 skipped.
Hardware: Arch Linux 7.2.15-1-omarchy, native GCC 16.2.1 and Clang 23.1.3; Windows target uses the llvm-mingw 20261104 UCRT headers/libraries. No GPU was used.
Scene: authored game-free fixtures, no game or save.
Method: one failing startup-fixture check against main's unchanged display source, one passing branch check, and the new stop driver with both native compilers. These are behavior checks, not interleaved game measurements or timing claims. The whole suite below belongs to the exact head above; main's supplied passing suite was not repeated.
How to reproduce:
python -I -B tests/test_windows_stop.py --cc gcc, then--cc clang;python -I -B tests/test_windows_display_startup.py. Run the latter with--sourcenaming main'shost/display.cto observe the missing stop route.python -I -B tests/run_all.pyruns the complete game-free suite.f41db065c830b2c590The baseline additionally rejects the missing setup-failure exit/log behavior. Its display source has the same Git blob as the pinned base. The new stop implementation is absent on main; no baseline Windows event-execution comparison is claimed.
Testing
Tested: On Linux, the named stop driver passes with GCC and Clang;
tests/test_windows_display_startup.py,tests/test_menu_restart.pyandtests/test_control.pypass. The complete exact-head suite reports 510 passed, 0 failed, 19 skipped. The completehost/windows_stop.c,host/display.c,host/hos_relaunch.candhost/control.ccompile to Windows x86-64 objects with-Wall -Wextra -Werror. Both new fixtures compile and link for that target usingtests/test_windows_stop.py --compile-only --cc COMPILER; the actual stop and relaunch objects also link with authored logging/renderer stand-ins. Generated runtime declarations came only from the public a64-kit source, not game data. Initial cross-compilation setup failures were unused link flags on compile-only commands and missing generated declarations; after correcting those inputs, the affected commands passed. No warning was suppressed.Not tested: Nothing was executed on Windows: neither fixtures nor game. No game was built or run on Linux either. Actual
taskkill, console delivery, hidden-window visibility/focus, logoff/shutdown, forced-timeout behavior, startup termination, saves, GPU/driver teardown, co-op leave receipt and hosted-relay/session shutdown remain untested. No performance, completed-save or guaranteed-delivery claim. This is not ready for merge.Needs other testers:
taskkill /PIDwithout/F, and inspect the actual game exit and normal-close marker; expect delivered WM_CLOSE to take normal close without showing a window (request to open)Screenshots
Nothing on screen. The added stop window must remain hidden; this has not been observed on Windows.
Risk and rollback
Four seconds can cut short a slow renderer/cache close; the existing preparation stop alone can wait ten seconds. Use the control command when that time is needed. The new watchdog deliberately avoids logger locks, so a blocked writer or forced OS end may leave no final timeout line. Require both the normal-close marker and an observed exit 0; even that is not save completion. A missing marker, exit 124/nonzero, or no exit is incomplete.
The hidden window and two waiting host threads exist only in headless Windows mode after renderer initialization. Console attachment, debugger handling and terminal input mode affect delivery. Windows can impose a shorter deadline or terminate without delivery. Reverting this commit removes the new route and restores the separate control-close tail; control quit remains available.
Reviewer notes
The promised investigation found no save-drain primitive in main. Guest Commit is a no-op and neither old close route waits for a complete guest save transaction. This change does not invent that guarantee.
Co-op is not on main. Pull request 68 inserts its bounded
coop_shutdown()intohos_window_closed_exit. When integrating, retain that call once in the common tail before the normal-close marker, not a second copy in control quit. It attempts BYE/LEAVE and a worker/relay stop; it does not acknowledge network delivery or drain reliable events. That combination still needs the Windows tester above. Pull request 47 supplies owned-session exit observation and explicit force, and correctly reports save draining unsupported.Windows API references: console handler return/timeout contract, user32/logoff and handler-reset rules, message-only window limits.
The original report and promise are https://github.com/NotRexed/Hookshot/pull/2274#issuecomment-6812494715 and https://github.com/NotRexed/Hookshot/pull/2274#issuecomment-6812576925 . This is source/fixture evidence, not a new Windows reproduction. The branch stays on its tested base while main has advanced; rebase and runtime evidence remain readiness requirements.
Issues
See #83
Checklist
For everyone
type(scope): description).main. Pinned tested base; main advanced during this investigation.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.mainand passing here.tests/that reads changed source updated in this PR, not weakened.New on this forge; not opened on GitHub.
Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra [email protected]
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.