Skip to content

Commit 093a72b

Browse files
fix: native watchdog kills process on renderer init hang to trigger safe-mode OpenGL fallback
When GraphicsDevice.CreateVulkan() hangs indefinitely in native Vulkan driver code, game threads are never created and the user sees a permanent black screen. The native watchdog now kills the process after 2× the hang threshold (20s) when no managed heartbeat has ever been observed. This leaves FLAG_STARTUP_IN_PROGRESS on disk, causing the next launch to enter safe-mode and force OpenGL rendering. Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/3b2e3212-5d99-4db3-855b-b2e0b0c2a186 Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
1 parent 21e4675 commit 093a72b

1 file changed

Lines changed: 42 additions & 2 deletions

File tree

osu.Android/Native/native_watchdog.cpp

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,17 @@ void* watchdogMain(void* /*arg*/)
522522
"watchdog thread up (tid=%d, hang_threshold=%ds)",
523523
(int)gettid(), (int)g_hangSeconds);
524524

525+
// Kill threshold: if no managed heartbeat has EVER been observed (meaning
526+
// the game threads were never created — renderer init hung) and this
527+
// condition persists for 2× the hang threshold (default: 20s with 10s
528+
// threshold), kill the process. This triggers the safe-mode system on the
529+
// next launch (FLAG_STARTUP_IN_PROGRESS remains on disk → next launch
530+
// forces OpenGL via ForceOpenGLRendererIfSafeMode). The 2× multiplier
531+
// gives the renderer a generous window: the first dump fires at 1×
532+
// threshold for diagnostics, then we wait one more threshold period before
533+
// concluding the hang is unrecoverable.
534+
const uint64_t killThresholdSec = (uint64_t)g_hangSeconds * 2;
535+
525536
for (;;)
526537
{
527538
struct timespec req{};
@@ -531,8 +542,6 @@ void* watchdogMain(void* /*arg*/)
531542
// We accept early wakeups (EINTR) silently and re-loop.
532543
(void)clock_nanosleep(CLOCK_MONOTONIC, 0, &req, nullptr);
533544

534-
if (g_dumpCount >= kMaxDumps) continue;
535-
536545
uint64_t now = monotonicSec();
537546
if (now == 0) continue;
538547

@@ -547,6 +556,37 @@ void* watchdogMain(void* /*arg*/)
547556
uint64_t age = now - reference;
548557
if (age < (uint64_t)g_hangSeconds) continue;
549558

559+
// Kill the process if no heartbeat was EVER observed and we have
560+
// exceeded the kill threshold. This means the renderer initialization
561+
// (typically GraphicsDevice.CreateVulkan) hung in native driver code
562+
// and game threads were never created. Killing triggers safe-mode on
563+
// the next launch, which falls back to OpenGL.
564+
if (lastTick == 0 && age >= killThresholdSec)
565+
{
566+
// Write a final diagnostic before killing.
567+
int fd = openLogAppend();
568+
writeStr(fd, "\n=========================================================\n");
569+
writeStr(fd, "=== NATIVE WATCHDOG KILL ===\n");
570+
writeStr(fd, " reason = renderer init hang (no heartbeat ever observed after ");
571+
writeDec(fd, (long long)age);
572+
writeStr(fd, "s)\n");
573+
writeStr(fd, " action = killing process for safe-mode restart (OpenGL fallback)\n");
574+
writeStr(fd, " kill_threshold = ");
575+
writeDec(fd, (long long)killThresholdSec);
576+
writeStr(fd, "s\n");
577+
writeStr(fd, "=== END NATIVE WATCHDOG KILL ===\n\n");
578+
if (fd >= 0) close(fd);
579+
580+
__android_log_write(ANDROID_LOG_ERROR, WATCHDOG_LOG_TAG,
581+
"NATIVE WATCHDOG KILL — renderer init hung, killing for safe-mode OpenGL restart");
582+
583+
// Use _exit to terminate immediately without running atexit handlers
584+
// or C++ destructors — the process is in an unrecoverable state.
585+
_exit(1);
586+
}
587+
588+
if (g_dumpCount >= kMaxDumps) continue;
589+
550590
if (g_lastDumpMonotonicSec != 0 && now - g_lastDumpMonotonicSec < kRedumpCooldownSec)
551591
continue;
552592

0 commit comments

Comments
 (0)