Skip to content

Commit 2dd8eeb

Browse files
author
Ubuntu
committed
fix: set surface event before SetFormat to prevent draw thread deadlock
The previous approach (surfaceEvent.Reset() + return) caused the draw thread to block on surfaceEvent.Wait(5000) while SetFormat triggered a synchronous surface teardown. The draw thread never got a valid surface handle and the native watchdog fired. New approach: Set surfaceEvent BEFORE calling SetFormat so the draw thread can proceed with the current (soon-to-be-recreated) surface. The native watchdog is also ticked before SetFormat to buy time for the synchronous teardown. Order: surfaceEvent.Set() → NativeWatchdog.Heartbeat() → holder.SetFormat()
1 parent 947ceab commit 2dd8eeb

1 file changed

Lines changed: 10 additions & 12 deletions

File tree

osu.Android/OsuGameActivity.cs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -900,17 +900,20 @@ public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Forma
900900
Logger.Log(rgb565Message, LoggingTarget.Runtime, LogLevel.Important);
901901
Logger.Log(rgb565Message, LoggingTarget.Performance, LogLevel.Important);
902902

903+
// Set the surface event BEFORE anything else so the draw thread can
904+
// proceed with the current (soon-to-be-recreated) surface. Without this,
905+
// the draw thread blocks on surfaceEvent.Wait(5000) and the native watchdog
906+
// fires because no managed heartbeat arrives during the synchronous teardown.
907+
// A brief frame or two with the old surface format is preferable to a
908+
// deadlocked draw thread and watchdog kill.
909+
surfaceEvent.Set();
910+
903911
// Tick the native watchdog BEFORE calling SetFormat. SetFormat triggers a
904912
// synchronous surface teardown on the UI thread that can block for hundreds
905913
// of milliseconds. During this window the Update thread may not get a chance
906914
// to send a heartbeat (especially during startup when the runtime is under
907915
// heavy load), and the native watchdog (10s default) would fire, killing the
908916
// process and producing a black screen.
909-
//
910-
// Calling Heartbeat() here resets the watchdog timer from the UI thread,
911-
// giving SetFormat time to complete without triggering a false positive.
912-
// The native watchdog checks g_lastHeartbeatMonotonicSec which is updated
913-
// by a simple atomic store — safe to call from any thread.
914917
try
915918
{
916919
NativeWatchdog.Heartbeat();
@@ -921,6 +924,8 @@ public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Forma
921924
Debug.WriteLine($"[osu!] NativeWatchdog.Heartbeat failed (non-fatal): {e.Message}");
922925
}
923926

927+
Debug.WriteLine("[osu!] Native surface format change requested (RGB565→RGBA8888)");
928+
924929
try
925930
{
926931
holder.SetFormat(global::Android.Graphics.Format.Rgba8888);
@@ -930,13 +935,6 @@ public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Forma
930935
Debug.WriteLine($"[osu!] Failed to request RGBA8888 format change for Vulkan: {e.Message}");
931936
}
932937

933-
Debug.WriteLine("[osu!] Native surface format change requested (RGB565→RGBA8888)");
934-
935-
// Reset the surface event so GetSurfaceGlobalRef() does NOT unblock with the
936-
// old (about-to-be-invalidated) surface handle. The event will be re-set when
937-
// SurfaceChanged fires again for the new RGBA8888 surface.
938-
surfaceEvent.Reset();
939-
940938
return;
941939
}
942940

0 commit comments

Comments
 (0)