Merge "Modern queue: enqueue multiple broadcast receivers"
This commit is contained in:
@@ -87,6 +87,7 @@ import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -140,6 +141,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
// We configure runnable size only once at boot; it'd be too complex to
|
||||
// try resizing dynamically at runtime
|
||||
mRunning = new BroadcastProcessQueue[mConstants.getMaxRunningQueues()];
|
||||
|
||||
// Set up the statistics for batched broadcasts.
|
||||
final int batchSize = mConstants.MAX_BROADCAST_BATCH_SIZE;
|
||||
mReceiverBatch = new BroadcastReceiverBatch(batchSize);
|
||||
Slog.i(TAG, "maximum broadcast batch size " + batchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,12 +208,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
private final BroadcastConstants mBgConstants;
|
||||
|
||||
/**
|
||||
* This single object allows the queue to dispatch receivers using scheduleReceiverList
|
||||
* without constantly allocating new ReceiverInfo objects or ArrayLists. This queue
|
||||
* implementation is known to have a maximum size of one entry.
|
||||
* The sole instance of BroadcastReceiverBatch that is used by scheduleReceiverWarmLocked().
|
||||
* The class is not a true singleton but only one instance is needed for the broadcast queue.
|
||||
* Although this is guarded by mService, it should never be accessed by any other function.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
final BroadcastReceiverBatch mReceiverBatch = new BroadcastReceiverBatch(1);
|
||||
@GuardedBy("mService")
|
||||
final BroadcastReceiverBatch mReceiverBatch;
|
||||
|
||||
/**
|
||||
* Timestamp when last {@link #testAllProcessQueues} failure was observed;
|
||||
@@ -576,7 +583,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
if (queue != null) {
|
||||
// If queue was running a broadcast, fail it
|
||||
if (queue.isActive()) {
|
||||
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
|
||||
finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
|
||||
"onApplicationCleanupLocked");
|
||||
}
|
||||
|
||||
@@ -749,22 +756,25 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
* case where a broadcast is handled by a remote app, and the case where the
|
||||
* broadcast was finished locally without the remote app being involved.
|
||||
*/
|
||||
@GuardedBy("mService")
|
||||
private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
|
||||
checkState(queue.isActive(), "isActive");
|
||||
BroadcastReceiverBatch batch = mReceiverBatch;
|
||||
batch.reset();
|
||||
|
||||
final BroadcastRecord r = queue.getActive();
|
||||
final int index = queue.getActiveIndex();
|
||||
|
||||
if (r.terminalCount == 0) {
|
||||
r.dispatchTime = SystemClock.uptimeMillis();
|
||||
r.dispatchRealTime = SystemClock.elapsedRealtime();
|
||||
r.dispatchClockTime = System.currentTimeMillis();
|
||||
while (collectReceiverList(queue, batch)) {
|
||||
if (batch.isFull()) {
|
||||
break;
|
||||
}
|
||||
if (!shouldContinueScheduling(queue)) {
|
||||
break;
|
||||
}
|
||||
if (queue.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
queue.makeActiveNextPending();
|
||||
}
|
||||
|
||||
if (maybeSkipReceiver(queue, r, index)) {
|
||||
return;
|
||||
}
|
||||
dispatchReceivers(queue, r, index);
|
||||
processReceiverList(queue, batch);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -772,36 +782,36 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
* skipped (and therefore no more work is required).
|
||||
*/
|
||||
private boolean maybeSkipReceiver(@NonNull BroadcastProcessQueue queue,
|
||||
@NonNull BroadcastRecord r, int index) {
|
||||
@NonNull BroadcastReceiverBatch batch, @NonNull BroadcastRecord r, int index) {
|
||||
final int oldDeliveryState = getDeliveryState(r, index);
|
||||
final ProcessRecord app = queue.app;
|
||||
final Object receiver = r.receivers.get(index);
|
||||
|
||||
// If someone already finished this broadcast, finish immediately
|
||||
if (isDeliveryStateTerminal(oldDeliveryState)) {
|
||||
enqueueFinishReceiver(queue, oldDeliveryState, "already terminal state");
|
||||
batch.finish(r, index, oldDeliveryState, "already terminal state");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consider additional cases where we'd want to finish immediately
|
||||
if (app.isInFullBackup()) {
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
|
||||
batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
|
||||
return true;
|
||||
}
|
||||
if (mSkipPolicy.shouldSkip(r, receiver)) {
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
|
||||
batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
|
||||
return true;
|
||||
}
|
||||
final Intent receiverIntent = r.getReceiverIntent(receiver);
|
||||
if (receiverIntent == null) {
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
|
||||
batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ignore registered receivers from a previous PID
|
||||
if ((receiver instanceof BroadcastFilter)
|
||||
&& ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
|
||||
batch.finish(r, index, BroadcastRecord.DELIVERY_SKIPPED,
|
||||
"BroadcastFilter for mismatched PID");
|
||||
return true;
|
||||
}
|
||||
@@ -809,17 +819,144 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect receivers into a list, to be dispatched in a single receiver list call. Return
|
||||
* true if remaining receivers in the queue should be examined, and false if the current list
|
||||
* is complete.
|
||||
*/
|
||||
private boolean collectReceiverList(@NonNull BroadcastProcessQueue queue,
|
||||
@NonNull BroadcastReceiverBatch batch) {
|
||||
final ProcessRecord app = queue.app;
|
||||
final BroadcastRecord r = queue.getActive();
|
||||
final int index = queue.getActiveIndex();
|
||||
final Object receiver = r.receivers.get(index);
|
||||
final Intent receiverIntent = r.getReceiverIntent(receiver);
|
||||
|
||||
if (r.terminalCount == 0) {
|
||||
r.dispatchTime = SystemClock.uptimeMillis();
|
||||
r.dispatchRealTime = SystemClock.elapsedRealtime();
|
||||
r.dispatchClockTime = System.currentTimeMillis();
|
||||
}
|
||||
if (maybeSkipReceiver(queue, batch, r, index)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final IApplicationThread thread = app.getOnewayThread();
|
||||
if (thread == null) {
|
||||
batch.finish(r, index, BroadcastRecord.DELIVERY_FAILURE, "missing IApplicationThread");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (receiver instanceof BroadcastFilter) {
|
||||
batch.schedule(((BroadcastFilter) receiver).receiverList.receiver,
|
||||
receiverIntent, r.resultCode, r.resultData, r.resultExtras,
|
||||
r.ordered, r.initialSticky, r.userId,
|
||||
app.mState.getReportedProcState(), r, index);
|
||||
// TODO: consider making registered receivers of unordered
|
||||
// broadcasts report results to detect ANRs
|
||||
if (!r.ordered) {
|
||||
batch.success(r, index, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered");
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
batch.schedule(receiverIntent, ((ResolveInfo) receiver).activityInfo,
|
||||
null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
|
||||
app.mState.getReportedProcState(), r, index);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the information in a BroadcastReceiverBatch. Elements in the finish and success
|
||||
* lists are sent to enqueueFinishReceiver(). Elements in the receivers list are transmitted
|
||||
* to the target in a single binder call.
|
||||
*/
|
||||
private void processReceiverList(@NonNull BroadcastProcessQueue queue,
|
||||
@NonNull BroadcastReceiverBatch batch) {
|
||||
// Transmit the receiver list.
|
||||
final ProcessRecord app = queue.app;
|
||||
final IApplicationThread thread = app.getOnewayThread();
|
||||
|
||||
batch.recordBatch(thread instanceof SameProcessApplicationThread);
|
||||
|
||||
// Mark all the receivers that were discarded. None of these have actually been scheduled.
|
||||
for (int i = 0; i < batch.finished().size(); i++) {
|
||||
final var finish = batch.finished().get(i);
|
||||
enqueueFinishReceiver(queue, finish.r, finish.index, finish.deliveryState,
|
||||
finish.reason);
|
||||
}
|
||||
// Prepare for delivery of all receivers that are about to be scheduled.
|
||||
for (int i = 0; i < batch.cookies().size(); i++) {
|
||||
final var cookie = batch.cookies().get(i);
|
||||
prepareToDispatch(queue, cookie.r, cookie.index);
|
||||
}
|
||||
|
||||
// Notify on dispatch. Note that receiver/cookies are recorded only if the thread is
|
||||
// non-null and the list will therefore be sent.
|
||||
for (int i = 0; i < batch.cookies().size(); i++) {
|
||||
// Cookies and receivers are 1:1
|
||||
final var cookie = batch.cookies().get(i);
|
||||
final BroadcastRecord r = cookie.r;
|
||||
final int index = cookie.index;
|
||||
final Object receiver = r.receivers.get(index);
|
||||
if (receiver instanceof BroadcastFilter) {
|
||||
notifyScheduleRegisteredReceiver(queue.app, r, (BroadcastFilter) receiver);
|
||||
} else {
|
||||
notifyScheduleReceiver(queue.app, r, (ResolveInfo) receiver);
|
||||
}
|
||||
}
|
||||
|
||||
// Transmit the enqueued receivers. The thread cannot be null because the lock has been
|
||||
// held since collectReceiverList(), which will not add any receivers if the thread is null.
|
||||
boolean remoteFailed = false;
|
||||
if (batch.receivers().size() > 0) {
|
||||
try {
|
||||
thread.scheduleReceiverList(batch.receivers());
|
||||
} catch (RemoteException e) {
|
||||
// Log the failure of the first receiver in the list. Note that there must be at
|
||||
// least one receiver/cookie to reach this point in the code, which means
|
||||
// cookie[0] is a valid element.
|
||||
final var info = batch.cookies().get(0);
|
||||
final BroadcastRecord r = info.r;
|
||||
final int index = info.index;
|
||||
final Object receiver = r.receivers.get(index);
|
||||
final String msg = "Failed to schedule " + r + " to " + receiver
|
||||
+ " via " + app + ": " + e;
|
||||
logw(msg);
|
||||
app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, true);
|
||||
remoteFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!remoteFailed) {
|
||||
// If transmission succeed, report all receivers that are assumed to be delivered.
|
||||
for (int i = 0; i < batch.success().size(); i++) {
|
||||
final var finish = batch.success().get(i);
|
||||
enqueueFinishReceiver(queue, finish.r, finish.index, finish.deliveryState,
|
||||
finish.reason);
|
||||
}
|
||||
} else {
|
||||
// If transmission failed, fail all receivers in the list.
|
||||
for (int i = 0; i < batch.cookies().size(); i++) {
|
||||
final var cookie = batch.cookies().get(i);
|
||||
enqueueFinishReceiver(queue, cookie.r, cookie.index,
|
||||
BroadcastRecord.DELIVERY_FAILURE, "remote app");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this receiver should be assumed to have been delivered.
|
||||
*/
|
||||
private boolean isAssumedDelivered(@NonNull BroadcastRecord r, int index) {
|
||||
private boolean isAssumedDelivered(BroadcastRecord r, int index) {
|
||||
return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered;
|
||||
}
|
||||
|
||||
/**
|
||||
* A receiver is about to be dispatched. Start ANR timers, if necessary.
|
||||
*/
|
||||
private void dispatchReceivers(@NonNull BroadcastProcessQueue queue,
|
||||
private void prepareToDispatch(@NonNull BroadcastProcessQueue queue,
|
||||
@NonNull BroadcastRecord r, int index) {
|
||||
final ProcessRecord app = queue.app;
|
||||
final Object receiver = r.receivers.get(index);
|
||||
@@ -858,43 +995,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
|
||||
setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
|
||||
"scheduleReceiverWarmLocked");
|
||||
|
||||
final IApplicationThread thread = app.getOnewayThread();
|
||||
if (thread != null) {
|
||||
try {
|
||||
final Intent receiverIntent = r.getReceiverIntent(receiver);
|
||||
if (receiver instanceof BroadcastFilter) {
|
||||
notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
|
||||
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
|
||||
((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
|
||||
r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
|
||||
r.userId, app.mState.getReportedProcState()));
|
||||
|
||||
// TODO: consider making registered receivers of unordered
|
||||
// broadcasts report results to detect ANRs
|
||||
if (assumeDelivered) {
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_DELIVERED,
|
||||
"assuming delivered");
|
||||
}
|
||||
} else {
|
||||
notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
|
||||
thread.scheduleReceiverList(mReceiverBatch.manifestReceiver(
|
||||
receiverIntent, ((ResolveInfo) receiver).activityInfo,
|
||||
null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
|
||||
app.mState.getReportedProcState()));
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
final String msg = "Failed to schedule " + r + " to " + receiver
|
||||
+ " via " + app + ": " + e;
|
||||
logw(msg);
|
||||
app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
|
||||
ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app");
|
||||
}
|
||||
} else {
|
||||
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE,
|
||||
"missing IApplicationThread");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -909,7 +1009,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
|
||||
app, OOM_ADJ_REASON_FINISH_RECEIVER);
|
||||
try {
|
||||
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(r.resultTo, r.intent,
|
||||
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
|
||||
r.resultTo, r.intent,
|
||||
r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
|
||||
r.userId, app.mState.getReportedProcState()));
|
||||
} catch (RemoteException e) {
|
||||
@@ -940,7 +1041,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
}
|
||||
|
||||
private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
|
||||
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
|
||||
finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
|
||||
"deliveryTimeoutHardLocked");
|
||||
}
|
||||
|
||||
@@ -973,7 +1074,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
}
|
||||
}
|
||||
|
||||
return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
|
||||
return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -989,7 +1090,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire;
|
||||
}
|
||||
|
||||
private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
|
||||
/**
|
||||
* Terminate all active broadcasts on the queue.
|
||||
*/
|
||||
private boolean finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue,
|
||||
@DeliveryState int deliveryState, @NonNull String reason) {
|
||||
if (!queue.isActive()) {
|
||||
logw("Ignoring finish; no active broadcast for " + queue);
|
||||
@@ -1015,16 +1119,25 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
|
||||
setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
|
||||
|
||||
final boolean early = r != queue.getActive() || index != queue.getActiveIndex();
|
||||
|
||||
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
|
||||
r.anrCount++;
|
||||
if (app != null && !app.isDebugging()) {
|
||||
mService.appNotResponding(queue.app, TimeoutRecord.forBroadcastReceiver(r.intent));
|
||||
}
|
||||
} else {
|
||||
} else if (!early) {
|
||||
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
|
||||
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
|
||||
}
|
||||
|
||||
if (early) {
|
||||
// This is an early receiver that was transmitted as part of a group. The delivery
|
||||
// state has been updated but don't make any further decisions.
|
||||
traceEnd(cookie);
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean res = shouldContinueScheduling(queue);
|
||||
if (res) {
|
||||
// We're on a roll; move onto the next broadcast for this process
|
||||
@@ -1721,6 +1834,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
|
||||
ipw.decreaseIndent();
|
||||
ipw.println();
|
||||
|
||||
ipw.println("Batch statistics:");
|
||||
ipw.increaseIndent();
|
||||
{
|
||||
final var stats = mReceiverBatch.getStatistics();
|
||||
ipw.println("Finished " + Arrays.toString(stats.finish));
|
||||
ipw.println("DispatchedLocal " + Arrays.toString(stats.local));
|
||||
ipw.println("DispatchedRemote " + Arrays.toString(stats.remote));
|
||||
}
|
||||
ipw.decreaseIndent();
|
||||
ipw.println();
|
||||
|
||||
if (dumpConstants) {
|
||||
mConstants.dump(ipw);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user