OSDN Git Service

[Magnifier-38] Avoid deadlock causing ANR
authorMihai Popa <popam@google.com>
Thu, 29 Mar 2018 14:56:17 +0000 (15:56 +0100)
committerMihai Popa <popam@google.com>
Mon, 2 Apr 2018 18:18:55 +0000 (18:18 +0000)
A deadlock between the UI and render threads caused by magnifier is
currently making the running app to become not responding. The deadlock
could happen in the following scenario:
1. The UI thread sets a frame callback and asks mRenderer to sync and
draw the current frame. A draw task is enqueued by RenderProxy in the
C++ code
2. The UI thread starts an InternalPopupWindow#destroy() on the UI
thread and acquires mLock
3. mRenderer#destroy() is called on the UI thread, which enqueues a
destroy task in the C++ code. However, this is implemented
synchronously, so the UI thread will wait until the destroy task is
dequeued and executed
4. Since the draw task was enqueued before the destroy task, this will
be executed and the frame callback will be called on the render thread
5. The frame callback tries to acquire mLock. However, this is held by
the UI thread, so the render thread has to wait for it. At the same
time, the UI thread cannot progress either as it is waiting for its
destroy task to execute.

The CL adds a new lock which is used by the UI thread to mark its
intention to #destroy(), such that the render thread will know about
this before trying to acquire mLock and starving.

Bug: 75276625
Test: manual testing
Test: atest CtsWidgetTestCases:android.widget.cts.MagnifierTest
Change-Id: Iedf2948350fcf8dd9c819c085b31b7ccaf2db7c5

core/java/android/widget/Magnifier.java

index c1c5d9d..5eb6699 100644 (file)
@@ -393,6 +393,12 @@ public final class Magnifier {
         private int mWindowPositionY;
         private boolean mPendingWindowPositionUpdate;
 
+        // The lock used to synchronize the UI and render threads when a #destroy
+        // is performed on the UI thread and a frame callback on the render thread.
+        // When both mLock and mDestroyLock need to be held at the same time,
+        // mDestroyLock should be acquired before mLock in order to avoid deadlocks.
+        private final Object mDestroyLock = new Object();
+
         InternalPopupWindow(final Context context, final Display display,
                 final Surface parentSurface,
                 final int width, final int height, final float elevation, final float cornerRadius,
@@ -517,9 +523,11 @@ public final class Magnifier {
          * Destroys this instance.
          */
         public void destroy() {
+            synchronized (mDestroyLock) {
+                mSurface.destroy();
+            }
             synchronized (mLock) {
                 mRenderer.destroy();
-                mSurface.destroy();
                 mSurfaceControl.destroy();
                 mSurfaceSession.kill();
                 mBitmapRenderNode.destroy();
@@ -567,21 +575,23 @@ public final class Magnifier {
                     final int pendingY = mWindowPositionY;
 
                     callback = frame -> {
-                        synchronized (mLock) {
+                        synchronized (mDestroyLock) {
                             if (!mSurface.isValid()) {
                                 return;
                             }
-                            mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
-                            // Show or move the window at the content draw frame.
-                            SurfaceControl.openTransaction();
-                            mSurfaceControl.deferTransactionUntil(mSurface, frame);
-                            if (updateWindowPosition) {
-                                mSurfaceControl.setPosition(pendingX, pendingY);
-                            }
-                            if (firstDraw) {
-                                mSurfaceControl.show();
+                            synchronized (mLock) {
+                                mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
+                                // Show or move the window at the content draw frame.
+                                SurfaceControl.openTransaction();
+                                mSurfaceControl.deferTransactionUntil(mSurface, frame);
+                                if (updateWindowPosition) {
+                                    mSurfaceControl.setPosition(pendingX, pendingY);
+                                }
+                                if (firstDraw) {
+                                    mSurfaceControl.show();
+                                }
+                                SurfaceControl.closeTransaction();
                             }
-                            SurfaceControl.closeTransaction();
                         }
                     };
                 } else {