Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d1c07481 authored by Lee Shombert's avatar Lee Shombert Committed by Android (Google) Code Review
Browse files

Merge "Modern queue: enqueue multiple broadcast receivers"

parents aa59953a ace585b4
Loading
Loading
Loading
Loading
+191 −67
Original line number Diff line number Diff line
@@ -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 (maybeSkipReceiver(queue, r, index)) {
            return;
            if (!shouldContinueScheduling(queue)) {
                break;
             }
        dispatchReceivers(queue, r, index);
            if (queue.isEmpty()) {
                break;
            }
            queue.makeActiveNextPending();
        }
        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);
        }