mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
A lost device used to end the process from whichever submit or wait noticed it first, while the present only logged it and went on and the other threads kept calling the driver. The completion thread could take the submissions' timeline as reached and run the callbacks of submissions that never finished. vk_device_lost_check now takes the result of every submit, present, acquire and wait. The first thread to see the loss sets a flag, writes the report and ends the process as a fatal error does (the log written, a save commit under way finished), with an exit code of its own; later threads stop in that call, and a watchdog ends the process after ten seconds should the first thread hang. The replay, its submit, the uploads, the present and the lazy fetch read the flag and return without touching the device. The completion thread asks the device with a call of its own before a new value counts and runs no callback once the flag is set. The executable's --probe-devlost and tests/device_lost_check.py check this without a lost device, and device_lost_at in nx2navi.ini ends a real run the same way. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
71 lines
3.0 KiB
Python
71 lines
3.0 KiB
Python
"""Run the game executable's --probe-devlost and check how a run ends when the GPU device is lost
|
|
(runtime/vk_device.c vk_device_lost_check, runtime/vk_sync.c vk_sync_probe_devlost).
|
|
|
|
The probe needs a Vulkan device and opens no window. It does not lose the device: it marks it lost, and then has four
|
|
threads report a loss at the same moment. Checked here:
|
|
|
|
- on the healthy device a completion callback runs (so the next check can fail);
|
|
- with the device marked lost no callback runs, no value counts as done, and the submit, upload and wait paths return
|
|
at once;
|
|
- the process ends with exit code 72 (dispatch.h RT_LOST_EXIT), with one device report and one FATAL line, well
|
|
before the watchdog's 10 seconds.
|
|
|
|
What a driver answers after a real loss is not covered.
|
|
|
|
Usage: device_lost_check.py PATH_TO_GAME_EXE
|
|
"""
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
LOST_EXIT = 72
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) != 2:
|
|
print(__doc__)
|
|
return 2
|
|
p = subprocess.Popen([sys.argv[1], '--probe-devlost'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
text=True, errors='replace')
|
|
lines, reported_at = [], None
|
|
for line in p.stdout:
|
|
lines.append(line.rstrip('\n'))
|
|
if 'four threads report the loss now' in line:
|
|
reported_at = time.monotonic()
|
|
code = p.wait()
|
|
ended = time.monotonic()
|
|
text = '\n'.join(lines)
|
|
if 'probe-devlost: SKIP' in text:
|
|
print('device_lost_check: SKIP (no Vulkan device)')
|
|
return 0
|
|
fails = []
|
|
|
|
def check(ok, what):
|
|
if not ok:
|
|
fails.append(what)
|
|
|
|
check('probe-devlost: healthy device: the callback of value' in text, 'no callback ran on the healthy device')
|
|
marked = [ln for ln in lines if ln.startswith('probe-devlost: device marked lost:')]
|
|
check(len(marked) == 1 and 'not run, done stayed, submit not recorded' in marked[0],
|
|
'a path went on after the device was marked lost: %s' % (marked or ['no line']))
|
|
check('probe-devlost: FAILED' not in text, 'the probe reports a failure')
|
|
check(code == LOST_EXIT, 'exit code %d, expected %d' % (code, LOST_EXIT))
|
|
fatal = [ln for ln in lines if ln.startswith('FATAL: ')]
|
|
check(len(fatal) == 1 and 'the GPU device was lost' in fatal[0], '%d FATAL lines, expected one: %s' % (len(fatal), fatal))
|
|
reports = [ln for ln in lines if 'video memory when the device was lost' in ln]
|
|
check(len(reports) == 1, '%d device reports, expected one' % len(reports))
|
|
took = ended - reported_at if reported_at else -1
|
|
check(0 <= took < 5, 'the process ended %.1f s after the loss was reported (the watchdog, not the reporting thread?)' % took)
|
|
for f in fails:
|
|
print('device_lost_check: FAIL:', f)
|
|
if fails:
|
|
print(text[-3000:])
|
|
return 1
|
|
print('device_lost_check: passed (exit code %d, one report, one FATAL line, %.2f s from the report to the exit)'
|
|
% (code, took))
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|