From 7e53d0e9f58a35e4db2ffb0041324b78da70d270 Mon Sep 17 00:00:00 2001 From: Robert Carr Date: Tue, 21 Dec 2021 10:34:36 -0800 Subject: [PATCH] ViewRootImpl/SurfaceView: Listen for queue stalls Native machinery now reports queue stalls from native layer up to java layer, which can more appropriately handle errors. The first case we handle is the case of "stuck fences", generally indicating GPU hangs. In this case we trigger a bespoke ANR rather than waiting for an ANR in dequeueBuffers later. dequeueBuffers ANR could have any number of causes, and this large cluster is difficult to debug. Bug: 216160569 Test: Existing tests pass Change-Id: I7b4429ce96d0bbfa1b74534ddf2b447facb22d10 --- core/java/android/view/SurfaceView.java | 2 + core/java/android/view/ViewRootImpl.java | 23 +++++++ .../jni/android_graphics_BLASTBufferQueue.cpp | 61 ++++++++++++++++++- .../android/graphics/BLASTBufferQueue.java | 10 +++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 96b1abb62dbf4..5c89e2215a9f7 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1200,8 +1200,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } mTransformHint = viewRoot.getBufferTransformHint(); mBlastSurfaceControl.setTransformHint(mTransformHint); + mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */); mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat); + mBlastBufferQueue.setTransactionHangCallback(ViewRootImpl.sTransactionHangCallback); } private void onDrawFinished() { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 27ce711558573..3ebe7436fdaf9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -851,6 +851,28 @@ public final class ViewRootImpl implements ViewParent, */ private Bundle mRelayoutBundle = new Bundle(); + private static volatile boolean sAnrReported = false; + static BLASTBufferQueue.TransactionHangCallback sTransactionHangCallback = + new BLASTBufferQueue.TransactionHangCallback() { + @Override + public void onTransactionHang(boolean isGPUHang) { + if (isGPUHang && !sAnrReported) { + sAnrReported = true; + try { + ActivityManager.getService().appNotResponding( + "Buffer processing hung up due to stuck fence. Indicates GPU hang"); + } catch (RemoteException e) { + // We asked the system to crash us, but the system + // already crashed. Unfortunately things may be + // out of control. + } + } else { + // TODO: Do something with this later. For now we just ANR + // in dequeue buffer later like we always have. + } + } + }; + private String mTag = TAG; public ViewRootImpl(Context context, Display display) { @@ -2086,6 +2108,7 @@ public final class ViewRootImpl implements ViewParent, } mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format); + mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback); Surface blastSurface = mBlastBufferQueue.createSurface(); // Only call transferFrom if the surface has changed to prevent inc the generation ID and // causing EGL resources to be recreated. diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index c0f7b41d116d1..4af28ea243611 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -47,6 +47,43 @@ static JNIEnv* getenv(JavaVM* vm) { return env; } + struct { + jmethodID onTransactionHang; +} gTransactionHangCallback; + +class TransactionHangCallbackWrapper : public LightRefBase { +public: + explicit TransactionHangCallbackWrapper(JNIEnv* env, jobject jobject) { + env->GetJavaVM(&mVm); + mTransactionHangObject = env->NewGlobalRef(jobject); + LOG_ALWAYS_FATAL_IF(!mTransactionHangObject, "Failed to make global ref"); + } + + ~TransactionHangCallbackWrapper() { + if (mTransactionHangObject) { + getenv()->DeleteGlobalRef(mTransactionHangObject); + mTransactionHangObject = nullptr; + } + } + + void onTransactionHang(bool isGpuHang) { + if (mTransactionHangObject) { + getenv()->CallVoidMethod(mTransactionHangObject, + gTransactionHangCallback.onTransactionHang, isGpuHang); + } + } + +private: + JavaVM* mVm; + jobject mTransactionHangObject; + + JNIEnv* getenv() { + JNIEnv* env; + mVm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + return env; + } +}; + static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jboolean updateDestinationFrame) { ScopedUtfChars name(env, jName); @@ -141,6 +178,20 @@ static bool nativeIsSameSurfaceControl(JNIEnv* env, jclass clazz, jlong ptr, jlo sp queue = reinterpret_cast(ptr); return queue->isSameSurfaceControl(reinterpret_cast(surfaceControl)); } + +static void nativeSetTransactionHangCallback(JNIEnv* env, jclass clazz, jlong ptr, + jobject transactionHangCallback) { + sp queue = reinterpret_cast(ptr); + if (transactionHangCallback == nullptr) { + queue->setTransactionHangCallback(nullptr); + } else { + sp wrapper = + new TransactionHangCallbackWrapper{env, transactionHangCallback}; + queue->setTransactionHangCallback([wrapper](bool isGpuHang) { + wrapper->onTransactionHang(isGpuHang); + }); + } +} static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong ptr, jlong frameNum) { @@ -163,7 +214,10 @@ static const JNINativeMethod gMethods[] = { {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum}, {"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions}, {"nativeIsSameSurfaceControl", "(JJ)Z", (void*)nativeIsSameSurfaceControl}, - {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions} + {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions}, + {"nativeSetTransactionHangCallback", + "(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V", + (void*)nativeSetTransactionHangCallback}, // clang-format on }; @@ -180,6 +234,11 @@ int register_android_graphics_BLASTBufferQueue(JNIEnv* env) { jclass consumer = FindClassOrDie(env, "java/util/function/Consumer"); gTransactionConsumer.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V"); + jclass transactionHangClass = + FindClassOrDie(env, "android/graphics/BLASTBufferQueue$TransactionHangCallback"); + gTransactionHangCallback.onTransactionHang = + GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang", "(Z)V"); + return 0; } diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 4b723d1569c9a..1c41d06a3da20 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -43,6 +43,12 @@ public final class BLASTBufferQueue { private static native boolean nativeIsSameSurfaceControl(long ptr, long surfaceControlPtr); private static native SurfaceControl.Transaction nativeGatherPendingTransactions(long ptr, long frameNumber); + private static native void nativeSetTransactionHangCallback(long ptr, + TransactionHangCallback callback); + + public interface TransactionHangCallback { + void onTransactionHang(boolean isGpuHang); + } /** Create a new connection with the surface flinger. */ public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height, @@ -184,4 +190,8 @@ public final class BLASTBufferQueue { public SurfaceControl.Transaction gatherPendingTransactions(long frameNumber) { return nativeGatherPendingTransactions(mNativeObject, frameNumber); } + + public void setTransactionHangCallback(TransactionHangCallback hangCallback) { + nativeSetTransactionHangCallback(mNativeObject, hangCallback); + } }