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

Commit dd35750c authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

BroadcastQueue: implement ANR timeouts.

Detect when an application is wedged when handling a broadcast, report
it as an ANR, and confirm the queues move forward to idle.  Tests to
confirm that both implementations detect ANRs.

Bug: 245771249
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Change-Id: I2707cfd0170fd9efdbda600d2a7623891990b1d4
parent 0060bd42
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -2440,7 +2440,8 @@ public class ActivityManagerService extends IActivityManager.Stub
        if (mEnableModernQueue) {
            mBroadcastQueues = new BroadcastQueue[1];
            mBroadcastQueues[0] = new BroadcastQueueModernImpl(this, mHandler, foreConstants);
            mBroadcastQueues[0] = new BroadcastQueueModernImpl(this, mHandler,
                    foreConstants, backConstants);
        } else {
            mBroadcastQueues = new BroadcastQueue[4];
            mBroadcastQueues[BROADCAST_QUEUE_FG] = new BroadcastQueueImpl(this, mHandler,
@@ -6557,6 +6558,10 @@ public class ActivityManagerService extends IActivityManager.Stub
        }
    }
    void appNotResponding(@NonNull ProcessRecord anrProcess, @NonNull TimeoutRecord timeoutRecord) {
        mAnrHelper.appNotResponding(anrProcess, timeoutRecord);
    }
    void startPersistentApps(int matchFlags) {
        if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) return;
+4 −8
Original line number Diff line number Diff line
@@ -40,26 +40,20 @@ public abstract class BroadcastQueue {

    final @NonNull ActivityManagerService mService;
    final @NonNull Handler mHandler;
    final @NonNull BroadcastConstants mConstants;
    final @NonNull BroadcastSkipPolicy mSkipPolicy;
    final @NonNull BroadcastHistory mHistory;
    final @NonNull String mQueueName;

    BroadcastQueue(@NonNull ActivityManagerService service, @NonNull Handler handler,
            @NonNull String name, @NonNull BroadcastConstants constants,
            @NonNull BroadcastSkipPolicy skipPolicy, @NonNull BroadcastHistory history) {
            @NonNull String name, @NonNull BroadcastSkipPolicy skipPolicy,
            @NonNull BroadcastHistory history) {
        mService = Objects.requireNonNull(service);
        mHandler = Objects.requireNonNull(handler);
        mQueueName = Objects.requireNonNull(name);
        mConstants = Objects.requireNonNull(constants);
        mSkipPolicy = Objects.requireNonNull(skipPolicy);
        mHistory = Objects.requireNonNull(history);
    }

    void start(@NonNull ContentResolver resolver) {
        mConstants.startObserving(mHandler, resolver);
    }

    static void checkState(boolean state, String msg) {
        if (!state) {
            Slog.wtf(TAG, msg, new Throwable());
@@ -75,6 +69,8 @@ public abstract class BroadcastQueue {
        return mQueueName;
    }

    public abstract void start(@NonNull ContentResolver resolver);

    public abstract boolean isDelayBehindServices();

    /**
+6 −3
Original line number Diff line number Diff line
@@ -97,6 +97,8 @@ public class BroadcastQueueImpl extends BroadcastQueue {
    private static final String TAG_MU = TAG + POSTFIX_MU;
    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;

    final BroadcastConstants mConstants;

    /**
     * If true, we can delay broadcasts while waiting services to finish in the previous
     * receiver's process.
@@ -193,14 +195,15 @@ public class BroadcastQueueImpl extends BroadcastQueue {
    BroadcastQueueImpl(ActivityManagerService service, Handler handler,
            String name, BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
            BroadcastHistory history, boolean allowDelayBehindServices, int schedGroup) {
        super(service, handler, name, constants, skipPolicy, history);
        super(service, handler, name, skipPolicy, history);
        mHandler = new BroadcastHandler(handler.getLooper());
        mConstants = constants;
        mDelayBehindServices = allowDelayBehindServices;
        mSchedGroup = schedGroup;
        mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
    }

    void start(ContentResolver resolver) {
    public void start(ContentResolver resolver) {
        mDispatcher.start();
        mConstants.startObserving(mHandler, resolver);
    }
@@ -1657,7 +1660,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {

            // The ANR should only be triggered if we have a process record (app is non-null)
            if (!debugging && app != null) {
                mService.mAnrHelper.appNotResponding(app, timeoutRecord);
                mService.appNotResponding(app, timeoutRecord);
            }

        } finally {
+44 −12
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.IndentingPrintWriter;
@@ -48,6 +49,8 @@ import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.TimeoutRecord;
import com.android.server.am.BroadcastRecord.DeliveryState;

import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -68,15 +71,17 @@ import java.util.concurrent.CountDownLatch;
 */
class BroadcastQueueModernImpl extends BroadcastQueue {
    BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
            BroadcastConstants constants) {
        this(service, handler, constants, new BroadcastSkipPolicy(service),
            BroadcastConstants fgConstants, BroadcastConstants bgConstants) {
        this(service, handler, fgConstants, bgConstants, new BroadcastSkipPolicy(service),
                new BroadcastHistory());
    }

    BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
            BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
            BroadcastHistory history) {
        super(service, handler, "modern", constants, skipPolicy, history);
            BroadcastConstants fgConstants, BroadcastConstants bgConstants,
            BroadcastSkipPolicy skipPolicy, BroadcastHistory history) {
        super(service, handler, "modern", skipPolicy, history);
        mFgConstants = Objects.requireNonNull(fgConstants);
        mBgConstants = Objects.requireNonNull(bgConstants);
        mLocalHandler = new Handler(handler.getLooper(), mLocalCallback);
    }

@@ -151,7 +156,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    @GuardedBy("mService")
    private final ArrayList<CountDownLatch> mWaitingForIdle = new ArrayList<>();

    private final BroadcastConstants mFgConstants;
    private final BroadcastConstants mBgConstants;

    private static final int MSG_UPDATE_RUNNING_LIST = 1;
    private static final int MSG_DELIVERY_TIMEOUT = 2;

    private void enqueueUpdateRunningList() {
        mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -168,6 +177,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                }
                return true;
            }
            case MSG_DELIVERY_TIMEOUT: {
                synchronized (mService) {
                    finishReceiverLocked((BroadcastProcessQueue) msg.obj,
                            BroadcastRecord.DELIVERY_TIMEOUT);
                }
                return true;
            }
        }
        return false;
    };
@@ -418,7 +434,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        final BroadcastRecord r = queue.getActive();
        final Object receiver = queue.getActiveReceiver();

        // TODO: schedule ANR timeout trigger event
        if (!r.timeoutExempt) {
            final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
            mLocalHandler.sendMessageDelayed(
                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout);
        }

        // TODO: apply temp allowlist exemptions
        // TODO: apply background activity launch exemptions

@@ -473,16 +494,26 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
    }

    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue, int deliveryState) {
    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
            @DeliveryState int deliveryState) {
        checkState(queue.isActive(), "isActive");

        queue.setActiveDeliveryState(deliveryState);

        if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
            Slog.w(TAG, "Failed delivery of " + queue.getActive() + " to " + queue);
            Slog.w(TAG, "Delivery state of " + queue.getActive() + " to " + queue + " changed to "
                    + BroadcastRecord.deliveryStateToString(deliveryState));
        }

        queue.setActiveDeliveryState(deliveryState);
        if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
            if (queue.app != null && !queue.app.isDebugging()) {
                mService.appNotResponding(queue.app, TimeoutRecord
                        .forBroadcastReceiver("Broadcast of " + queue.getActive().toShortString()));
            }
        } else {
            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT, queue);
        }

        // TODO: cancel any outstanding ANR timeout
        // TODO: if we're the last receiver of this broadcast, record to history

        // Even if we have more broadcasts, if we've made reasonable progress
@@ -516,8 +547,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    }

    @Override
    void start(@NonNull ContentResolver resolver) {
        super.start(resolver);
    public void start(@NonNull ContentResolver resolver) {
        mFgConstants.startObserving(mHandler, resolver);
        mBgConstants.startObserving(mHandler, resolver);

        mService.registerUidObserver(new UidObserver() {
            @Override
+29 −11
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
@@ -48,6 +49,8 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -83,7 +86,7 @@ final class BroadcastRecord extends Binder {
    final int appOp;        // an app op that is associated with this broadcast
    final BroadcastOptions options; // BroadcastOptions supplied by caller
    final List receivers;   // contains BroadcastFilter and ResolveInfo
    final int[] delivery;   // delivery state of each receiver
    final @DeliveryState int[] delivery;   // delivery state of each receiver
    final long[] scheduledTime; // uptimeMillis when each receiver was scheduled
    final long[] duration;   // duration a receiver took to process broadcast
    IIntentReceiver resultTo; // who receives final result if non-null
@@ -141,6 +144,29 @@ final class BroadcastRecord extends Binder {
    /** Terminal state: failure to dispatch */
    static final int DELIVERY_FAILURE = 5;

    @IntDef(flag = false, prefix = { "DELIVERY_" }, value = {
            DELIVERY_PENDING,
            DELIVERY_DELIVERED,
            DELIVERY_SKIPPED,
            DELIVERY_TIMEOUT,
            DELIVERY_SCHEDULED,
            DELIVERY_FAILURE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface DeliveryState {}

    static @NonNull String deliveryStateToString(@DeliveryState int deliveryState) {
        switch (deliveryState) {
            case DELIVERY_PENDING: return "Pending";
            case DELIVERY_DELIVERED: return "Delivered";
            case DELIVERY_SKIPPED: return "Skipped";
            case DELIVERY_TIMEOUT: return "Timeout";
            case DELIVERY_SCHEDULED: return "Scheduled";
            case DELIVERY_FAILURE: return "Failure";
            default: return Integer.toString(deliveryState);
        }
    }

    ProcessRecord curApp;       // hosting application of current receiver.
    ComponentName curComponent; // the receiver class that is currently running.
    ActivityInfo curReceiver;   // the manifest receiver that is currently running.
@@ -257,15 +283,7 @@ final class BroadcastRecord extends Binder {
        for (int i = 0; i < N; i++) {
            Object o = receivers.get(i);
            pw.print(prefix);
            switch (delivery[i]) {
                case DELIVERY_PENDING:   pw.print("Pending"); break;
                case DELIVERY_DELIVERED: pw.print("Deliver"); break;
                case DELIVERY_SKIPPED:   pw.print("Skipped"); break;
                case DELIVERY_TIMEOUT:   pw.print("Timeout"); break;
                case DELIVERY_SCHEDULED: pw.print("Schedul"); break;
                case DELIVERY_FAILURE:   pw.print("Failure"); break;
                default:                 pw.print("???????"); break;
            }
            pw.print(deliveryStateToString(delivery[i]));
            pw.print(" "); TimeUtils.formatDuration(duration[i], pw);
            pw.print(" #"); pw.print(i); pw.print(": ");
            if (o instanceof BroadcastFilter) {
@@ -514,7 +532,7 @@ final class BroadcastRecord extends Binder {
     * Update the delivery state of the given {@link #receivers} index.
     * Automatically updates any time measurements related to state changes.
     */
    void setDeliveryState(int index, int deliveryState) {
    void setDeliveryState(int index, @DeliveryState int deliveryState) {
        delivery[index] = deliveryState;

        switch (deliveryState) {
Loading