mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
End a hung fatal error's report with its own code when a thread waiting beside it gives up, not with the crash code.
The review of the shared failure guard found that a thread parked in dispatch.c rt_ending_enter ended the process with 71 after 30 s even when the first report was a FATAL (70, or 72 for a lost device), so a fatal error whose report hung read as a crash to the launcher. rt_ending_enter now takes the code its caller ends with: fatal_end passes its own, main.c crash_handler 71. The first caller stores it next to ending_tid, and a waiting thread that gives up after 30 s ends the process with it. Before the store the value is 71, as before. Reproduced before the change with a one-off build of tests/fatal_exit_check.c given a fatal note that never returns and a fault on another thread: the process ended with 71 after 30.5 s; with this change it ends with 70 after 30.5 s. That case takes 30 s, so the matrix gets a static check instead: tests/fatal_exit_check.py holds rt_ending_enter to storing the first caller's code and ending with it, fatal_end to passing its code, and crash_handler to its first line `if (!rt_ending_enter(71)) plat_exit(71);`. fatal_exit_check and the compile check of every runtime file (a header changed) pass, with no new -Wall warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -88,21 +88,26 @@ void rt_rumble_stop_bounded(void) {
|
||||
// where it was, together with its wait for a save Commit and RivaTuner's restore, and its own FATAL line and exit
|
||||
// code replaced the first (the launcher shows the last FATAL). Another thread now waits until the first end is done;
|
||||
// it logs nothing, since it may hold the log queue's lock, which the first report only tries. A first report that
|
||||
// hangs still ends: after 30 s the waiting thread ends the process with the crash report's code.
|
||||
// hangs still ends: after 30 s the waiting thread ends the process with the first report's own code (a fatal error's
|
||||
// 70 or 72, the crash report's 71), so the launcher reads the end as the report says it.
|
||||
static volatile uint32_t ending_tid; // the thread that reports, 0 before one does
|
||||
static volatile int32_t ending_code = 71; // the code that report ends with, stored right after it took ending_tid
|
||||
|
||||
int rt_ending_enter(void) {
|
||||
int rt_ending_enter(int code) {
|
||||
uint32_t me = plat_thread_id(), was = 0;
|
||||
if (__atomic_compare_exchange_n(&ending_tid, &was, me, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) return 1;
|
||||
if (__atomic_compare_exchange_n(&ending_tid, &was, me, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
|
||||
__atomic_store_n(&ending_code, code, __ATOMIC_RELEASE);
|
||||
return 1;
|
||||
}
|
||||
if (was == me) return 0;
|
||||
for (int i = 0; i < 3000; i++) plat_sleep_ms(10);
|
||||
plat_terminate(71);
|
||||
plat_terminate(__atomic_load_n(&ending_code, __ATOMIC_ACQUIRE));
|
||||
}
|
||||
|
||||
// The fatal line, the report's notes and what has to be flushed, then the exit with `code`.
|
||||
static void fatal_end(int code, const char *fmt, va_list ap) __attribute__((noreturn));
|
||||
static void fatal_end(int code, const char *fmt, va_list ap) {
|
||||
if (!rt_ending_enter()) plat_terminate(code); // a fatal error inside this thread's own report: no second one
|
||||
if (!rt_ending_enter(code)) plat_terminate(code); // a fatal error inside this thread's own report: no second one
|
||||
rt_log_ending(); // the report reaches the log file past its cap (main.c log_sink)
|
||||
rt_log_sync(); // main.c: the lines queued for the log thread, then these, written here
|
||||
rt_log_raw("FATAL: ", 7);
|
||||
|
||||
@@ -62,9 +62,10 @@ void rt_fatal_code(int code, const char *fmt, ...) __attribute__((noreturn, form
|
||||
// needs none. At most four; adding one twice keeps one. rt_fatal_notes runs them, once per process.
|
||||
void rt_fatal_note_add(void (*fn)(void));
|
||||
void rt_fatal_notes(void);
|
||||
// The one end of a failed run: the fatal error (rt_fatal) and the crash report (main.c crash_handler) call it first. 1
|
||||
// for the first caller, which reports and ends the process; 0 for that thread again (a failure inside its own report),
|
||||
// which ends at once with its code; any other thread waits, logging nothing, until the process ends (30 s at most).
|
||||
int rt_ending_enter(void);
|
||||
// The one end of a failed run: the fatal error (rt_fatal) and the crash report (main.c crash_handler) call it first,
|
||||
// with the code they end the process with. 1 for the first caller, which reports and ends the process; 0 for that
|
||||
// thread again (a failure inside its own report), which ends at once with its code; any other thread waits, logging
|
||||
// nothing, until the process ends, and after 30 s ends it with the first caller's code.
|
||||
int rt_ending_enter(int code);
|
||||
// HD rumble stopped before a failed run ends, on a thread of its own that the caller waits for 300 ms at most.
|
||||
void rt_rumble_stop_bounded(void);
|
||||
|
||||
@@ -373,7 +373,7 @@ static int mode_rc;
|
||||
static void crash_handler(const PlatCrash *x) {
|
||||
// shared with the fatal error's end (dispatch.c rt_ending_enter): another thread's fault or fatal error waits there
|
||||
// while this report is written; a fault inside this thread's own report ends the process at once
|
||||
if (!rt_ending_enter()) plat_exit(71);
|
||||
if (!rt_ending_enter(71)) plat_exit(71);
|
||||
rt_log_ending(); // the report reaches the log file past its cap (log_sink)
|
||||
if (__atomic_load_n(&mode_done, __ATOMIC_ACQUIRE) && x->module) {
|
||||
rt_log("exit: %s (%#x) in %s while the process was ending, after its work was done and saved%s: the run ends "
|
||||
|
||||
@@ -74,7 +74,7 @@ void input_rumble_hd_stop(void) {
|
||||
|
||||
// ---- the crash report and the threads that fail beside it
|
||||
static void crash(const PlatCrash *x) {
|
||||
if (!rt_ending_enter()) plat_exit(71); // main.c crash_handler's first line
|
||||
if (!rt_ending_enter(71)) plat_exit(71); // main.c crash_handler's first line
|
||||
char b[160];
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
snprintf(b, sizeof b, "report line %d: %s on thread %lu\n", i, x->what, (unsigned long)GetCurrentThreadId());
|
||||
|
||||
@@ -75,7 +75,7 @@ def static_checks():
|
||||
main_c = (RT / 'main.c').read_text(encoding='utf-8')
|
||||
body = re.sub(r'//[^\n]*', '', function(main_c, 'static void crash_handler(const PlatCrash *x)'))
|
||||
first = body[body.index('{') + 1:].strip().split('\n')[0].strip()
|
||||
ok = first == 'if (!rt_ending_enter()) plat_exit(71);'
|
||||
ok = first == 'if (!rt_ending_enter(71)) plat_exit(71);'
|
||||
print(f'static: main.c crash_handler starts with the shared guard: {"ok" if ok else "FAIL (" + first + ")"}',
|
||||
flush=True)
|
||||
bad += not ok
|
||||
@@ -95,6 +95,15 @@ 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
|
||||
# a first report that hangs: the thread that waits for it ends the process after 30 s with that report's code, and
|
||||
# fatal_end hands its own code to the guard (a hung FATAL must not read as a crash to the launcher)
|
||||
dispatch = re.sub(r'//[^\n]*', '', (RT / 'dispatch.c').read_text(encoding='utf-8'))
|
||||
enter = function(dispatch, 'int rt_ending_enter(int code)')
|
||||
ok = ('__atomic_store_n(&ending_code, code' in enter
|
||||
and 'plat_terminate(__atomic_load_n(&ending_code' in enter and 'rt_ending_enter(code)' in fatal)
|
||||
print(f'static: dispatch.c rt_ending_enter ends a hung first report with that report\'s code: '
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user