Modern queue: enqueue multiple broadcast receivers
Bug: 253906553 Dispatch multiple broadcast receivers to the same process in a single binder call. This change only applies to the modern queue. The strategy is to collect a list of receiver instructions in the ReceiverBatch object, and then process those instructions. This change never dispatches more than one receiver in a single call, which means the behavior of the queue has not been changed. A later commit will update the maximum number of receivers that can be sent in a single call. That change will modify the maximum batch size in BroadcastConstants. The two "testDead" tests in the BroadcastQueueTest have been modified to ensure the first broadcast is processed before the next broadcast is enqueued. This avoids the situation in which the modern queue tries to send a batch of receivers in a single message, failing both broadcasts in the test method at the same time. Test: atest * FrameworksMockingServicesTests:BroadcastQueueTest Change-Id: I4f07df2a2edc16a85b9cfb06451429950abdf1fc
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
|
||||
@@ -1705,6 +1818,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