mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Check that a crash on RivaTuner Statistics Server's own thread, with no line in the state file, returns at once.
The review found that no test ran rtss_limit_crash's thread gate: where the state file holds no line of the run, rtss_limit_crash puts the profile back through rtss_limit_restore, except on the worker's own thread, which may hold the lock that restore tries for a second; only the static check in tests/rtss_limit_check.py looked at it. tests/rtss_worker_check.c crash_worker_thread now makes the state file unwritable and calls rtss_limit_crash from inside SaveProfile on the worker thread, with the lock held: it must return within 200 ms with no call into the fake hooks library (a new count of every call), close the run, and let no later want reach the profile. With the gate removed from a copy of rtss_limit.c, the scenario fails. Checks that passed: tests/rtss_limit_check.py, tools/check_content.py --staged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -64,16 +64,18 @@ static int readonly; // SaveProfile does not reach the file (no administ
|
||||
static DWORD refuse_over; // RTSS refuses a limit above this many frames a second (0: none refused)
|
||||
static int no_library; // LoadLibraryA fails
|
||||
static volatile LONG saves, deletes, updates, loads, created, removed;
|
||||
static volatile LONG calls; // every call into the fake hooks library
|
||||
|
||||
static void touch(Prof *p) { p->stamp++; }
|
||||
static void WINAPI f_load(LPCSTR name) {
|
||||
(void)name;
|
||||
calls++;
|
||||
loaded = own.exists ? own : global;
|
||||
}
|
||||
static void (*during_save)(void); // runs inside the next SaveProfile, once
|
||||
static void WINAPI f_save(LPCSTR name) {
|
||||
(void)name;
|
||||
saves++;
|
||||
calls++, saves++;
|
||||
if (during_save) {
|
||||
void (*f)(void) = during_save;
|
||||
during_save = NULL;
|
||||
@@ -86,16 +88,18 @@ static void WINAPI f_save(LPCSTR name) {
|
||||
}
|
||||
static void WINAPI f_delete(LPCSTR name) {
|
||||
(void)name;
|
||||
deletes++;
|
||||
calls++, deletes++;
|
||||
if (!readonly) own.exists = 0, touch(&own);
|
||||
}
|
||||
static BOOL WINAPI f_get(LPCSTR prop, LPBYTE data, DWORD size) {
|
||||
calls++;
|
||||
if (size != 4) return FALSE;
|
||||
if (!strcmp(prop, "FramerateLimit")) return memcpy(data, &loaded.limit, 4), TRUE;
|
||||
if (!strcmp(prop, "FramerateLimitDenominator") && !no_denominator) return memcpy(data, &loaded.denom, 4), TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
static BOOL WINAPI f_set(LPCSTR prop, LPBYTE data, DWORD size) {
|
||||
calls++;
|
||||
if (size != 4) return FALSE;
|
||||
DWORD v;
|
||||
memcpy(&v, data, 4);
|
||||
@@ -104,8 +108,13 @@ static BOOL WINAPI f_set(LPCSTR prop, LPBYTE data, DWORD size) {
|
||||
if (!strcmp(prop, "FramerateLimitDenominator") && !no_denominator) return memcpy(&loaded.denom, data, 4), TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
static void WINAPI f_update(void) { updates++; }
|
||||
static DWORD WINAPI f_enum(LPSTR list, DWORD size) { (void)size; list[0] = 0; return 1; }
|
||||
static void WINAPI f_update(void) { calls++, updates++; }
|
||||
static DWORD WINAPI f_enum(LPSTR list, DWORD size) {
|
||||
(void)size;
|
||||
calls++;
|
||||
list[0] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static Prof *file_of(const char *path) {
|
||||
if (strstr(path, "\\Profiles\\ocarina.exe.cfg")) return own.exists ? &own : NULL;
|
||||
@@ -1029,6 +1038,35 @@ static void state_locked(void) {
|
||||
CHECK(!own.exists && !strcmp(state(), "rtss1 0 0 1 60 1 60 1 ocarina_dev.exe\n"), "still as it was:\n%s", state());
|
||||
}
|
||||
|
||||
// A crash on RTSS's own thread, inside SaveProfile with the lock held, in a run whose state file could not be written:
|
||||
// rtss_limit_crash closes the run and returns at once, with no call into RTSS's library and no wait for the lock that
|
||||
// thread holds (rtss_limit_restore would try it for a second); nothing after it reaches the profile.
|
||||
static ULONGLONG crash_ms;
|
||||
static LONG crash_calls = -1;
|
||||
static void crash_on_worker(void) {
|
||||
LONG c0 = calls;
|
||||
ULONGLONG t0 = GetTickCount64();
|
||||
rtss_limit_crash();
|
||||
crash_ms = GetTickCount64() - t0;
|
||||
crash_calls = calls - c0;
|
||||
}
|
||||
static void crash_worker_thread(void) {
|
||||
state_readonly = 1;
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
during_save = crash_on_worker;
|
||||
ask(60000);
|
||||
poll();
|
||||
show("a crash on RTSS's thread inside SaveProfile");
|
||||
CHECK(crash_calls == 0, "%ld calls into RTSS's library from the crash", crash_calls);
|
||||
CHECK(crash_ms < 200, "the crash took %llu ms", crash_ms);
|
||||
CHECK(atomic_load(&closed) && !rtss_limit_active() && !rtss_limit_state(), "the run closed");
|
||||
LONG s0 = saves;
|
||||
ask(144000);
|
||||
poll();
|
||||
poll();
|
||||
CHECK(saves == s0, "nothing after the crash reaches the profile");
|
||||
}
|
||||
|
||||
// A crash with the line in the state file: no call into RTSS's library, the profile and the line stay for the next
|
||||
// start, and nothing after it reaches the profile.
|
||||
static void crash_recorded(void) {
|
||||
@@ -1242,6 +1280,7 @@ static const struct { const char *name; void (*run)(void); } scenarios[] = {
|
||||
{"state_unwritable", state_unwritable},
|
||||
{"state_locked", state_locked},
|
||||
{"crash_recorded", crash_recorded},
|
||||
{"crash_worker_thread", crash_worker_thread},
|
||||
{"kill_restart", kill_restart},
|
||||
{"kill_restart_player", kill_restart_player},
|
||||
{"kill_restart_edited", kill_restart_edited},
|
||||
|
||||
Reference in New Issue
Block a user