From 4200233adccc9ab5976429c57d50adc147f27e5c Mon Sep 17 00:00:00 2001 From: Linus Nilsson Date: Tue, 11 Jul 2023 14:12:58 +0200 Subject: [PATCH] Add exception callbacks to EglThread This allows EglRenderer to preserve existing behavior of not sending any more tasks to the render thread after an GL exception has been thrown. Bug: b/225229697 Change-Id: I09e7cc48bf139aab4c9e147c2b24972ccd401672 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/311548 Reviewed-by: Sergey Silkin Commit-Queue: Linus Nilsson Cr-Commit-Position: refs/heads/main@{#40419} --- sdk/android/api/org/webrtc/EglThread.java | 68 +++++++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/sdk/android/api/org/webrtc/EglThread.java b/sdk/android/api/org/webrtc/EglThread.java index 0227f2a238..47f7810b7c 100644 --- a/sdk/android/api/org/webrtc/EglThread.java +++ b/sdk/android/api/org/webrtc/EglThread.java @@ -12,8 +12,12 @@ package org.webrtc; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; +import java.util.ArrayList; +import java.util.List; import org.webrtc.EglBase.EglConnection; /** EGL graphics thread that allows multiple clients to share the same underlying EGLContext. */ @@ -31,7 +35,8 @@ public class EglThread { @Nullable final EglBase.Context sharedContext, final int[] configAttributes) { final HandlerThread renderThread = new HandlerThread("EglThread"); renderThread.start(); - Handler handler = new Handler(renderThread.getLooper()); + HandlerWithExceptionCallbacks handler = + new HandlerWithExceptionCallbacks(renderThread.getLooper()); // Not creating the EGLContext on the thread it will be used on seems to cause issues with // creating window surfaces on certain devices. So keep the same legacy behavior as EglRenderer @@ -51,12 +56,51 @@ public class EglThread { releaseMonitor != null ? releaseMonitor : eglThread -> true, handler, eglConnection); } + /** + * Handler that triggers callbacks when an uncaught exception happens when handling a message. + */ + private static class HandlerWithExceptionCallbacks extends Handler { + private final Object callbackLock = new Object(); + @GuardedBy("callbackLock") private final List exceptionCallbacks = new ArrayList<>(); + + public HandlerWithExceptionCallbacks(Looper looper) { + super(looper); + } + + @Override + public void dispatchMessage(Message msg) { + try { + super.dispatchMessage(msg); + } catch (Exception e) { + Logging.e("EglThread", "Exception on EglThread", e); + synchronized (callbackLock) { + for (Runnable callback : exceptionCallbacks) { + callback.run(); + } + } + throw e; + } + } + + public void addExceptionCallback(Runnable callback) { + synchronized (callbackLock) { + exceptionCallbacks.add(callback); + } + } + + public void removeExceptionCallback(Runnable callback) { + synchronized (callbackLock) { + exceptionCallbacks.remove(callback); + } + } + } + private final ReleaseMonitor releaseMonitor; - private final Handler handler; + private final HandlerWithExceptionCallbacks handler; private final EglConnection eglConnection; - @VisibleForTesting - EglThread(ReleaseMonitor releaseMonitor, Handler handler, EglConnection eglConnection) { + private EglThread(ReleaseMonitor releaseMonitor, HandlerWithExceptionCallbacks handler, + EglConnection eglConnection) { this.releaseMonitor = releaseMonitor; this.handler = handler; this.eglConnection = eglConnection; @@ -88,4 +132,18 @@ public class EglThread { public Handler getHandler() { return handler; } + + /** + * Adds a callback that will be called on the EGL thread if there is an exception on the thread. + */ + public void addExceptionCallback(Runnable callback) { + handler.addExceptionCallback(callback); + } + + /** + * Removes a previously added exception callback. + */ + public void removeExceptionCallback(Runnable callback) { + handler.removeExceptionCallback(callback); + } }