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

Commit 9d11e150 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

BroadcastQueue: offer to wait for a barrier.

The current "wait-for-idle" mechanism can get quite bogged down when
the system is under heavy load, leading to test timeouts.  Instead,
some clients are purely interested in knowing that all currently
outstanding broadcasts have been dispatched, so this change offers
a new "wait-for-barrier" mechanism.

Bug: 243656033
Test: atest CtsContentTestCases:BroadcastReceiverTest
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Change-Id: Ie58a4111921b5b6f256904e002fe9fea12fc905a
parent 7cf2f049
Loading
Loading
Loading
Loading
+10 −30
Original line number Diff line number Diff line
@@ -10621,7 +10621,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        if (!onlyHistory && !onlyReceivers && dumpAll) {
            pw.println();
            for (BroadcastQueue queue : mBroadcastQueues) {
                pw.println("  Queue " + queue.toString() + ": " + queue.describeState());
                pw.println("  Queue " + queue.toString() + ": " + queue.describeStateLocked());
            }
            pw.println("  mHandler:");
            mHandler.dump(new PrintWriterPrinter(pw), "    ");
@@ -15209,7 +15209,7 @@ public class ActivityManagerService extends IActivityManager.Stub
    @GuardedBy("this")
    final boolean canGcNowLocked() {
        for (BroadcastQueue q : mBroadcastQueues) {
            if (!q.isIdle()) {
            if (!q.isIdleLocked()) {
                return false;
            }
        }
@@ -17786,35 +17786,15 @@ public class ActivityManagerService extends IActivityManager.Stub
    public void waitForBroadcastIdle(@Nullable PrintWriter pw) {
        enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()");
        while (true) {
            boolean idle = true;
            synchronized (this) {
        for (BroadcastQueue queue : mBroadcastQueues) {
                    if (!queue.isIdle()) {
                        final String msg = "Waiting for queue " + queue + " to become idle...";
                        if (pw != null) {
                            pw.println(msg);
                            pw.println(queue.describeState());
                            pw.flush();
                        }
                        Slog.v(TAG, msg);
                        queue.flush();
                        idle = false;
                    }
            queue.waitForIdle(pw);
        }
    }
            if (idle) {
                final String msg = "All broadcast queues are idle!";
                if (pw != null) {
                    pw.println(msg);
                    pw.flush();
                }
                Slog.v(TAG, msg);
                return;
            } else {
                SystemClock.sleep(1000);
            }
    public void waitForBroadcastBarrier(@Nullable PrintWriter pw) {
        enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
        for (BroadcastQueue queue : mBroadcastQueues) {
            queue.waitForBarrier(pw);
        }
    }
+7 −0
Original line number Diff line number Diff line
@@ -341,6 +341,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
                    return runNoHomeScreen(pw);
                case "wait-for-broadcast-idle":
                    return runWaitForBroadcastIdle(pw);
                case "wait-for-broadcast-barrier":
                    return runWaitForBroadcastBarrier(pw);
                case "compat":
                    return runCompat(pw);
                case "refresh-settings-cache":
@@ -3110,6 +3112,11 @@ final class ActivityManagerShellCommand extends ShellCommand {
        return 0;
    }

    int runWaitForBroadcastBarrier(PrintWriter pw) throws RemoteException {
        mInternal.waitForBroadcastBarrier(pw);
        return 0;
    }

    int runRefreshSettingsCache() throws RemoteException {
        mInternal.refreshSettingsCache();
        return 0;
+27 −11
Original line number Diff line number Diff line
@@ -18,12 +18,10 @@ package com.android.server.am;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
@@ -144,22 +142,40 @@ public abstract class BroadcastQueue {
     * Quickly determine if this queue has broadcasts that are still waiting to
     * be delivered at some point in the future.
     *
     * @see #flush()
     * @see #waitForIdle
     * @see #waitForBarrier
     */
    public abstract boolean isIdle();
    @GuardedBy("mService")
    public abstract boolean isIdleLocked();

    /**
     * Brief summary of internal state, useful for debugging purposes.
     * Wait until this queue becomes completely idle.
     * <p>
     * Any broadcasts waiting to be delivered at some point in the future will
     * be dispatched as quickly as possible.
     * <p>
     * Callers are cautioned that the queue may take a long time to go idle,
     * since running apps can continue sending new broadcasts in perpetuity;
     * consider using {@link #waitForBarrier} instead.
     */
    public abstract @NonNull String describeState();
    public abstract void waitForIdle(@Nullable PrintWriter pw);

    /**
     * Flush any broadcasts still waiting to be delivered, causing them to be
     * delivered as soon as possible.
     *
     * @see #isIdle()
     * Wait until any currently waiting broadcasts have been dispatched.
     * <p>
     * Any broadcasts waiting to be delivered at some point in the future will
     * be dispatched as quickly as possible.
     * <p>
     * Callers are advised that this method will <em>not</em> wait for any
     * future broadcasts that are newly enqueued after being invoked.
     */
    public abstract void waitForBarrier(@Nullable PrintWriter pw);

    /**
     * Brief summary of internal state, useful for debugging purposes.
     */
    public abstract void flush();
    @GuardedBy("mService")
    public abstract @NonNull String describeStateLocked();

    public abstract void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId);

+66 −8
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Set;
import java.util.function.BooleanSupplier;

/**
 * BROADCASTS
@@ -1781,13 +1782,72 @@ public class BroadcastQueueImpl extends BroadcastQueue {
                record.intent == null ? "" : record.intent.getAction());
    }

    public boolean isIdle() {
    public boolean isIdleLocked() {
        return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle()
                && (mPendingBroadcast == null);
    }

    public void flush() {
    public boolean isBeyondBarrierLocked(long barrierTime) {
        // If nothing active, we're beyond barrier
        if (isIdleLocked()) return true;

        // Check if active broadcast is beyond barrier
        final BroadcastRecord active = getActiveBroadcastLocked();
        if (active != null && active.enqueueTime > barrierTime) {
            return true;
        }

        // Check if pending broadcast is beyond barrier
        final BroadcastRecord pending = getPendingBroadcastLocked();
        if (pending != null && pending.enqueueTime > barrierTime) {
            return true;
        }

        return false;
    }

    public void waitForIdle(PrintWriter pw) {
        waitFor(() -> isIdleLocked(), pw, "idle");
    }

    public void waitForBarrier(PrintWriter pw) {
        final long barrierTime = SystemClock.uptimeMillis();
        waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier");
    }

    private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) {
        long lastPrint = 0;
        while (true) {
            synchronized (mService) {
                if (condition.getAsBoolean()) {
                    final String msg = "Queue [" + mQueueName + "] reached " + conditionName
                            + " condition";
                    Slog.v(TAG, msg);
                    if (pw != null) {
                        pw.println(msg);
                        pw.flush();
                    }
                    return;
                }
            }

            // Print at most every second
            final long now = SystemClock.uptimeMillis();
            if (now >= lastPrint + 1000) {
                lastPrint = now;
                final String msg = "Queue [" + mQueueName + "] waiting for " + conditionName
                        + " condition; state is " + describeStateLocked();
                Slog.v(TAG, msg);
                if (pw != null) {
                    pw.println(msg);
                    pw.flush();
                }
            }

            // Push through any deferrals to try meeting our condition
            cancelDeferrals();
            SystemClock.sleep(100);
        }
    }

    // Used by wait-for-broadcast-idle : fast-forward all current deferrals to
@@ -1799,12 +1859,10 @@ public class BroadcastQueueImpl extends BroadcastQueue {
        }
    }

    public String describeState() {
        synchronized (mService) {
    public String describeStateLocked() {
        return mParallelBroadcasts.size() + " parallel; "
                + mDispatcher.describeStateLocked();
    }
    }

    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
        long token = proto.start(fieldId);
+1 −5
Original line number Diff line number Diff line
@@ -298,11 +298,7 @@ public class BroadcastQueueTest {
    }

    private void waitForIdle() throws Exception {
        for (int i = 0; i < 100; i++) {
            if (mQueue.isIdle()) break;
            SystemClock.sleep(100);
        }
        assertTrue(mQueue.isIdle());
        mQueue.waitForIdle(null);
    }

    private void verifyScheduleReceiver(ProcessRecord app, Intent intent) throws Exception {