Write a fatal error's frame-time summary after RivaTuner's restore and the rumble stop, not before them.

The review of the fatal summary change found that dispatch.c fatal_end called perf_summary_flush_try (ft_stop waits up
to 50 ms, then the summary and the stutter map are formatted) before rtss_limit_restore and rt_rumble_stop_bounded.
The lane's order for the failed ends, and A-6's for the orderly ones (gfx_present.c rt_exit_steps), puts what a kill
of the process must not lose first: RTSS's profile and the controller's rumble.

perf_summary_flush_try now runs after the restore and the bounded rumble stop, just before the stdio flushes and
plat_terminate. The summary's lines still reach the log: rt_log_sync ran at the start of fatal_end, so every later
line is written as it is made.

Not reproduced before the change (an order of steps). tests/fatal_exit_check.py checks statically that fatal_end
calls perf_summary_flush_try after rt_rumble_stop_bounded; the check fails on the code before this change and passes
after it, and the program's cases (fatal, fatal_rumble, crash_crash, fatal_crash, crash_fatal) pass. The compile
check of dispatch.c passes with no -Wall warnings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 18:27:13 -05:00
parent 313ef09081
commit 78e9d23211
2 changed files with 9 additions and 1 deletions

View File

@@ -116,12 +116,14 @@ static void fatal_end(int code, const char *fmt, va_list ap) {
rt_fatal_notes();
kern_free_cores_note();
fs_save_crash_wait(); // a save the game already reported as done finishes first (bounded)
perf_summary_flush_try(); // "frametime: at a fatal error" and the stutter map: the session's last period
prof_sampler_write(); // NX2NAVI_PROFILE: what was sampled until now, else a crash loses the whole profile
// RivaTuner Statistics Server's profile as it was: left with the game's limit in it, the next run would take that
// limit for the player's own (rtss_limit.c; it gives up after a second if the failing thread holds its lock)
rtss_limit_restore();
rt_rumble_stop_bounded(); // a controller on HD rumble: nothing renews or stops its vibration after the end
// "frametime: at a fatal error" and the stutter map, the session's last period: after what a kill of the process
// must not lose, as in the orderly end (gfx_present.c rt_exit_steps); ft_stop waits up to 50 ms
perf_summary_flush_try();
fflush(stdout);
fflush(stderr);
// Not _Exit: in the C runtime it is ExitProcess, which stops the other threads wherever they are and then runs every

View File

@@ -94,6 +94,12 @@ def static_checks():
print(f'static: {name} stops HD rumble through rt_rumble_stop_bounded after the RTSS restore: '
f'{"ok" if ok else "FAIL"}', flush=True)
bad += not ok
# the fatal error's frame-time summary comes after the RTSS restore and the rumble stop, as in the orderly end
s, p = fatal.find('rt_rumble_stop_bounded('), fatal.find('perf_summary_flush_try(')
ok = 0 <= s < p
print(f'static: dispatch.c fatal_end writes the frame-time summary after the RTSS restore and the rumble stop: '
f'{"ok" if ok else "FAIL"}', flush=True)
bad += not ok
return bad