Stop the replay and exit when the GPU device is lost #1291
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1291
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/exit-on-device-lost"
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
After
VK_ERROR_DEVICE_LOSTthe replay wrote the checkpoint report and keptrunning. Nothing it submitted completed again, so captures and staging were
never retired: in #1275
captures keptwent from 25 to 6527 and staging from256 MB to 1408 MB, until system RAM ran out with a black window.
The four
VK_ERROR_DEVICE_LOSTchecks ingpu/vk/vk_replay.cppnow call a newdevice_lost(), which writes the report once throughstd::call_onceand thenends the process through the existing
replay_fatal(exit code 3). Withcall_once, a second thread that sees the loss at the same time (the #1275 logshows several) waits for the report instead of ending the process before it is
written.
This does not fix the GPU hang itself in #1275, only what happens after it.
Tests
tests/test_device_lost_exit.py(new, game-free): everyVK_ERROR_DEVICE_LOSTcheck must calldevice_lost();device_lost()is cutinto a fixture and called from five threads with a slow report, which must be
written once before the exit. It fails on the old code path and on a
check-once flag without
call_once.tests/test_parallel_init_order.py: its fixture gained adevice_loststub(the segment-submit block it cuts now calls it); passes.
changed function was compiled, in the fixture). Linux, g++.
Question
Do you want the process to stay alive after a device lost in some debugging
setup (e.g. with
RX_VK_FAULT_INFO)? If so, I can put the exit behind anopt-out variable.
Refs #1275
AI assistance
Assisted-by: Claude Opus 5.5
Assisted-by: gpt-oss-120b (LLM council)
Assisted-by: Qwen3.8-27B (LLM council)
Assisted-by: Gemini 3.5 Flash-Lite (LLM council)
Assisted-by: dots-3-note-preview (LLM council)
Assisted-by: Nemotron 3.5 Lightning (LLM council)
Assisted-by: Nemotron 3 Ultra 550B (LLM council)
Assisted-by: Nemotron 3.5 Super VL (LLM council)
Co-Authored-By: Claude Opus 5.5 [email protected]
The LLM council (several models answer the same question independently, then
rank each other's answers anonymously) was used to define the approach and the
solution: which problem to take on, how to fix it and what to avoid. It saw a
summary of the issues, not the code. The change and its test were written with
Claude Code, then reviewed and tested by me.
Nice find, and the call_once ordering is right. I read through the paths around it:
replay_fatalends instd::_Exit(3), so no atexit/static destructors run and nothing takes a lock.checkpoints_report(anddevice_fault_report) take nopresent_muand never wait on a fence/semaphore, so calling it underpresent_mufromqueue_flush_lockedcan't deadlock and can't re-enterdevice_lost(). Worker threads (segment submit) are fine for the same reason.ExitProcess(0)with no settings or pipeline-cache save either, and the cache is saved every 300 presents in the present loop, so you lose at most that.wait_value, the slot wait,vk_timeline_waitand the*okwait still ignore it, and present/acquire only log. They all return immediately on a lost device, and the next submit orwait()hits one of your four checks, so I think this is covered in practice. Not blocking.One thing I'd fix: on Windows with a console,
hos_logis queued and written by a background thread.hos_fatalcallshos_log_flush()before exiting, butreplay_fataldoesn't (its own comment says the queued log goes with the process). That was fine for its one-line use, but now the whole point ofdevice_lost()is the checkpoint report, which is alllogflines, and a_Exitright after can drop them. Thefprintf(stderr)inreplay_fatalwould still show, but the report might not. Suggest giving the Vulkan side a way to flush the host log (an extra callback inMkwVkHost, or the host'shos_log_flush) and calling it inreplay_fatalbefore_Exit. Linux isn't affected (log_queue is a no-op there).Smaller: with
call_once, ifcheckpoints_reportitself hangs (the comment in vk_draw.cpp saysvkGetDeviceFaultInfoEXTdidn't come back on NVIDIA, only with RX_VK_FAULT_INFO), every thread that sees the loss now blocks behind it, so the process stays up with a black window. Previously other threads would at least keep running. Acceptable for a debug-only env var, just be aware.On your question: I'd keep exit-by-default; the RX_VK_FAULT_INFO case is the only one I can see wanting to stay alive, and it already reports first.
Thanks, the flush fix looks right. replay_fatal now calls host->log_flush() (hos_log_flush, which takes the write lock and is safe from any thread) before the _Exit, and device_lost() goes through replay_fatal on every path, worker threads included. The API version bump, the vk_host table entry and the test fixture all match, and the new check in test_device_lost_exit.py covers the flush-before-_Exit ordering.
Still open, and only a nit: std::call_once makes every other thread wait if checkpoints_report hangs (the NVIDIA RX_VK_FAULT_INFO case), so the process would never reach _Exit. A timeout or try-lock around the report would avoid that, but it is fine to leave as is.
No CI checks are reported on the branch.
Thanks for the careful read. Fixed in
fb879db:MkwVkHostgetslog_flush(hos_log_flush, API version 10), andreplay_fatalcalls it before_Exit;test_device_lost_exit.pychecks both. On the report hanging withRX_VK_FAULT_INFO: agreed, I left it, since it only happens with that debug variable. Keeping exit-by-default as you suggest.