Skip to content

Commit 55a452e

Browse files
author
Ubuntu
committed
fix: reset VulkanUiWatchdog pong before SetFormat to prevent 7s kill
Root cause (confirmed from new logs, build 2026.523.248.0): The VulkanUiWatchdog fires at 7s (before the native watchdog at 10s). PR #356 reset the native watchdog via NativeWatchdog.Heartbeat() but did NOT reset lastPongMonotonicMs — the timestamp checked by the VulkanUiWatchdog. SetFormat blocks the UI thread for the entire SurfaceDestroyed→SurfaceCreated→SurfaceChanged cycle. During this window the Handler pong cannot run. With ANGLE-GC and Veldrid-AsyncPr threads active (extra GPU/JIT overhead on affected Adreno devices), the block exceeds 7s, the VulkanUiWatchdog fires, and killProcess() kills the app before the game ever renders a frame. Fix: - Promote lastPongMonotonicMs from a local lambda capture to an activity field (vulkanWatchdogLastPongMs). - Reset it via Volatile.Write alongside NativeWatchdog.Heartbeat() at every SetFormat call site (reactive guard + proactive lambda). This extends the effective watchdog window from 7s to 7s + (time since reset), which is always sufficient for SetFormat to complete and the draw thread to resume normally.
1 parent 4fe73b4 commit 55a452e

1 file changed

Lines changed: 20 additions & 4 deletions

File tree

osu.Android/OsuGameActivity.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,9 @@ protected override void OnCreate(Bundle? savedInstanceState)
405405
try
406406
{
407407
setFormatPending = true;
408+
// Reset both watchdogs before the blocking SetFormat call.
409+
NativeWatchdog.Heartbeat();
410+
System.Threading.Volatile.Write(ref vulkanWatchdogLastPongMs[0], System.Environment.TickCount64);
408411
holder.SetFormat(global::Android.Graphics.Format.Rgba8888);
409412
Logger.Log("[osu!] SurfaceHolder.SetFormat(Rgba8888) applied (Vulkan renderer).", LoggingTarget.Runtime, LogLevel.Important);
410413
}
@@ -722,6 +725,13 @@ await Task.WhenAll(uris.Select(async uri =>
722725

723726
private readonly System.Threading.ManualResetEventSlim surfaceEvent = new System.Threading.ManualResetEventSlim(false);
724727

728+
/// <summary>
729+
/// Shared timestamp written by the UI-thread pong and read by the VulkanUiWatchdog.
730+
/// Promoted to an activity field so that known UI-thread-blocking operations
731+
/// (e.g. SetFormat) can reset it and prevent a spurious watchdog kill.
732+
/// </summary>
733+
private readonly long[] vulkanWatchdogLastPongMs = { System.Environment.TickCount64 };
734+
725735
// Hold both the JNI global ref AND the managed Surface peer alive against the
726736
// SurfaceView lifecycle. The global ref alone is NOT enough — .NET-for-Android
727737
// tracks managed peers separately, and once the local `Surface` returned by
@@ -909,7 +919,13 @@ public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Forma
909919
try
910920
{
911921
NativeWatchdog.Heartbeat();
912-
Debug.WriteLine("[osu!] Native watchdog heartbeat ticked before SetFormat(RGBA8888)");
922+
// Also reset the VulkanUiWatchdog pong timestamp. SetFormat blocks the
923+
// UI thread for the full SurfaceDestroyed→SurfaceCreated→SurfaceChanged
924+
// cycle; during this window the Handler pong cannot run. Without this
925+
// reset the 7-second VulkanUiWatchdog fires and kills the process before
926+
// the native watchdog (10 s) would, producing the observed black screen.
927+
System.Threading.Volatile.Write(ref vulkanWatchdogLastPongMs[0], System.Environment.TickCount64);
928+
Debug.WriteLine("[osu!] Native watchdog heartbeat + VulkanUiWatchdog pong reset before SetFormat(RGBA8888)");
913929
}
914930
catch (Exception e)
915931
{
@@ -1063,9 +1079,9 @@ private void startVulkanUiWatchdog()
10631079

10641080
// Shared field written by the UI-thread pong and read by the watchdog thread.
10651081
// Interlocked/Volatile access: the pong runs on the UI thread, the reader runs
1066-
// on the watchdog thread. The field is a reference so it can be captured by
1067-
// both lambdas without a ref capture.
1068-
long[] lastPongMonotonicMs = { System.Environment.TickCount64 };
1082+
// on the watchdog thread. Promoted to an activity field so that SurfaceChanged
1083+
// can reset it before blocking SetFormat calls, preventing spurious kills.
1084+
long[] lastPongMonotonicMs = vulkanWatchdogLastPongMs;
10691085

10701086
// Self-rescheduling pong: posts itself every ping_interval_ms on the UI thread.
10711087
// Capturing pingHandler via a local ref rather than the outer variable so the

0 commit comments

Comments
 (0)