diff options
| -rw-r--r-- | services/core/java/com/android/server/am/BroadcastQueueModernImpl.java | 258 |
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); } |