From 422cc7b91bfd10b293673f6497daa6daf469fabe Mon Sep 17 00:00:00 2001 From: Fredia Huya-Kouadio Date: Fri, 19 Dec 2025 07:53:21 -0800 Subject: [PATCH] Fix ANRs when shutting down the engine --- .../main/java/org/godotengine/godot/Godot.kt | 9 +++++- .../godotengine/godot/GodotGLRenderView.java | 4 +-- .../godotengine/godot/GodotRenderView.java | 2 +- .../godot/GodotVulkanRenderView.java | 4 +-- .../godotengine/godot/gl/GLSurfaceView.java | 29 +++++++++++++++++++ .../godotengine/godot/vulkan/VkSurfaceView.kt | 9 ++++++ .../org/godotengine/godot/vulkan/VkThread.kt | 27 ++++++++++++++++- 7 files changed, 77 insertions(+), 7 deletions(-) diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt index fe29efb25f..2e045215d0 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/Godot.kt @@ -108,6 +108,8 @@ class Godot private constructor(val context: Context) { } } + private const val EXIT_RENDERER_TIMEOUT_IN_MS = 1500L + // Supported build flavors private const val EDITOR_FLAVOR = "editor" private const val TEMPLATE_FLAVOR = "template" @@ -748,7 +750,12 @@ class Godot private constructor(val context: Context) { plugin.onMainDestroy() } - renderView?.onActivityDestroyed() + if (renderView?.blockingExitRenderer(EXIT_RENDERER_TIMEOUT_IN_MS) != true) { + Log.w(TAG, "Unable to exit the renderer within $EXIT_RENDERER_TIMEOUT_IN_MS ms... Force quitting the process.") + onGodotTerminating() + forceQuit(0) + } + this.primaryHost = null } diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotGLRenderView.java index b57fff1e23..9b8f60a025 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotGLRenderView.java @@ -132,8 +132,8 @@ class GodotGLRenderView extends GLSurfaceView implements GodotRenderView { } @Override - public void onActivityDestroyed() { - requestRenderThreadExitAndWait(); + public boolean blockingExitRenderer(long blockingTimeInMs) { + return requestRenderThreadExitAndWait(blockingTimeInMs); } @Override diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotRenderView.java index c998af7485..2220a28904 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotRenderView.java @@ -55,7 +55,7 @@ public interface GodotRenderView { void onActivityStarted(); - void onActivityDestroyed(); + boolean blockingExitRenderer(long blockingTimeInMs); GodotInputHandler getInputHandler(); diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotVulkanRenderView.java index 9060baf448..b1ce89c85e 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/GodotVulkanRenderView.java @@ -117,8 +117,8 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { } @Override - public void onActivityDestroyed() { - requestRenderThreadExitAndWait(); + public boolean blockingExitRenderer(long blockingTimeInMs) { + return requestRenderThreadExitAndWait(blockingTimeInMs); } @Override diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/gl/GLSurfaceView.java b/platform/android/java/lib/src/main/java/org/godotengine/godot/gl/GLSurfaceView.java index 6a4e9da699..8b25fdfece 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/gl/GLSurfaceView.java +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/gl/GLSurfaceView.java @@ -604,6 +604,18 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback mGLThread.requestExitAndWait(); } } + + /** + * Requests the render thread to exit and block up to the given timeInMs until it's done. + * + * @return true if the thread exited, false otherwise. + */ + protected final boolean requestRenderThreadExitAndWait(long timeInMs) { + if (mGLThread != null) { + return mGLThread.requestExitAndWait(timeInMs); + } + return false; + } // -- GODOT end -- /** @@ -1796,6 +1808,23 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } } + public boolean requestExitAndWait(long timeInMs) { + // Don't call this from GLThread thread or it is a guaranteed deadlock! + synchronized(sGLThreadManager) { + mShouldExit = true; + sGLThreadManager.notifyAll(); + if (!mExited) { + try { + sGLThreadManager.wait(timeInMs); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + + return mExited; + } + } + public void requestReleaseEglContextLocked() { mShouldReleaseEglContext = true; sGLThreadManager.notifyAll(); diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/main/java/org/godotengine/godot/vulkan/VkSurfaceView.kt index 9e30de6a15..c711fc519f 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/vulkan/VkSurfaceView.kt +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/vulkan/VkSurfaceView.kt @@ -119,6 +119,15 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf vkThread.requestExitAndWait() } + /** + * Requests the render thread to exit and block up to the given [timeInMs] until it's done. + * + * @return true if the thread exited, false otherwise. + */ + fun requestRenderThreadExitAndWait(timeInMs: Long): Boolean { + return vkThread.requestExitAndWait(timeInMs) + } + override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { vkThread.onSurfaceChanged(width, height) } diff --git a/platform/android/java/lib/src/main/java/org/godotengine/godot/vulkan/VkThread.kt b/platform/android/java/lib/src/main/java/org/godotengine/godot/vulkan/VkThread.kt index c7cb97d911..e5e3b89b59 100644 --- a/platform/android/java/lib/src/main/java/org/godotengine/godot/vulkan/VkThread.kt +++ b/platform/android/java/lib/src/main/java/org/godotengine/godot/vulkan/VkThread.kt @@ -32,6 +32,7 @@ package org.godotengine.godot.vulkan import android.util.Log +import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -104,13 +105,37 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk try { Log.i(TAG, "Waiting on exit for $name") lockCondition.await() - } catch (ex: InterruptedException) { + } catch (_: InterruptedException) { currentThread().interrupt() } } } } + /** + * Request the thread to exit and block up to the given [timeInMs] until it's done. + * + * @return true if the thread exited, false otherwise. + */ + fun requestExitAndWait(timeInMs: Long): Boolean { + lock.withLock { + shouldExit = true + lockCondition.signalAll() + + var remainingTimeInNanos = TimeUnit.MILLISECONDS.toNanos(timeInMs) + while (!exited && remainingTimeInNanos > 0) { + try { + Log.i(TAG, "Waiting on exit for $name for $remainingTimeInNanos") + remainingTimeInNanos = lockCondition.awaitNanos(remainingTimeInNanos) + } catch (_: InterruptedException) { + currentThread().interrupt() + } + } + + return exited + } + } + /** * Invoked when the app resumes. */