Fix ANRs when shutting down the engine

This commit is contained in:
Fredia Huya-Kouadio
2025-12-19 07:53:21 -08:00
parent 1559ab34c6
commit 422cc7b91b
7 changed files with 77 additions and 7 deletions

View File

@@ -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
}

View File

@@ -132,8 +132,8 @@ class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
}
@Override
public void onActivityDestroyed() {
requestRenderThreadExitAndWait();
public boolean blockingExitRenderer(long blockingTimeInMs) {
return requestRenderThreadExitAndWait(blockingTimeInMs);
}
@Override

View File

@@ -55,7 +55,7 @@ public interface GodotRenderView {
void onActivityStarted();
void onActivityDestroyed();
boolean blockingExitRenderer(long blockingTimeInMs);
GodotInputHandler getInputHandler();

View File

@@ -117,8 +117,8 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
}
@Override
public void onActivityDestroyed() {
requestRenderThreadExitAndWait();
public boolean blockingExitRenderer(long blockingTimeInMs) {
return requestRenderThreadExitAndWait(blockingTimeInMs);
}
@Override

View File

@@ -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();

View File

@@ -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)
}

View File

@@ -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.
*/