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

Commit c7886e98 authored by Lee Shombert's avatar Lee Shombert
Browse files

BroadcastQueue now uses scheduleReceiverList

Bug: 253906553

Convert the broadcast queues to the new scheduleReceiverList API in
IApplicationThread.  The changes are as follows:

 1. The ReceiverBatch class is created to handle the arguments to
    scheduleReceiverList.  The class has additional functionality in
    preparation for allowing batches of more than one receiver.  The
    ReceiverBatch class is essentially bookkeeping: it maintains lists
    of information for a client.

    The ReceiverBatch class has a single() method that does everything
    necessary to dispatch a single manifest or registered receiver.

    In normal operation, the ReceiverBatch class uses object pools and
    does not do any allocation after it has been instantiated.  There
    is a test mode that always allocates new objects.  See the notes
    on BroadcastQueueTest.

 2. BroadcastQueueImpl and BroadcastQueueModernImpl each contain an
    instance of ReceiverBatch, size 1, and use that instance to invoke
    scheduleReceiverList.

 3. BroadcastQueueTest is modified to test against the new thread
    API.  There are changes in the support methods in the class.  The
    intent is that the actual test code looks like a textual rewrite
    rather than a logical rewrite.

 4. BroadcastConstants includes a MAX_BROADCAST_BATCH_SIZE, which is
    set to 1.  This is not required for this CL but is a stake in the
    ground to minimize future merge conflicts.  This constant will be
    increased when the modern queue supports more than one receiver in
    a batch.

 5. BroadcastRecord has two new per-receiver attributes: the transmit
    group and the transmit order.  These are initialized in the
    ReceiverBatch.recordBatch() method (which is not called in this
    commit).  Again, this is a stake in the ground to minimize future
    merge conflicts.

There is no behavioral change in this commit.

Test: atest
 * FrameworksMockingServicesTests:BroadcastQueueTest

Change-Id: I543092cdd68a979fb466a2156c251c6c4f024b61
parent fc65cabd
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -236,6 +236,15 @@ public class BroadcastConstants {
    private static final int DEFAULT_MAX_HISTORY_SUMMARY_SIZE =
            ActivityManager.isLowRamDeviceStatic() ? 25 : 300;

    /**
     * For {@link BroadcastQueueModernImpl}: Maximum number of broadcast receivers to process in a
     * single synchronized block.  Up to this many messages may be dispatched in a single binder
     * call.  Set this to 1 (or zero) for pre-batch behavior.
     */
    public int MAX_BROADCAST_BATCH_SIZE = DEFAULT_MAX_BROADCAST_BATCH_SIZE;
    private static final String KEY_MAX_BROADCAST_BATCH_SIZE = "bcast_max_batch_size";
    private static final int DEFAULT_MAX_BROADCAST_BATCH_SIZE = 1;

    // Settings override tracking for this instance
    private String mSettingsKey;
    private SettingsObserver mSettingsObserver;
@@ -373,6 +382,8 @@ public class BroadcastConstants {
                    DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
            MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
                    DEFAULT_MAX_HISTORY_SUMMARY_SIZE);
            MAX_BROADCAST_BATCH_SIZE = getDeviceConfigInt(KEY_MAX_BROADCAST_BATCH_SIZE,
                    DEFAULT_MAX_BROADCAST_BATCH_SIZE);
        }
    }

@@ -418,6 +429,7 @@ public class BroadcastConstants {
                    MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
            pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
                    MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
            pw.print(KEY_MAX_BROADCAST_BATCH_SIZE, MAX_BROADCAST_BATCH_SIZE).println();
            pw.decreaseIndent();
            pw.println();
        }
+15 −4
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ import android.util.Slog;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
@@ -186,6 +187,14 @@ public class BroadcastQueueImpl extends BroadcastQueue {
        }
    }

    /**
     * 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.
     */
    @VisibleForTesting
    final BroadcastReceiverBatch mReceiverBatch = new BroadcastReceiverBatch(1);

    BroadcastQueueImpl(ActivityManagerService service, Handler handler,
            String name, BroadcastConstants constants, boolean allowDelayBehindServices,
            int schedGroup) {
@@ -388,10 +397,11 @@ public class BroadcastQueueImpl extends BroadcastQueue {
                    + ": " + r);
            mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
                                      PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
            thread.scheduleReceiver(prepareReceiverIntent(r.intent, r.curFilteredExtras),
            thread.scheduleReceiverList(mReceiverBatch.manifestReceiver(
                    prepareReceiverIntent(r.intent, r.curFilteredExtras),
                    r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
                    r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
                    app.mState.getReportedProcState());
                    app.mState.getReportedProcState()));
            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                    "Process cur broadcast " + r + " DELIVERED for app " + app);
            started = true;
@@ -725,9 +735,10 @@ public class BroadcastQueueImpl extends BroadcastQueue {
                // If we have an app thread, do the call through that so it is
                // correctly ordered with other one-way calls.
                try {
                    thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                    thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
                            receiver, intent, resultCode,
                            data, extras, ordered, sticky, sendingUser,
                            app.mState.getReportedProcState());
                            app.mState.getReportedProcState()));
                } catch (RemoteException ex) {
                    // Failed to call into the process. It's either dying or wedged. Kill it gently.
                    synchronized (mService) {
+21 −10
Original line number Diff line number Diff line
@@ -201,6 +201,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    private final BroadcastConstants mFgConstants;
    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.
     */
    @VisibleForTesting
    final BroadcastReceiverBatch mReceiverBatch = new BroadcastReceiverBatch(1);

    /**
     * Timestamp when last {@link #testAllProcessQueues} failure was observed;
     * used for throttling log messages.
@@ -763,7 +771,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
     * Examine a receiver and possibly skip it.  The method returns true if the receiver is
     * skipped (and therefore no more work is required).
     */
    private boolean maybeSkipReceiver(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
    private boolean maybeSkipReceiver(@NonNull BroadcastProcessQueue queue,
            @NonNull BroadcastRecord r, int index) {
        final int oldDeliveryState = getDeliveryState(r, index);
        final ProcessRecord app = queue.app;
        final Object receiver = r.receivers.get(index);
@@ -803,14 +812,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    /**
     * Return true if this receiver should be assumed to have been delivered.
     */
    private boolean isAssumedDelivered(BroadcastRecord r, int index) {
    private boolean isAssumedDelivered(@NonNull 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(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
    private void dispatchReceivers(@NonNull BroadcastProcessQueue queue,
            @NonNull BroadcastRecord r, int index) {
        final ProcessRecord app = queue.app;
        final Object receiver = r.receivers.get(index);

@@ -849,16 +859,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
                "scheduleReceiverWarmLocked");

        final Intent receiverIntent = r.getReceiverIntent(receiver);
        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.scheduleRegisteredReceiver(
                    thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
                            ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
                            r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
                            r.userId, app.mState.getReportedProcState());
                            r.userId, app.mState.getReportedProcState()));

                    // TODO: consider making registered receivers of unordered
                    // broadcasts report results to detect ANRs
@@ -868,9 +878,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                    }
                } else {
                    notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
                    thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
                    thread.scheduleReceiverList(mReceiverBatch.manifestReceiver(
                            receiverIntent, ((ResolveInfo) receiver).activityInfo,
                            null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
                            app.mState.getReportedProcState());
                            app.mState.getReportedProcState()));
                }
            } catch (RemoteException e) {
                final String msg = "Failed to schedule " + r + " to " + receiver
@@ -898,9 +909,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
                    app, OOM_ADJ_REASON_FINISH_RECEIVER);
            try {
                thread.scheduleRegisteredReceiver(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());
                        r.userId, app.mState.getReportedProcState()));
            } catch (RemoteException e) {
                final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
                logw(msg);
+346 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.am;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ReceiverInfo;
import android.content.Intent;
import android.content.IIntentReceiver;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
import android.os.Bundle;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * A batch of receiver instructions. This includes a list of finish requests and a list of
 * receivers.  The instructions are for a single queue.  It is constructed and consumed in a single
 * call to {@link BroadcastQueueModernImpl#scheduleReceiverWarmLocked}.  The list size is bounded by
 * {@link BroadcastConstants#MAX_BROADCAST_BATCH_SIZE}.  Because this class is ephemeral and its use
 * is bounded, it is pre-allocated to avoid allocating new objects every time it is used.
 *
 * The {@link #single} methods support the use of this class in {@link BroadcastQueueImpl}.  These
 * methods simplify the use of {@link IApplicationThread#scheduleReceiverList} as a replacement
 * for scheduleReceiver and scheduleRegisteredReceiver.
 *
 * This class is designed to be allocated once and constantly reused (call {@link #reset} between
 * uses).  Objects needed by the instance are kept in a pool - all object allocation occurs when
 * the instance is created, and no allocation occurs thereafter.  However, if the variable
 * mDeepReceiverCopy is set true (it is false by default) then the method {@link #receivers} always
 * returns newly allocated objects.  This is required for mock testing.
 *
 * This class is not thread-safe.  Instances must be protected by the caller.
 * @hide
 */
final class BroadcastReceiverBatch {

    /**
     * If this is true then receivers() returns a deep copy of the ReceiveInfo array.  If this is
     * false, receivers() returns a reference to the array.  A deep copy is needed only for the
     * broadcast queue mocking tests.
     */
    @VisibleForTesting
    boolean mDeepReceiverCopy = false;

    /**
     * A private pool implementation this class.
     */
    private static class Pool<T> {
        final int size;
        final ArrayList<T> pool;
        int next;
        Pool(int n, @NonNull Class<T> c) {
            size = n;
            pool = new ArrayList<>(size);
            try {
                for (int i = 0; i < size; i++) {
                    pool.add(c.getDeclaredConstructor().newInstance());
                }
            } catch (Exception e) {
                // This class is only used locally.  Turn any exceptions into something fatal.
                throw new RuntimeException(e);
            }
        }
        T next() {
            return pool.get(next++);
        }
        void reset() {
            next = 0;
        }
    }

    /**
     * The information needed to finish off a receiver.  This is valid only in the context of
     * a queue.
     */
    static class FinishInfo {
        BroadcastRecord r = null;
        int index = 0;
        int deliveryState = 0;
        String reason = null;
        FinishInfo set(@Nullable BroadcastRecord r, int index,
                int deliveryState, @Nullable String reason) {
            this.r = r;
            this.index = index;
            this.deliveryState = deliveryState;
            this.reason = reason;
            return this;
        }
    }

    /**
     * The information needed to recreate a receiver info.  The broadcast record can be null if the
     * caller does not expect to need it later.
     */
    static class ReceiverCookie {
        BroadcastRecord r = null;
        int index = 0;
        ReceiverCookie set(@Nullable BroadcastRecord r, int index) {
            this.r = r;
            this.index = index;
            return this;
        }
    }

    // The object pools.
    final int mSize;
    private final Pool<ReceiverInfo> receiverPool;
    private final Pool<FinishInfo> finishPool;
    private final Pool<ReceiverCookie> cookiePool;

    // The accumulated data.  The receivers should be an ArrayList to be directly compatible
    // with scheduleReceiverList().  The receivers array is not final because a new array must
    // be created for every new call to scheduleReceiverList().
    private final ArrayList<ReceiverInfo> mReceivers;
    private final ArrayList<ReceiverCookie> mCookies;
    private final ArrayList<FinishInfo> mFinished;
    // The list of finish records to complete if the binder succeeds or fails.
    private final ArrayList<FinishInfo> mSuccess;

    BroadcastReceiverBatch(int size) {
        mSize = size;
        mReceivers = new ArrayList<>(mSize);
        mCookies = new ArrayList<>(mSize);
        mFinished = new ArrayList<>(mSize);
        mSuccess = new ArrayList<>(mSize);

        receiverPool = new Pool<>(mSize, ReceiverInfo.class);
        finishPool = new Pool<>(mSize, FinishInfo.class);
        cookiePool = new Pool<>(mSize, ReceiverCookie.class);
        mStats = new Statistics(mSize);
        reset();
    }

    void reset() {
        mReceivers.clear();
        mCookies.clear();
        mFinished.clear();
        mSuccess.clear();

        receiverPool.reset();
        finishPool.reset();
        cookiePool.reset();
    }

    void finish(@Nullable BroadcastRecord r, int index,
            int deliveryState, @Nullable String reason) {
        mFinished.add(finishPool.next().set(r, index, deliveryState, reason));
    }
    void success(@Nullable BroadcastRecord r, int index,
            int deliveryState, @Nullable String reason) {
        mSuccess.add(finishPool.next().set(r, index, deliveryState, reason));
    }
    // Add a ReceiverInfo for a registered receiver.
    void schedule(@Nullable IIntentReceiver receiver, Intent intent,
            int resultCode, @Nullable String data, @Nullable Bundle extras, boolean ordered,
            boolean sticky, int sendingUser, int processState,
            @Nullable BroadcastRecord r, int index) {
        ReceiverInfo ri = new ReceiverInfo();
        ri.intent = intent;
        ri.data = data;
        ri.extras = extras;
        ri.sendingUser = sendingUser;
        ri.processState = processState;
        ri.resultCode = resultCode;
        ri.registered = true;
        ri.receiver = receiver;
        ri.ordered = ordered;
        ri.sticky = sticky;
        mReceivers.add(ri);
        mCookies.add(cookiePool.next().set(r, index));
    }
    // Add a ReceiverInfo for a manifest receiver.
    void schedule(@Nullable Intent intent, @Nullable ActivityInfo activityInfo,
            @Nullable CompatibilityInfo compatInfo, int resultCode, @Nullable String data,
            @Nullable Bundle extras, boolean sync, int sendingUser, int processState,
            @Nullable BroadcastRecord r, int index) {
        ReceiverInfo ri = new ReceiverInfo();
        ri.intent = intent;
        ri.data = data;
        ri.extras = extras;
        ri.sendingUser = sendingUser;
        ri.processState = processState;
        ri.resultCode = resultCode;
        ri.registered = false;
        ri.activityInfo = activityInfo;
        ri.compatInfo = compatInfo;
        ri.sync = sync;
        mReceivers.add(ri);
        mCookies.add(cookiePool.next().set(r, index));
    }

    /**
     * Two convenience functions for dispatching a single receiver.  The functions start with a
     * reset.  Then they create the ReceiverInfo array and return it.  Statistics are not
     * collected.
     */
    ArrayList<ReceiverInfo> registeredReceiver(@Nullable IIntentReceiver receiver,
            @Nullable Intent intent, int resultCode, @Nullable String data,
            @Nullable Bundle extras, boolean ordered, boolean sticky,
            int sendingUser, int processState) {
        reset();
        schedule(receiver, intent, resultCode, data, extras, ordered, sticky,
                sendingUser, processState, null, 0);
        return receivers();
    }

    ArrayList<ReceiverInfo> manifestReceiver(@Nullable Intent intent,
            @Nullable ActivityInfo activityInfo, @Nullable CompatibilityInfo compatInfo,
            int resultCode, @Nullable String data, @Nullable Bundle extras, boolean sync,
            int sendingUser, int processState) {
        reset();
        schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync,
                sendingUser, processState, null, 0);
        return receivers();
    }

    // Return true if the batch is full.  Adding any more entries will throw an exception.
    boolean isFull() {
        return (mFinished.size() + mReceivers.size()) >= mSize;
    }
    int finishCount() {
        return mFinished.size() + mSuccess.size();
    }
    int receiverCount() {
        return mReceivers.size();
    }

    /**
     * Create a deep copy of the receiver list.  This is only for testing which is confused when
     * objects are reused.
     */
    private ArrayList<ReceiverInfo> copyReceiverInfo() {
        ArrayList<ReceiverInfo> copy = new ArrayList<>();
        for (int i = 0; i < mReceivers.size(); i++) {
            final ReceiverInfo r = mReceivers.get(i);
            final ReceiverInfo n = new ReceiverInfo();
            n.intent = r.intent;
            n.data = r.data;
            n.extras = r.extras;
            n.sendingUser = r.sendingUser;
            n.processState = r.processState;
            n.resultCode = r.resultCode;
            n.registered = r.registered;
            n.receiver = r.receiver;
            n.ordered = r.ordered;
            n.sticky = r.sticky;
            n.activityInfo = r.activityInfo;
            n.compatInfo = r.compatInfo;
            n.sync = r.sync;
            copy.add(n);
        }
        return copy;
    }

    /**
     * Accessors for the accumulated instructions.  The important accessor is receivers(), since
     * it can be modified to return a deep copy of the mReceivers array.
     */
    @NonNull
    ArrayList<ReceiverInfo> receivers() {
        if (!mDeepReceiverCopy) {
            return mReceivers;
        } else {
            return copyReceiverInfo();
        }
    }
    @NonNull
    ArrayList<ReceiverCookie> cookies() {
        return mCookies;
    }
    @NonNull
    ArrayList<FinishInfo> finished() {
        return mFinished;
    }
    @NonNull
    ArrayList<FinishInfo> success() {
        return mSuccess;
    }

    /**
     * A simple POD for statistics.  The parameter is the size of the BroadcastReceiverBatch.
     */
    static class Statistics {
        final int[] finish;
        final int[] local;
        final int[] remote;
        Statistics(int size) {
            finish = new int[size+1];
            local = new int[size+1];
            remote = new int[size+1];
        }
    }

    private final Statistics mStats;

    /**
     * A unique counter that identifies individual transmission groups.  This is only used for
     * debugging.  It is used to determine which receivers were sent in the same batch, and in
     * which order.  This is static to distinguish between batches across all queues in the
     * system.
     */
    private static final AtomicInteger sTransmitGroup = new AtomicInteger(0);

    /**
     * Record statistics for this batch of instructions.  This updates the local statistics and it
     * updates the transmitGroup and transmitOrder fields of the BroadcastRecords being
     * dispatched.
     */
    void recordBatch(boolean local) {
        final int group = sTransmitGroup.addAndGet(1);
        for (int i = 0; i < cookies().size(); i++) {
            final var cookie = cookies().get(i);
            cookie.r.transmitGroup[cookie.index] = group;
            cookie.r.transmitOrder[cookie.index] = i;
        }
        mStats.finish[finishCount()]++;
        if (local) {
            mStats.local[receiverCount()]++;
        } else {
            mStats.remote[receiverCount()]++;
        }
    }

    @NonNull
    Statistics getStatistics() {
        return mStats;
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -113,6 +113,8 @@ final class BroadcastRecord extends Binder {
    @UptimeMillisLong       long finishTime;         // when broadcast finished
    final @UptimeMillisLong long[] scheduledTime;    // when each receiver was scheduled
    final @UptimeMillisLong long[] terminalTime;     // when each receiver was terminal
    final                   int[] transmitGroup;     // the batch group for each receiver
    final                   int[] transmitOrder;     // the position of the receiver in the group
    final boolean timeoutExempt;  // true if this broadcast is not subject to receiver timeouts
    int resultCode;         // current result code value.
    @Nullable String resultData;      // current result data value.
@@ -380,6 +382,8 @@ final class BroadcastRecord extends Binder {
        blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
        scheduledTime = new long[delivery.length];
        terminalTime = new long[delivery.length];
        transmitGroup = new int[delivery.length];
        transmitOrder = new int[delivery.length];
        resultToApp = _resultToApp;
        resultTo = _resultTo;
        resultCode = _resultCode;
@@ -433,6 +437,8 @@ final class BroadcastRecord extends Binder {
        blockedUntilTerminalCount = from.blockedUntilTerminalCount;
        scheduledTime = from.scheduledTime;
        terminalTime = from.terminalTime;
        transmitGroup = from.transmitGroup;
        transmitOrder = from.transmitOrder;
        resultToApp = from.resultToApp;
        resultTo = from.resultTo;
        enqueueTime = from.enqueueTime;
Loading