test(vk): make recorder destruction checks independent of scheduling #101
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!101
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:test/recorder-sync-observer"
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
The recorder regression can report
NOT CAUGHTfor a deliberately missing destruction wait when the recorder finishes before the observer sees the bad order. This repairs the test, not the production recorder; Windows execution still needs a repeated run.What changed
tests/recorder_sync_test.cpp,destroy_recorded: drain the preceding frame, restart the empty ring at a packet boundary, and keep the recorder awake with its existing thread-start hook until the next publication. The final copy remains unpublished until an ordered boundary flushes it.tests/test_recorder_sync.pyand the existing development guide. All broken variants remain. No production source changed.Flags and settings
None.
Measurements
Branch state: on main
5eaaf7fb9c548782a3dfabcf89ad37363ee76b1e; headea5c10c2eb1266fc3ca1864979e410a3b484ef5c; whole game-free suite on this head:510 passed, 0 failed, 19 skipped(main5eaaf7fb9c548782a3dfabcf89ad37363ee76b1e:510 passed, 0 failed, 19 skipped); the commits are unchanged by rebases apart from the results table.main:
f41db065cf(2026-11-12)this PR: move/test/recorder-sync-observer @ 755c6ac3ffd0b5c394d349a3005a66d6791d0400
Hardware: Intel Core i9-13900K; Arch Linux, kernel 7.2.15-1-omarchy; clang 23.1.3. No GPU or Vulkan device used.
Scene: the fake Vulkan recorder fixture, not a game scene.
Method: repeated destruction-negative executions; deliberate CPU contention; temporary producer delays; and two interleaved batches per version of all eight positive environment cases, after one warm-up each.
How to reproduce:
python -I -B tests/test_recorder_sync.py --cxx clang++ --headers path/to/Vulkan-Headers/include.The escaping schedule
The original
destroy_recordedqueues a frame and a final copy naming a buffer, then destroys that buffer. The fake destruction and copy share the log mutex. If the producer is descheduled long enough after packing the copy, the recorder reaches the fake first. Both the resource check and call-order comparison then see the valid order, although the destruction boundary is broken. This scenario had nohold()call; even a fixed-duration hold would not guarantee the producer runs before it expires.To force that schedule, a temporary fixture copy inserted
std::this_thread::sleep_for(std::chrono::milliseconds(10));on the producer immediately before the finalcopy_runs(kUp, 0)and immediately beforevkDestroyBuffer, only in THREAD mode. The first pause lets the recorder sleep, causing the next packet to publish immediately; the second lets that packet finish. These two sleeps are not in this PR. The same pauses were applied to both versions.The isolated runs select the existing destruction scenario and its unchanged
check_runobserver. The complete--only threadchecks were also run with the forced schedule, including both million-packet stress runs.f41db065cEvery repaired negative failure above was the intended destroyed-buffer violation and copy/destruction mismatch, not a timeout, setup failure or crash. The repaired positive fixture still passed 100/100 isolated runs with the same pauses.
Original full thread-check escape:
With the repaired observer and the same broken recorder and producer pauses:
Test duration
All eight positive environment cases passed in each batch, in main / PR / main / PR order. These are fixture execution times, excluding compilation, on the same shared PC; no game speed claim is made.
f41db065cNo material increase was observed. The small difference is not claimed as a speedup; the machine was shared. The repair adds no timer wait.
Testing
Tested: On Linux, the complete
tests/test_recorder_sync.pypassed: all eight positive environment cases and all 23 applicable broken variants. The full named test took 83.072 seconds, including compilation. The repeated before/after checks and 32 interleaved positive executions are above. The final fixture compiled to x86-64 Windows COFF objects with both real and destruction-negative recorder headers, using clang's Windows target, llvm-mingw headers and libc++, C++20, and-Wall -Wextra -Werror. That was compilation only, without Windows execution.The complete game-free suite on the exact head also passed:
python -I -B tests/run_all.py— 509 passed, 0 failed, 19 skipped. No failure was retried.Not tested: Windows execution of the changed fixture and its existing Windows-only process-exit/DLL checks: no Windows runtime was available. The cross-compile does not test Microsoft's C++ library. No game or GPU run, because production code is unchanged. The precise scheduling of the historical Windows failure was not captured; Linux reproduces a concrete cause of the same result, not a trace of that Windows run.
Needs other testers:
python -I -B tests/test_recorder_sync.py --headers path/to/Vulkan-Headers/includeat least 200 times, including under CPU load, expecting every positive case to pass and every broken variant, especiallyvkDestroyBuffer made without waiting, to be caught on every run (test request T-3FRA)Screenshots
Nothing on screen.
Risk and rollback
The fixture depends on the recorder's publication counters, batch size and existing start hook. A future packet/batching change may require updating this setup; the explicit unpublished-copy check prevents a silently ineffective negative test. The hook changes only while the recorder is joined. Reverting this commit restores the previous test. No player setting or production behavior changes.
Reviewer notes
Start with
destroy_recordedand the unchanged destruction mutation intest_recorder_sync.py. The test now exercises a pending unpublished copy; it does not claim to make every other timing-dependent negative case deterministic, or to test a different flush-without-wait mutation.The original Windows output is in https://github.com/mooki000/Hookshot/actions/runs/43280308588/job/131399531380 . The separate investigation was promised at https://github.com/NotRexed/Hookshot/pull/2830#issuecomment-6832639364 . Its preceding 30-second timeout concerned the deliberately swallowed exception, not the destruction escape.
The measured base is retained while this remains WIP; it has not been refreshed onto the newer main.
Issues
See #90
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.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]
The test request for a repeated Windows run is open (T-3FRA). Rebased onto main
5eaaf7fb9with the patch unchanged; whole suite on the new headea5c10c2e: 510 passed, 0 failed, 19 skipped. Ready for review.WIP: test(vk): make recorder destruction checks independent of schedulingto test(vk): make recorder destruction checks independent of scheduling755c6ac3ffea5c10c2ebView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.