WIP: fix(vk): a game ended while it closes, after a quit during the precompile, no longer counts as a crash #498

Draft
mooki wants to merge 2 commits from mooki/Longshot:fix/double-quit-precompile into main
Member

Depends on #428. This branch is #428's commit (4dd978d6) plus one commit. Review it after #428, or fold it into #428.

Why

Quitting during the start's precompile and then quitting again still counted as a crash. That is the usual reaction when
the game stops answering after the first quit. Two such starts dropped the precompile's notes.

With #428, a quit during the start's pipeline steps does not count as a crash. Those steps are the precompile, the
create-mode warm, the predicted pipelines and the stage libraries. But #428 puts each step's file back only at the end of
the close. Before that, the close waits up to 10 s for the preparation's workers to stop, on the window's thread.
Meanwhile the window does not answer:

  • Windows marks it "Not Responding" after 5 s and lets the player close it.
  • The desktops on Linux and macOS offer Force Quit.

A second quit there ends the process before the files are put back, so the start counted as one that crashed in the step.
After two such starts the notes were dropped and the warm was skipped. After one, the stage libraries were left out.

What this is for: a quit by the player or the control socket never counts as a crash, however the process ends after
it. A crash, or a kill with no quit asked for, still counts. What is measured: tests/test_quit_not_crash.py, extended
here. It now also has starts ended by a kill and quits whose process is then killed while it closes. It fails 78 of its 101
behaviour checks on main and 36 on #428, and passes here. Not measured: no game run, and nothing on Windows or Linux
(see Testing).

What changed

The problem (lines at #428's head 4dd978d6, and at main 9c9d69ed for host/ and the worker):

  • mkwvk_close_asked (gpu/vk/vk_main.cpp:1567-1580) runs in this order:
    • programs_census_log, module_ids_flush;
    • fsr4_close and pipeline_cache_close, each of which can wait up to 10 s in play;
    • while the shaders are prepared, prepare_stop() and WaitForSingleObject(prep_ended, 10000) (1573-1577);
    • and only then crash_markers::close() (1578).
  • The workers it waits for cannot cut a driver compile short, and the source names driver compiles of 15 s and 41 s
    (vk_draw.cpp:8728, 8742 at 5e072fc8). Once stopped, the worker also saves the pipeline cache before it signals that it
    ended (vk_main.cpp:1191 at main). So a compile under way uses up the 10 s.
  • The close runs on the thread that answers the window:
    • Windows: WM_CLOSE in the window procedure (host/display.c:489-493). During the start-up screen, that thread is the
      renderer's start (vk_main.cpp:1129-1132 at main).
    • Linux and macOS: the SDL event loop on the main thread (host/posix/display_sdl.c:348-351, 426-434).
  • "The operating system defines an application hang as a UI thread that has not processed messages for at least 5
    seconds", and its ghost window lets the user "close the unresponsive application"
    (Microsoft).
  • So the second quit ends the process before line 1578, and the step's file is left with the start's count plus one.

The change:

  • gpu/vk/vk_main.cpp: crash_markers::close() is now the first statement of mkwvk_close_asked, before anything that
    takes time. The rest of the close is unchanged and in the same order. The comment above the function says so.
  • gpu/vk/crash_markers.h: comments only. The header's rule is now "only a crash, or a kill that no quit asked for, may
    count". close()'s doc says it is called first, and that a worker that crashes the driver after the quit is no longer
    counted.
  • tests/test_quit_not_crash.py:
    • New ends of a start:
      • kill: ended from outside while the step works, with no quit asked for.
      • quit-killed-early: a quit whose process is then ended in the close's first part other than the files.
      • quit-killed-waiting: the same, ended while the close waits for the workers.
    • For every quit, a new sequence: a quit then a crash is one crash.
    • The close is now taken whole from vk_main.cpp, in its order. The preparation's stop and the crash_markers lines are
      kept as they are. Every other statement becomes a stand-in where the process can be ended.
    • #428's source check wanted crash_markers::close() after the wait. It now wants it as the close's first statement (the
      reason is under Risk).

Unchanged: the four steps, their files and thresholds, host/, and every exit path.

Flags and settings

None. No switch, menu row, ini key or default changes.

Measurements

main: 5e072fc8 (#428's base, 2026-11-14), and 9c9d69ed (2026-11-14) for the test against its sources
#428: fix/quit-is-not-a-crash @ 4dd978d6
this PR: fix/double-quit-precompile @ 6219fbbe
Hardware: Apple M4 Max (16 cores), 64 GiB, macOS 26.7.1 (25G241), Apple clang 21.0.0, Python 3.14.10. No GPU or driver is
involved: the checks are game-free.
Scene: none (game-free checks only)
Method:

  • tests/test_quit_not_crash.py from this branch, run on its head and against the other trees' sources (--source):
    main, #428, main, #428, this PR, then the same again.
  • One run each against main 9c9d69ed's sources, the macOS port's integrated branch (1c53cff1, which carries #428), and that
    branch with this commit applied.
  • Object compiles of the changed renderer file.
  • One whole-suite run with the wrapper #428 used (fresh HOME, short TMPDIR, run_all.py --jobs 6).

This is correctness evidence, not a timing comparison.
How to reproduce: python -I -B tests/test_quit_not_crash.py --cxx clang++, then the same with --source set to a checkout
of main or of #428. Run it from this branch's checkout, so that the test is this PR's. Then python -I -B tests/run_all.py.

Each check runs one step as a series of starts in a new folder, each start ending as told, then one start that gets
through. That last start shows whether the step was still tried. Every quit comes while the step runs, before the start
hands over to the game.

main @ 5e072fc8 #428 @ 4dd978d6 this PR @ 6219fbbe
tests/test_quit_not_crash.py exit 1: 78 of 101 behaviour checks wrong, 10 source checks fail exit 1: 36 of 101 wrong, 1 source check fails exit 0, all pass
quit, then ended while the close waits for the workers (18 checks over the four steps) 18 wrong 18 wrong 0
quit, then ended in the close's first other part (18 checks) 18 wrong 18 wrong 0
#428's quits: no start-up screen / workers stop in time / a compile outlasts the wait (18 checks each) 18 / 6 / 18 wrong 0 / 0 / 0 0 / 0 / 0
precompile, quit then ended while it closes: twice in a row; then a crash both wrong (the notes dropped at the third start) both wrong both right
two crashes or two kills with no quit (one each for the libraries) let the step go; a start that got through clears a crash (11 checks) all right all right all right
against main 9c9d69ed's sources the same 88 lines as 5e072fc8
integrated 1c53cff1 (with #428) / with this commit 1c53cff1: exit 1, the same 37 lines as #428 exit 0, all pass
vk_main.cpp objects, arm64 and x86_64 exit 0
vk_main.cpp and vk_draw.cpp, with a receipt-only header declaring posix_fallocate (missing on macOS, as for #428) exit 0, arm64 and x86_64
whole game-free suite 525 passed, 19 failed, 17 skipped 526 passed, 19 failed, 17 skipped 526 passed, 19 failed, 17 skipped: the same 19 failures and 17 skips as main

Both runs on each tree printed the same output, byte for byte (the test is deterministic). From main's run (7 of its 88
lines):

- precompile, starts ended by quit, quit then one through: two quits counted as crashes (got to the step's work: [True, True, False], wanted [True, True, True])
- precompile, starts ended by quit, crash then one through: a quit then a crash: wanted one crash counted (got to the step's work: [True, True, False], wanted [True, True, True])
- precompile, starts ended by quit-killed-waiting, quit-killed-waiting then one through: two quits counted as crashes (got to the step's work: [True, True, False], wanted [True, True, True])
- precompile, starts ended by quit-killed-waiting, crash then one through: a quit then a crash: wanted one crash counted (got to the step's work: [True, True, False], wanted [True, True, True])
- precompile, starts ended by crash, quit-killed-waiting, crash then one through: a quit cleared the crash before it (got to the step's work: [True, True, False, True], wanted [True, True, True, False])
- precompile: a quit (quit-killed-waiting) in a new folder left ['longshot_pipelines.bin.running']
- mkwvk_close_asked does not put the steps' files back (crash_markers::close)

From #428's run, the same quit-killed-waiting lines for the precompile, the other three steps and quit-killed-early, and:

- mkwvk_close_asked does programs_census_log before it puts the steps' files back (crash_markers::close): a player who ends the game while that takes time is counted as a crash

and on this PR:

PASS: a quit in a step leaves its file as the start found it, also when its process is ended while it closes; crashes and kills with no quit still let the step go

The suite's summary lines, main 5e072fc8 first, then #428's and this PR's:

525 passed, 19 failed, 17 skipped in 177 s (linux, 6 at once)
526 passed, 19 failed, 17 skipped in 184 s (linux, 6 at once)
526 passed, 19 failed, 17 skipped in 171 s (linux, 6 at once)

The runner calls this Mac linux; these are macOS results.

Testing

Tested:

  • The test on this PR's head, and from this head against the sources of main (5e072fc8 and 9c9d69ed), #428, and the macOS
    port's integrated branch with and without this commit. The marker code and the close it runs are cut from vk_draw.cpp
    and vk_main.cpp as they are.
  • On the integrated branch the close has a call main lacks (draw_layouts_report); the test took it as a stand-in without
    change. Its precompile hands off to the game while giant compiles go on, and a quit before that hand-off is covered there.
  • The whole suite on this head. Its 19 failures are the same 19 by name as on main 5e072fc8 and on #428, and so are its
    17 skips: this PR adds no failure and removes none. The 19 are test_appimage_package_retry, test_collect_wide,
    test_control, 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 and test_program_cache_one_variant (not triaged here).
  • The seven checks that read the close or the steps' markers pass on this head: test_quit_not_crash,
    test_predict_resume, test_prepare_stall, test_prepare_cache_save, test_fsr4_tune, test_startup_progress and
    test_vendor_fixes.
  • arm64 and x86_64 objects of the changed renderer file.

Not tested:

  • No game run: only a real start with a GPU, and a window that stops answering, goes through the close this changes.
  • Nothing ran on Windows or Linux, and no Windows (llvm-mingw) or Linux compile was made. The change moves one existing call
    and changes comments.
  • The kill in the test is _Exit from another thread while the close waits. It stands in for Force Quit or End task: an end
    from outside after which nothing more of the process runs. No real Force Quit was used.

Needs other testers:

  • Windows, any GPU, after a fresh preparation: during "noted pipelines", close the window. When it shows "(Not Responding)",
    close it again ("Close the program"). Do this at two starts in a row. Expect, at the third start, no "its notes are
    dropped" line in the log, and longshot_pipelines.bin kept (request to open).
  • Linux, any GPU: the same with the desktop's Force Quit (request to open).
  • A driver that crashes in the precompile, if one is known: two crashed starts must still drop the notes (as for #428;
    request to open).

Screenshots

Nothing on screen.

Risk and rollback

  • A worker that crashes the driver in the seconds after a quit no longer counts. #428 counted it.
    • One that does so at every start also crashes the next start, which has no quit, and that start counts. The step is still
      let go after two crashes, with at most one extra start.
    • A crash and a kill end the process the same way, so only the quit can draw the line.
  • From the quit on, nothing can change the files: begin() and end() do nothing after close().
    • A step that finishes during the close's wait therefore leaves its file as its start found it.
    • If that start had found a crash counted, the next start tries the step once more instead of finding it cleared. Under
      #428 the same happened when the step finished after its close().
  • Reverting this commit restores #428's order. There is no switch.

Reviewer notes

  • Start with mkwvk_close_asked (one call moved), then close()'s doc in crash_markers.h, then the test's new ends of a
    start and its close.
  • Why the files are put back first, not after the stop as in #428: see Risk.
  • Overlaps (git merge-tree): clean with main 9c9d69ed and onto the integrated branch.
    • Among the open pull requests that touch vk_main.cpp, crash_markers.h or the test, this commit adds no conflict to
      #428's.
    • #426 and #475, newer than #428's own overlap search, conflict with #428 in vk_draw.cpp, which this commit does not
      touch.

Issues

See #420

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main. — On #428's head (main 5e072fc8); it merges with main 9c9d69ed without conflicts. It will be rebased with #428, in one sweep before ready.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing. — Named under Testing; no game run yet, so this stays WIP.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). — Filled in; the test requests are still to open.
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. — A fix, so the measurement is the test: run against main, #428 and this PR, interleaved twice each, on the same Mac, with the same result each time (it is deterministic); no frame time applies.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. — Not built on Windows or Linux: macOS arm64 and x86_64 objects only. One existing call moved; no new code.
**Depends on #428.** This branch is #428's commit (4dd978d6) plus one commit. Review it after #428, or fold it into #428. ## Why Quitting during the start's precompile and then quitting again still counted as a crash. That is the usual reaction when the game stops answering after the first quit. Two such starts dropped the precompile's notes. With #428, a quit during the start's pipeline steps does not count as a crash. Those steps are the precompile, the create-mode warm, the predicted pipelines and the stage libraries. But #428 puts each step's file back only at the end of the close. Before that, the close waits up to 10 s for the preparation's workers to stop, on the window's thread. Meanwhile the window does not answer: - Windows marks it "Not Responding" after 5 s and lets the player close it. - The desktops on Linux and macOS offer Force Quit. A second quit there ends the process before the files are put back, so the start counted as one that crashed in the step. After two such starts the notes were dropped and the warm was skipped. After one, the stage libraries were left out. **What this is for:** a quit by the player or the control socket never counts as a crash, however the process ends after it. A crash, or a kill with no quit asked for, still counts. **What is measured:** `tests/test_quit_not_crash.py`, extended here. It now also has starts ended by a kill and quits whose process is then killed while it closes. It fails 78 of its 101 behaviour checks on `main` and 36 on #428, and passes here. **Not measured:** no game run, and nothing on Windows or Linux (see Testing). ## What changed **The problem** (lines at #428's head 4dd978d6, and at `main` 9c9d69ed for `host/` and the worker): - `mkwvk_close_asked` (`gpu/vk/vk_main.cpp:1567-1580`) runs in this order: - `programs_census_log`, `module_ids_flush`; - `fsr4_close` and `pipeline_cache_close`, each of which can wait up to 10 s in play; - while the shaders are prepared, `prepare_stop()` and `WaitForSingleObject(prep_ended, 10000)` (`1573-1577`); - and only then `crash_markers::close()` (`1578`). - The workers it waits for cannot cut a driver compile short, and the source names driver compiles of 15 s and 41 s (`vk_draw.cpp:8728`, `8742` at 5e072fc8). Once stopped, the worker also saves the pipeline cache before it signals that it ended (`vk_main.cpp:1191` at `main`). So a compile under way uses up the 10 s. - The close runs on the thread that answers the window: - Windows: `WM_CLOSE` in the window procedure (`host/display.c:489-493`). During the start-up screen, that thread is the renderer's start (`vk_main.cpp:1129-1132` at `main`). - Linux and macOS: the SDL event loop on the main thread (`host/posix/display_sdl.c:348-351`, `426-434`). - "The operating system defines an application hang as a UI thread that has not processed messages for at least 5 seconds", and its ghost window lets the user "close the unresponsive application" ([Microsoft](https://learn.microsoft.com/en-us/windows/win32/win7appqual/preventing-hangs-in-windows-applications)). - So the second quit ends the process before line 1578, and the step's file is left with the start's count plus one. **The change:** - `gpu/vk/vk_main.cpp`: `crash_markers::close()` is now the first statement of `mkwvk_close_asked`, before anything that takes time. The rest of the close is unchanged and in the same order. The comment above the function says so. - `gpu/vk/crash_markers.h`: comments only. The header's rule is now "only a crash, or a kill that no quit asked for, may count". `close()`'s doc says it is called first, and that a worker that crashes the driver after the quit is no longer counted. - `tests/test_quit_not_crash.py`: - New ends of a start: - `kill`: ended from outside while the step works, with no quit asked for. - `quit-killed-early`: a quit whose process is then ended in the close's first part other than the files. - `quit-killed-waiting`: the same, ended while the close waits for the workers. - For every quit, a new sequence: a quit then a crash is one crash. - The close is now taken whole from `vk_main.cpp`, in its order. The preparation's stop and the `crash_markers` lines are kept as they are. Every other statement becomes a stand-in where the process can be ended. - #428's source check wanted `crash_markers::close()` after the wait. It now wants it as the close's first statement (the reason is under Risk). Unchanged: the four steps, their files and thresholds, `host/`, and every exit path. ## Flags and settings None. No switch, menu row, ini key or default changes. ## Measurements main: 5e072fc8 (#428's base, 2026-11-14), and 9c9d69ed (2026-11-14) for the test against its sources #428: fix/quit-is-not-a-crash @ 4dd978d6 this PR: fix/double-quit-precompile @ 6219fbbe Hardware: Apple M4 Max (16 cores), 64 GiB, macOS 26.7.1 (25G241), Apple clang 21.0.0, Python 3.14.10. No GPU or driver is involved: the checks are game-free. Scene: none (game-free checks only) Method: - `tests/test_quit_not_crash.py` from this branch, run on its head and against the other trees' sources (`--source`): main, #428, main, #428, this PR, then the same again. - One run each against main 9c9d69ed's sources, the macOS port's integrated branch (1c53cff1, which carries #428), and that branch with this commit applied. - Object compiles of the changed renderer file. - One whole-suite run with the wrapper #428 used (fresh HOME, short TMPDIR, `run_all.py --jobs 6`). This is correctness evidence, not a timing comparison. How to reproduce: `python -I -B tests/test_quit_not_crash.py --cxx clang++`, then the same with `--source` set to a checkout of `main` or of #428. Run it from this branch's checkout, so that the test is this PR's. Then `python -I -B tests/run_all.py`. Each check runs one step as a series of starts in a new folder, each start ending as told, then one start that gets through. That last start shows whether the step was still tried. Every quit comes while the step runs, before the start hands over to the game. | | main @ 5e072fc8 | #428 @ 4dd978d6 | this PR @ 6219fbbe | |---|---|---|---| | `tests/test_quit_not_crash.py` | exit 1: 78 of 101 behaviour checks wrong, 10 source checks fail | exit 1: 36 of 101 wrong, 1 source check fails | exit 0, all pass | | quit, then ended while the close waits for the workers (18 checks over the four steps) | 18 wrong | 18 wrong | 0 | | quit, then ended in the close's first other part (18 checks) | 18 wrong | 18 wrong | 0 | | #428's quits: no start-up screen / workers stop in time / a compile outlasts the wait (18 checks each) | 18 / 6 / 18 wrong | 0 / 0 / 0 | 0 / 0 / 0 | | precompile, quit then ended while it closes: twice in a row; then a crash | both wrong (the notes dropped at the third start) | both wrong | both right | | two crashes or two kills with no quit (one each for the libraries) let the step go; a start that got through clears a crash (11 checks) | all right | all right | all right | | against main 9c9d69ed's sources | the same 88 lines as 5e072fc8 | | | | integrated 1c53cff1 (with #428) / with this commit | | 1c53cff1: exit 1, the same 37 lines as #428 | exit 0, all pass | | `vk_main.cpp` objects, arm64 and x86_64 | | | exit 0 | | `vk_main.cpp` and `vk_draw.cpp`, with a receipt-only header declaring `posix_fallocate` (missing on macOS, as for #428) | | | exit 0, arm64 and x86_64 | | whole game-free suite | 525 passed, 19 failed, 17 skipped | 526 passed, 19 failed, 17 skipped | 526 passed, 19 failed, 17 skipped: the same 19 failures and 17 skips as `main` | Both runs on each tree printed the same output, byte for byte (the test is deterministic). From `main`'s run (7 of its 88 lines): ``` - precompile, starts ended by quit, quit then one through: two quits counted as crashes (got to the step's work: [True, True, False], wanted [True, True, True]) - precompile, starts ended by quit, crash then one through: a quit then a crash: wanted one crash counted (got to the step's work: [True, True, False], wanted [True, True, True]) - precompile, starts ended by quit-killed-waiting, quit-killed-waiting then one through: two quits counted as crashes (got to the step's work: [True, True, False], wanted [True, True, True]) - precompile, starts ended by quit-killed-waiting, crash then one through: a quit then a crash: wanted one crash counted (got to the step's work: [True, True, False], wanted [True, True, True]) - precompile, starts ended by crash, quit-killed-waiting, crash then one through: a quit cleared the crash before it (got to the step's work: [True, True, False, True], wanted [True, True, True, False]) - precompile: a quit (quit-killed-waiting) in a new folder left ['longshot_pipelines.bin.running'] - mkwvk_close_asked does not put the steps' files back (crash_markers::close) ``` From #428's run, the same `quit-killed-waiting` lines for the precompile, the other three steps and `quit-killed-early`, and: ``` - mkwvk_close_asked does programs_census_log before it puts the steps' files back (crash_markers::close): a player who ends the game while that takes time is counted as a crash ``` and on this PR: ``` PASS: a quit in a step leaves its file as the start found it, also when its process is ended while it closes; crashes and kills with no quit still let the step go ``` The suite's summary lines, `main` 5e072fc8 first, then #428's and this PR's: ``` 525 passed, 19 failed, 17 skipped in 177 s (linux, 6 at once) 526 passed, 19 failed, 17 skipped in 184 s (linux, 6 at once) 526 passed, 19 failed, 17 skipped in 171 s (linux, 6 at once) ``` The runner calls this Mac `linux`; these are macOS results. ## Testing **Tested:** - The test on this PR's head, and from this head against the sources of `main` (5e072fc8 and 9c9d69ed), #428, and the macOS port's integrated branch with and without this commit. The marker code and the close it runs are cut from `vk_draw.cpp` and `vk_main.cpp` as they are. - On the integrated branch the close has a call `main` lacks (`draw_layouts_report`); the test took it as a stand-in without change. Its precompile hands off to the game while giant compiles go on, and a quit before that hand-off is covered there. - The whole suite on this head. Its 19 failures are the same 19 by name as on `main` 5e072fc8 and on #428, and so are its 17 skips: this PR adds no failure and removes none. The 19 are `test_appimage_package_retry`, `test_collect_wide`, `test_control`, `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` and `test_program_cache_one_variant` (not triaged here). - The seven checks that read the close or the steps' markers pass on this head: `test_quit_not_crash`, `test_predict_resume`, `test_prepare_stall`, `test_prepare_cache_save`, `test_fsr4_tune`, `test_startup_progress` and `test_vendor_fixes`. - arm64 and x86_64 objects of the changed renderer file. **Not tested:** - No game run: only a real start with a GPU, and a window that stops answering, goes through the close this changes. - Nothing ran on Windows or Linux, and no Windows (llvm-mingw) or Linux compile was made. The change moves one existing call and changes comments. - The kill in the test is `_Exit` from another thread while the close waits. It stands in for Force Quit or End task: an end from outside after which nothing more of the process runs. No real Force Quit was used. **Needs other testers:** - Windows, any GPU, after a fresh preparation: during "noted pipelines", close the window. When it shows "(Not Responding)", close it again ("Close the program"). Do this at two starts in a row. Expect, at the third start, no "its notes are dropped" line in the log, and `longshot_pipelines.bin` kept (request to open). - Linux, any GPU: the same with the desktop's Force Quit (request to open). - A driver that crashes in the precompile, if one is known: two crashed starts must still drop the notes (as for #428; request to open). ## Screenshots Nothing on screen. ## Risk and rollback - A worker that crashes the driver in the seconds after a quit no longer counts. #428 counted it. - One that does so at every start also crashes the next start, which has no quit, and that start counts. The step is still let go after two crashes, with at most one extra start. - A crash and a kill end the process the same way, so only the quit can draw the line. - From the quit on, nothing can change the files: `begin()` and `end()` do nothing after `close()`. - A step that finishes during the close's wait therefore leaves its file as its start found it. - If that start had found a crash counted, the next start tries the step once more instead of finding it cleared. Under #428 the same happened when the step finished after its `close()`. - Reverting this commit restores #428's order. There is no switch. ## Reviewer notes - Start with `mkwvk_close_asked` (one call moved), then `close()`'s doc in `crash_markers.h`, then the test's new ends of a start and its close. - Why the files are put back first, not after the stop as in #428: see Risk. - Overlaps (`git merge-tree`): clean with `main` 9c9d69ed and onto the integrated branch. - Among the open pull requests that touch `vk_main.cpp`, `crash_markers.h` or the test, this commit adds no conflict to #428's. - #426 and #475, newer than #428's own overlap search, conflict with #428 in `vk_draw.cpp`, which this commit does not touch. ## Issues See #420 ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [ ] Rebased on current `main`. — On #428's head (main 5e072fc8); it merges with `main` 9c9d69ed without conflicts. It will be rebased with #428, in one sweep before ready. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [ ] Tested in the game, or what wasn't is named under Testing. — Named under Testing; no game run yet, so this stays WIP. - [ ] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). — Filled in; the test requests are still to open. - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. — A fix, so the measurement is the test: run against `main`, #428 and this PR, interleaved twice each, on the same Mac, with the same result each time (it is deterministic); no frame time applies. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. — Not built on Windows or Linux: macOS arm64 and x86_64 objects only. One existing call moved; no new code.
fix(vk): a quit during the start's pipeline steps no longer counts as a crash
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Third-party parts / As their notes say (pull_request) Successful in 6s
Website / Website on Cloudflare (pull_request_target) Has been skipped
4dd978d68c
Four steps before the title leave a file that says they began and did not end, so that a driver
whose compiler crashes on one pipeline does not crash every start: the stage libraries
(longshot_prepared.bin.warming, left out after one), the predicted pipelines (.predicting), the
warm into the pipeline cache (.built.warming) and the precompile (.running), each let go after
two. Closing the window, the Insert menu's Quit and Restart now and the control socket's quit end
the process with ExitProcess (on POSIX fflush and _exit), which runs no destructor and joins no
thread, so the file stayed as if the start had crashed. Two quits in the precompile dropped its
notes; with the start-up screen this happened whenever a compile outlasted the close's 10 s wait,
and in the create-mode warm on every close.

gpu/vk/crash_markers.h keeps the steps that are open. mkwvk_close_asked, which every close path
calls first, puts each one's file back as its start found it once the preparation has stopped:
a close neither counts as a crash nor clears one. A crash or a kill still counts as before.

tests/test_quit_not_crash.py runs the marker code cut from vk_draw.cpp and the close from
vk_main.cpp as starts that end through, in a crash or in three kinds of quit. The checks that cut
predict_run and prepare_run out of the source (test_predict_resume, test_prepare_stall,
test_prepare_cache_save) read the markers through crash_markers.h now.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
fix(vk): a game ended while it closes, after a quit during the precompile, no longer counts as a crash
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Third-party parts / As their notes say (pull_request) Successful in 9s
Website / Website on Cloudflare (pull_request_target) Has been skipped
6219fbbed3
The close (mkwvk_close_asked) saved the caches and waited up to 10 s for the preparation's workers before it put the steps'
crash files back (crash_markers::close), with the window not answering meanwhile. A player who quits again then ends the
process (Force Quit, End task; the system at a log-out does the same), and that start was counted as one that crashed in
the precompile, the create-mode warm, the predicted pipelines or the stage libraries: two such starts dropped the
precompile's notes. The files are now put back first, before anything that takes time. A crash, or a kill with no quit
asked for, still counts; a worker that crashes the driver after the quit no longer does (one that does so at every start
crashes the next start too).

tests/test_quit_not_crash.py: starts that are killed with no quit, and quits whose process is ended in the close's first
other part or while it waits for the workers; a quit then a crash is one crash. The source check now wants the files put
back as the close's first statement. The close is taken in its order, every other part a stand-in that can take time.

Depends on #428.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Third-party parts / As their notes say (pull_request) Successful in 9s
Website / Website on Cloudflare (pull_request_target) Has been skipped
This pull request is marked as a work in progress.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'fix/double-quit-precompile:mooki-fix/double-quit-precompile'
git switch 'mooki-fix/double-quit-precompile'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!498
No description provided.