From d577e728e9bccbafc707af3060ea914caa73c14f Mon Sep 17 00:00:00 2001 From: Jing Ji Date: Sat, 25 Jul 2020 19:00:04 -0700 Subject: [PATCH] Workaround memory leaks in Looper Message object from previous iteration may remain live, moving the iteration body into another function as a workaround. Bug: 146185876 Test: See b/146185876#comment1 for detailed steps Change-Id: I9239f0b8c862466f124690348ce69f31aaec2fb7 --- core/java/android/os/Looper.java | 196 +++++++++++++++++-------------- 1 file changed, 106 insertions(+), 90 deletions(-) diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index a7e326378228d..b05ea39850f88 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -94,6 +94,11 @@ public final class Looper { */ private long mSlowDeliveryThresholdMs; + /** + * True if a message delivery takes longer than {@link #mSlowDeliveryThresholdMs}. + */ + private boolean mSlowDeliveryDetected; + /** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call @@ -147,6 +152,105 @@ public final class Looper { sObserver = observer; } + /** + * Poll and deliver single message, return true if the outer loop should continue. + */ + private static boolean loopOnce(final Looper me, + final long ident, final int thresholdOverride) { + Message msg = me.mQueue.next(); // might block + if (msg == null) { + // No message indicates that the message queue is quitting. + return false; + } + + // This must be in a local variable, in case a UI event sets the logger + final Printer logging = me.mLogging; + if (logging != null) { + logging.println(">>>>> Dispatching to " + msg.target + " " + + msg.callback + ": " + msg.what); + } + // Make sure the observer won't change while processing a transaction. + final Observer observer = sObserver; + + final long traceTag = me.mTraceTag; + long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; + long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs; + if (thresholdOverride > 0) { + slowDispatchThresholdMs = thresholdOverride; + slowDeliveryThresholdMs = thresholdOverride; + } + final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0); + final boolean logSlowDispatch = (slowDispatchThresholdMs > 0); + + final boolean needStartTime = logSlowDelivery || logSlowDispatch; + final boolean needEndTime = logSlowDispatch; + + if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { + Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); + } + + final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0; + final long dispatchEnd; + Object token = null; + if (observer != null) { + token = observer.messageDispatchStarting(); + } + long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid); + try { + msg.target.dispatchMessage(msg); + if (observer != null) { + observer.messageDispatched(token, msg); + } + dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; + } catch (Exception exception) { + if (observer != null) { + observer.dispatchingThrewException(token, msg, exception); + } + throw exception; + } finally { + ThreadLocalWorkSource.restore(origWorkSource); + if (traceTag != 0) { + Trace.traceEnd(traceTag); + } + } + if (logSlowDelivery) { + if (me.mSlowDeliveryDetected) { + if ((dispatchStart - msg.when) <= 10) { + Slog.w(TAG, "Drained"); + me.mSlowDeliveryDetected = false; + } + } else { + if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery", + msg)) { + // Once we write a slow delivery log, suppress until the queue drains. + me.mSlowDeliveryDetected = true; + } + } + } + if (logSlowDispatch) { + showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg); + } + + if (logging != null) { + logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); + } + + // Make sure that during the course of dispatching the + // identity of the thread wasn't corrupted. + final long newIdent = Binder.clearCallingIdentity(); + if (ident != newIdent) { + Log.wtf(TAG, "Thread identity changed from 0x" + + Long.toHexString(ident) + " to 0x" + + Long.toHexString(newIdent) + " while dispatching to " + + msg.target.getClass().getName() + " " + + msg.callback + " what=" + msg.what); + } + + msg.recycleUnchecked(); + + return true; + } + /** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. @@ -162,7 +266,6 @@ public final class Looper { } me.mInLoop = true; - final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. @@ -177,99 +280,12 @@ public final class Looper { + Thread.currentThread().getName() + ".slow", 0); - boolean slowDeliveryDetected = false; + me.mSlowDeliveryDetected = false; for (;;) { - Message msg = queue.next(); // might block - if (msg == null) { - // No message indicates that the message queue is quitting. + if (!loopOnce(me, ident, thresholdOverride)) { return; } - - // This must be in a local variable, in case a UI event sets the logger - final Printer logging = me.mLogging; - if (logging != null) { - logging.println(">>>>> Dispatching to " + msg.target + " " + - msg.callback + ": " + msg.what); - } - // Make sure the observer won't change while processing a transaction. - final Observer observer = sObserver; - - final long traceTag = me.mTraceTag; - long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; - long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs; - if (thresholdOverride > 0) { - slowDispatchThresholdMs = thresholdOverride; - slowDeliveryThresholdMs = thresholdOverride; - } - final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0); - final boolean logSlowDispatch = (slowDispatchThresholdMs > 0); - - final boolean needStartTime = logSlowDelivery || logSlowDispatch; - final boolean needEndTime = logSlowDispatch; - - if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { - Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); - } - - final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0; - final long dispatchEnd; - Object token = null; - if (observer != null) { - token = observer.messageDispatchStarting(); - } - long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid); - try { - msg.target.dispatchMessage(msg); - if (observer != null) { - observer.messageDispatched(token, msg); - } - dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; - } catch (Exception exception) { - if (observer != null) { - observer.dispatchingThrewException(token, msg, exception); - } - throw exception; - } finally { - ThreadLocalWorkSource.restore(origWorkSource); - if (traceTag != 0) { - Trace.traceEnd(traceTag); - } - } - if (logSlowDelivery) { - if (slowDeliveryDetected) { - if ((dispatchStart - msg.when) <= 10) { - Slog.w(TAG, "Drained"); - slowDeliveryDetected = false; - } - } else { - if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery", - msg)) { - // Once we write a slow delivery log, suppress until the queue drains. - slowDeliveryDetected = true; - } - } - } - if (logSlowDispatch) { - showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg); - } - - if (logging != null) { - logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); - } - - // Make sure that during the course of dispatching the - // identity of the thread wasn't corrupted. - final long newIdent = Binder.clearCallingIdentity(); - if (ident != newIdent) { - Log.wtf(TAG, "Thread identity changed from 0x" - + Long.toHexString(ident) + " to 0x" - + Long.toHexString(newIdent) + " while dispatching to " - + msg.target.getClass().getName() + " " - + msg.callback + " what=" + msg.what); - } - - msg.recycleUnchecked(); } }