summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java258
1 files changed, 191 insertions, 67 deletions
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index d8e99921f64f..a850c8aac21e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -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();
- }
-
- if (maybeSkipReceiver(queue, r, index)) {
- return;
+ while (collectReceiverList(queue, batch)) {
+ if (batch.isFull()) {
+ break;
+ }
+ if (!shouldContinueScheduling(queue)) {
+ break;
+ }
+ if (queue.isEmpty()) {
+ break;
+ }
+ queue.makeActiveNextPending();
}
- 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;
}
@@ -810,16 +820,143 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
/**
+ * 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);
}