Commit 85bceaa6 authored by jdduke's avatar jdduke Committed by Commit bot

[Android] Experimental sync barrier detection for tracing

When an Android View is invalidated outside of frame dispatch
(animation/input/draw), a sync barrier may be inserted into the shared
UI thread message loop. This effectively blocks dispatch of all Chrome
tasks posted to the browser UI thread for an entire frame (or more if
the View is continually invalidated).

There are currently no easy or even automated ways to avoid this
untimely invalidation, and judicious coding can only take us so far. As
an intermediate assist in debugging the issue, use reflection and a
crude form of MessageQueue inspection to trace known cases where the
MessageQueue is blocked by a sync barrier.

Note that this detection is not perfect, neither is it exact, but it's a
solid proxy for informing traces about such pipeline stalls.

BUG=407133

Review URL: https://codereview.chromium.org/575103002

Cr-Commit-Position: refs/heads/master@{#296249}
parent 54f6f80c
...@@ -5,10 +5,19 @@ ...@@ -5,10 +5,19 @@
package org.chromium.base; package org.chromium.base;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.MessageQueue;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class SystemMessageHandler extends Handler { class SystemMessageHandler extends Handler {
private static final String TAG = "SystemMessageHandler";
private static final int SCHEDULED_WORK = 1; private static final int SCHEDULED_WORK = 1;
private static final int DELAYED_SCHEDULED_WORK = 2; private static final int DELAYED_SCHEDULED_WORK = 2;
...@@ -16,12 +25,23 @@ class SystemMessageHandler extends Handler { ...@@ -16,12 +25,23 @@ class SystemMessageHandler extends Handler {
private long mMessagePumpDelegateNative = 0; private long mMessagePumpDelegateNative = 0;
private long mDelayedScheduledTimeTicks = 0; private long mDelayedScheduledTimeTicks = 0;
// The following members are used to detect and trace the presence of sync
// barriers in Android's MessageQueue. Note that this detection is
// experimental, temporary and intended only for diagnostic purposes.
private MessageQueue mMessageQueue;
private Field mMessageQueueMessageField;
private Field mMessageTargetField;
private boolean mQueueHasSyncBarrier;
private long mSyncBarrierTraceId;
private SystemMessageHandler(long messagePumpDelegateNative) { private SystemMessageHandler(long messagePumpDelegateNative) {
mMessagePumpDelegateNative = messagePumpDelegateNative; mMessagePumpDelegateNative = messagePumpDelegateNative;
} tryEnableSyncBarrierDetection();
}
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
updateWhetherQueueHasBlockingSyncBarrier();
if (msg.what == DELAYED_SCHEDULED_WORK) { if (msg.what == DELAYED_SCHEDULED_WORK) {
mDelayedScheduledTimeTicks = 0; mDelayedScheduledTimeTicks = 0;
} }
...@@ -31,6 +51,8 @@ class SystemMessageHandler extends Handler { ...@@ -31,6 +51,8 @@ class SystemMessageHandler extends Handler {
@SuppressWarnings("unused") @SuppressWarnings("unused")
@CalledByNative @CalledByNative
private void scheduleWork() { private void scheduleWork() {
updateWhetherQueueHasBlockingSyncBarrier();
if (mQueueHasSyncBarrier) TraceEvent.instant("SystemMessageHandler:immediateWorkBlocked");
sendEmptyMessage(SCHEDULED_WORK); sendEmptyMessage(SCHEDULED_WORK);
} }
...@@ -41,16 +63,99 @@ class SystemMessageHandler extends Handler { ...@@ -41,16 +63,99 @@ class SystemMessageHandler extends Handler {
removeMessages(DELAYED_SCHEDULED_WORK); removeMessages(DELAYED_SCHEDULED_WORK);
} }
mDelayedScheduledTimeTicks = delayedTimeTicks; mDelayedScheduledTimeTicks = delayedTimeTicks;
updateWhetherQueueHasBlockingSyncBarrier();
if (mQueueHasSyncBarrier) TraceEvent.instant("SystemMessageHandler:delayedWorkBlocked");
sendEmptyMessageDelayed(DELAYED_SCHEDULED_WORK, millis); sendEmptyMessageDelayed(DELAYED_SCHEDULED_WORK, millis);
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
@CalledByNative @CalledByNative
private void removeAllPendingMessages() { private void removeAllPendingMessages() {
updateWhetherQueueHasBlockingSyncBarrier();
removeMessages(SCHEDULED_WORK); removeMessages(SCHEDULED_WORK);
removeMessages(DELAYED_SCHEDULED_WORK); removeMessages(DELAYED_SCHEDULED_WORK);
} }
private void updateWhetherQueueHasBlockingSyncBarrier() {
if (mMessageQueue == null) return;
// As barrier detection is only used for tracing, early out when tracing
// is disabled to avoid any potential performance penalties.
if (!TraceEvent.enabled()) {
mQueueHasSyncBarrier = false;
return;
}
Message queueHead = (Message)getField(mMessageQueue, mMessageQueueMessageField);
setqueueHasSyncBarrier(isSyncBarrierMessage(queueHead));
}
private boolean isSyncBarrierMessage(Message message) {
if (message == null) return false;
// Sync barrier messages have null targets.
return getField(message, mMessageTargetField) == null;
}
private void tryEnableSyncBarrierDetection() {
assert mMessageQueue == null;
boolean success = false;
try {
Method getQueueMethod = Looper.class.getMethod("getQueue", new Class[]{});
mMessageQueue = (MessageQueue)getQueueMethod.invoke(getLooper());
mMessageQueueMessageField = mMessageQueue.getClass().getDeclaredField("mMessages");
mMessageQueueMessageField.setAccessible(true);
mMessageTargetField = Message.class.getDeclaredField("target");
mMessageTargetField.setAccessible(true);
mSyncBarrierTraceId = hashCode();
success = true;
} catch (NoSuchMethodException e) {
Log.e(TAG, "Failed to load method: " + e);
} catch (NoSuchFieldException e) {
Log.e(TAG, "Failed to load field: " + e);
} catch (InvocationTargetException e) {
Log.e(TAG, "Failed invocation: " + e);
} catch (IllegalAccessException e) {
Log.e(TAG, "Illegal access to reflected invocation: " + e);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Illegal argument to reflected invocation: " + e);
} catch (RuntimeException e) {
Log.e(TAG, e.toString());
} finally {
if (!success) disableSyncBarrierDetection();
}
}
private void disableSyncBarrierDetection() {
Log.e(TAG, "Unexpected error with sync barrier detection, disabling.");
mMessageQueue = null;
mMessageQueueMessageField = null;
mMessageTargetField = null;
setqueueHasSyncBarrier(false);
}
private void setqueueHasSyncBarrier(boolean queueHasSyncBarrier) {
if (queueHasSyncBarrier == mQueueHasSyncBarrier) return;
mQueueHasSyncBarrier = queueHasSyncBarrier;
if (mQueueHasSyncBarrier) {
TraceEvent.startAsync("SyncBarrier", mSyncBarrierTraceId);
} else {
TraceEvent.finishAsync("SyncBarrier", mSyncBarrierTraceId);
}
}
private Object getField(Object object, Field field) {
try {
return field.get(object);
} catch (IllegalAccessException e) {
Log.e(TAG, "Failed field access: " + e);
disableSyncBarrierDetection();
}
return null;
}
@CalledByNative @CalledByNative
private static SystemMessageHandler create(long messagePumpDelegateNative) { private static SystemMessageHandler create(long messagePumpDelegateNative) {
return new SystemMessageHandler(messagePumpDelegateNative); return new SystemMessageHandler(messagePumpDelegateNative);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment