diff --git a/runtime/dispatch.c b/runtime/dispatch.c index 608ee482..13001ac6 100644 --- a/runtime/dispatch.c +++ b/runtime/dispatch.c @@ -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); diff --git a/runtime/dispatch.h b/runtime/dispatch.h index 5ecb436e..255ab386 100644 --- a/runtime/dispatch.h +++ b/runtime/dispatch.h @@ -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); diff --git a/runtime/main.c b/runtime/main.c index 54d81e7a..ed9035e6 100644 --- a/runtime/main.c +++ b/runtime/main.c @@ -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 " diff --git a/tests/fatal_exit_check.c b/tests/fatal_exit_check.c index e9840d9e..7b8639da 100644 --- a/tests/fatal_exit_check.c +++ b/tests/fatal_exit_check.c @@ -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()); diff --git a/tests/fatal_exit_check.py b/tests/fatal_exit_check.py index c75e7e6c..3b97f8bc 100644 --- a/tests/fatal_exit_check.py +++ b/tests/fatal_exit_check.py @@ -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