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

Commit 0609dc15 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

BroadcastQueue: store "runnable" as linked list.

Our initial strategy was focused on implementation correctness, with
the known overhead of a sorted ArrayList.  This CL is a refinement
of that design that stores the "runnable" queues as a sorted linked
list, along with tests to confirm correctness.

Bug: 245771249
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Test: atest FrameworksMockingServicesTests:BroadcastQueueModernImplTest
Change-Id: Ia431ce2e0505a26a72a82fc2b2bf50c45e41fbef
parent 1f139a5b
Loading
Loading
Loading
Loading
+68 −7
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@ import android.annotation.UptimeMillisLong;
import android.os.UserHandle;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.IndentingPrintWriter;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.SomeArgs;


import java.util.ArrayDeque;
import java.util.ArrayDeque;
@@ -40,7 +41,7 @@ import java.util.ArrayDeque;
 * be dispatched, and a single active broadcast which is currently being
 * be dispatched, and a single active broadcast which is currently being
 * dispatched.
 * dispatched.
 */
 */
class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> {
class BroadcastProcessQueue {
    /**
    /**
     * Default delay to apply to background broadcasts, giving a chance for
     * Default delay to apply to background broadcasts, giving a chance for
     * debouncing of rapidly changing events.
     * debouncing of rapidly changing events.
@@ -61,7 +62,14 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> {
     * Linked list connection to another process under this {@link #uid} which
     * Linked list connection to another process under this {@link #uid} which
     * has a different {@link #processName}.
     * has a different {@link #processName}.
     */
     */
    @Nullable BroadcastProcessQueue next;
    @Nullable BroadcastProcessQueue processNameNext;

    /**
     * Linked list connections to runnable process with lower and higher
     * {@link #getRunnableAt()} times.
     */
    @Nullable BroadcastProcessQueue runnableAtNext;
    @Nullable BroadcastProcessQueue runnableAtPrev;


    /**
    /**
     * Currently known details about the target process; typically undefined
     * Currently known details about the target process; typically undefined
@@ -257,11 +265,64 @@ class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> {
        }
        }
    }
    }


    @Override
    /**
    public int compareTo(BroadcastProcessQueue o) {
     * Insert the given queue into a sorted linked list of "runnable" queues.
        if (mRunnableAtInvalidated) updateRunnableAt();
     *
        if (o.mRunnableAtInvalidated) o.updateRunnableAt();
     * @param head the current linked list head
        return Long.compare(mRunnableAt, o.mRunnableAt);
     * @param item the queue to insert
     * @return a potentially updated linked list head
     */
    @VisibleForTesting
    static @Nullable BroadcastProcessQueue insertIntoRunnableList(
            @Nullable BroadcastProcessQueue head, @NonNull BroadcastProcessQueue item) {
        if (head == null) {
            return item;
        }
        final long itemRunnableAt = item.getRunnableAt();
        BroadcastProcessQueue test = head;
        BroadcastProcessQueue tail = null;
        while (test != null) {
            if (test.getRunnableAt() >= itemRunnableAt) {
                item.runnableAtNext = test;
                item.runnableAtPrev = test.runnableAtPrev;
                if (item.runnableAtNext != null) {
                    item.runnableAtNext.runnableAtPrev = item;
                }
                if (item.runnableAtPrev != null) {
                    item.runnableAtPrev.runnableAtNext = item;
                }
                return (test == head) ? item : head;
            }
            tail = test;
            test = test.runnableAtNext;
        }
        item.runnableAtPrev = tail;
        item.runnableAtPrev.runnableAtNext = item;
        return head;
    }

    /**
     * Remove the given queue from a sorted linked list of "runnable" queues.
     *
     * @param head the current linked list head
     * @param item the queue to remove
     * @return a potentially updated linked list head
     */
    @VisibleForTesting
    static @Nullable BroadcastProcessQueue removeFromRunnableList(
            @Nullable BroadcastProcessQueue head, @NonNull BroadcastProcessQueue item) {
        if (head == item) {
            head = item.runnableAtNext;
        }
        if (item.runnableAtNext != null) {
            item.runnableAtNext.runnableAtPrev = item.runnableAtPrev;
        }
        if (item.runnableAtPrev != null) {
            item.runnableAtPrev.runnableAtNext = item.runnableAtNext;
        }
        item.runnableAtNext = null;
        item.runnableAtPrev = null;
        return head;
    }
    }


    @Override
    @Override
+55 −24
Original line number Original line Diff line number Diff line
@@ -20,6 +20,8 @@ import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;


import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
@@ -111,12 +113,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    private final SparseArray<BroadcastProcessQueue> mProcessQueues = new SparseArray<>();
    private final SparseArray<BroadcastProcessQueue> mProcessQueues = new SparseArray<>();


    /**
    /**
     * Collection of queues which are "runnable". They're sorted by
     * Head of linked list containing queues which are "runnable". They're
     * {@link BroadcastProcessQueue#getRunnableAt()} so that we prefer
     * sorted by {@link BroadcastProcessQueue#getRunnableAt()} so that we prefer
     * dispatching of longer-waiting broadcasts first.
     * dispatching of longer-waiting broadcasts first.
     *
     * @see BroadcastProcessQueue#insertIntoRunnableList
     * @see BroadcastProcessQueue#removeFromRunnableList
     */
     */
    @GuardedBy("mService")
    private BroadcastProcessQueue mRunnableHead = null;
    private final ArrayList<BroadcastProcessQueue> mRunnable = new ArrayList<>();


    /**
    /**
     * Collection of queues which are "running". This will never be larger than
     * Collection of queues which are "running". This will never be larger than
@@ -176,12 +180,29 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
            return;
            return;
        }
        }


        // TODO: better optimize by using insertion sort data structure
        final boolean wantQueue = queue.isRunnable();
        mRunnable.remove(queue);
        final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
        if (queue.isRunnable()) {
                || (queue.runnableAtNext != null);
            mRunnable.add(queue);
        if (wantQueue) {
            if (inQueue) {
                // We're in a good state, but our position within the linked
                // list might need to move based on a runnableAt change
                final boolean prevLower = (queue.runnableAtPrev != null)
                        ? queue.runnableAtPrev.getRunnableAt() <= queue.getRunnableAt() : true;
                final boolean nextHigher = (queue.runnableAtNext != null)
                        ? queue.runnableAtNext.getRunnableAt() >= queue.getRunnableAt() : true;
                if (prevLower && nextHigher) {
                    return;
                } else {
                    mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
                    mRunnableHead = insertIntoRunnableList(mRunnableHead, queue);
                }
            } else {
                mRunnableHead = insertIntoRunnableList(mRunnableHead, queue);
            }
        } else if (inQueue) {
            mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
        }
        }
        mRunnable.sort(null);
    }
    }


    /**
    /**
@@ -196,6 +217,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        int avail = MAX_RUNNING_PROCESS_QUEUES - mRunning.size();
        int avail = MAX_RUNNING_PROCESS_QUEUES - mRunning.size();
        if (avail == 0) return;
        if (avail == 0) return;


        final long now = SystemClock.uptimeMillis();

        // If someone is waiting to go idle, everything is runnable now
        // If someone is waiting to go idle, everything is runnable now
        final boolean waitingForIdle = !mWaitingForIdle.isEmpty();
        final boolean waitingForIdle = !mWaitingForIdle.isEmpty();


@@ -204,9 +227,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
        mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);


        boolean updateOomAdj = false;
        boolean updateOomAdj = false;
        final long now = SystemClock.uptimeMillis();
        BroadcastProcessQueue queue = mRunnableHead;
        for (int i = 0; i < mRunnable.size() && avail > 0; i++) {
        while (queue != null && avail > 0) {
            final BroadcastProcessQueue queue = mRunnable.get(i);
            BroadcastProcessQueue nextQueue = queue.runnableAtNext;
            final long runnableAt = queue.getRunnableAt();
            final long runnableAt = queue.getRunnableAt();


            // If queues beyond this point aren't ready to run yet, schedule
            // If queues beyond this point aren't ready to run yet, schedule
@@ -228,6 +251,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                if (mRunningColdStart == null) {
                if (mRunningColdStart == null) {
                    mRunningColdStart = queue;
                    mRunningColdStart = queue;
                } else {
                } else {
                    // Move to considering next runnable queue
                    queue = nextQueue;
                    continue;
                    continue;
                }
                }
            }
            }
@@ -236,10 +261,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                    + " from runnable to running; process is " + queue.app);
                    + " from runnable to running; process is " + queue.app);


            // Allocate this available permit and start running!
            // Allocate this available permit and start running!
            mRunnable.remove(i);
            mRunning.add(queue);
            mRunning.add(queue);
            avail--;
            avail--;
            i--;

            // Remove ourselves from linked list of runnable things
            mRunnableHead = removeFromRunnableList(mRunnableHead, queue);


            queue.makeActiveNextPending();
            queue.makeActiveNextPending();


@@ -253,6 +279,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {


            mService.enqueueOomAdjTargetLocked(queue.app);
            mService.enqueueOomAdjTargetLocked(queue.app);
            updateOomAdj = true;
            updateOomAdj = true;

            // Move to considering next runnable queue
            queue = nextQueue;
        }
        }


        if (updateOomAdj) {
        if (updateOomAdj) {
@@ -486,7 +515,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                    while (leaf != null) {
                    while (leaf != null) {
                        leaf.setProcessCached(cached);
                        leaf.setProcessCached(cached);
                        updateRunnableList(leaf);
                        updateRunnableList(leaf);
                        leaf = leaf.next;
                        leaf = leaf.processNameNext;
                    }
                    }
                    enqueueUpdateRunningList();
                    enqueueUpdateRunningList();
                }
                }
@@ -496,7 +525,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {


    @Override
    @Override
    public boolean isIdleLocked() {
    public boolean isIdleLocked() {
        return mRunnable.isEmpty() && mRunning.isEmpty();
        return (mRunnableHead == null) && mRunning.isEmpty();
    }
    }


    @Override
    @Override
@@ -521,7 +550,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {


    @Override
    @Override
    public String describeStateLocked() {
    public String describeStateLocked() {
        return mRunnable.size() + " runnable, " + mRunning.size() + " running";
        return mRunning.size() + " running";
    }
    }


    @Override
    @Override
@@ -551,10 +580,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        while (leaf != null) {
        while (leaf != null) {
            if (Objects.equals(leaf.processName, processName)) {
            if (Objects.equals(leaf.processName, processName)) {
                return leaf;
                return leaf;
            } else if (leaf.next == null) {
            } else if (leaf.processNameNext == null) {
                break;
                break;
            }
            }
            leaf = leaf.next;
            leaf = leaf.processNameNext;
        }
        }


        BroadcastProcessQueue created = new BroadcastProcessQueue(processName, uid);
        BroadcastProcessQueue created = new BroadcastProcessQueue(processName, uid);
@@ -563,7 +592,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        if (leaf == null) {
        if (leaf == null) {
            mProcessQueues.put(uid, created);
            mProcessQueues.put(uid, created);
        } else {
        } else {
            leaf.next = created;
            leaf.processNameNext = created;
        }
        }
        return created;
        return created;
    }
    }
@@ -578,7 +607,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
            if (Objects.equals(leaf.processName, processName)) {
            if (Objects.equals(leaf.processName, processName)) {
                return leaf;
                return leaf;
            }
            }
            leaf = leaf.next;
            leaf = leaf.processNameNext;
        }
        }
        return null;
        return null;
    }
    }
@@ -606,7 +635,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
            BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
            BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
            while (leaf != null) {
            while (leaf != null) {
                leaf.dumpLocked(ipw);
                leaf.dumpLocked(ipw);
                leaf = leaf.next;
                leaf = leaf.processNameNext;
            }
            }
        }
        }
        ipw.decreaseIndent();
        ipw.decreaseIndent();
@@ -614,13 +643,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        ipw.println();
        ipw.println();
        ipw.println("🧍 Runnable:");
        ipw.println("🧍 Runnable:");
        ipw.increaseIndent();
        ipw.increaseIndent();
        if (mRunnable.isEmpty()) {
        if (mRunnableHead == null) {
            ipw.println("(none)");
            ipw.println("(none)");
        } else {
        } else {
            for (BroadcastProcessQueue queue : mRunnable) {
            BroadcastProcessQueue queue = mRunnableHead;
            while (queue != null) {
                TimeUtils.formatDuration(queue.getRunnableAt(), now, ipw);
                TimeUtils.formatDuration(queue.getRunnableAt(), now, ipw);
                ipw.print(' ');
                ipw.print(' ');
                ipw.println(queue.toShortString());
                ipw.println(queue.toShortString());
                queue = queue.runnableAtNext;
            }
            }
        }
        }
        ipw.decreaseIndent();
        ipw.decreaseIndent();
+158 −0
Original line number Original line 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 static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;

import android.annotation.NonNull;
import android.os.HandlerThread;
import android.provider.Settings;

import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.List;

@SmallTest
@RunWith(MockitoJUnitRunner.class)
public class BroadcastQueueModernImplTest {
    @Mock ActivityManagerService mAms;

    @Mock BroadcastProcessQueue mQueue1;
    @Mock BroadcastProcessQueue mQueue2;
    @Mock BroadcastProcessQueue mQueue3;
    @Mock BroadcastProcessQueue mQueue4;

    HandlerThread mHandlerThread;
    BroadcastQueueModernImpl mImpl;

    BroadcastProcessQueue mHead;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mHandlerThread = new HandlerThread(getClass().getSimpleName());
        mHandlerThread.start();
        mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
                new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS));

        doReturn(1L).when(mQueue1).getRunnableAt();
        doReturn(2L).when(mQueue2).getRunnableAt();
        doReturn(3L).when(mQueue3).getRunnableAt();
        doReturn(4L).when(mQueue4).getRunnableAt();
    }

    private static void assertOrphan(BroadcastProcessQueue queue) {
        assertNull(queue.runnableAtNext);
        assertNull(queue.runnableAtPrev);
    }

    private static void assertRunnableList(@NonNull List<BroadcastProcessQueue> expected,
            @NonNull BroadcastProcessQueue actualHead) {
        BroadcastProcessQueue test = actualHead;
        final int N = expected.size();
        for (int i = 0; i < N; i++) {
            final BroadcastProcessQueue expectedPrev = (i > 0) ? expected.get(i - 1) : null;
            final BroadcastProcessQueue expectedTest = expected.get(i);
            final BroadcastProcessQueue expectedNext = (i < N - 1) ? expected.get(i + 1) : null;

            assertEquals("prev", expectedPrev, test.runnableAtPrev);
            assertEquals("test", expectedTest, test);
            assertEquals("next", expectedNext, test.runnableAtNext);

            test = test.runnableAtNext;
        }
        if (N == 0) {
            assertNull(actualHead);
        }
    }

    @Test
    public void testRunnableAt_Simple() {
        assertRunnableList(List.of(), mHead);

        mHead = insertIntoRunnableList(mHead, mQueue1);
        assertRunnableList(List.of(mQueue1), mHead);

        mHead = removeFromRunnableList(mHead, mQueue1);
        assertRunnableList(List.of(), mHead);
    }

    @Test
    public void testRunnableAt_InsertLast() {
        mHead = insertIntoRunnableList(mHead, mQueue1);
        mHead = insertIntoRunnableList(mHead, mQueue2);
        mHead = insertIntoRunnableList(mHead, mQueue3);
        mHead = insertIntoRunnableList(mHead, mQueue4);
        assertRunnableList(List.of(mQueue1, mQueue2, mQueue3, mQueue4), mHead);
    }

    @Test
    public void testRunnableAt_InsertFirst() {
        mHead = insertIntoRunnableList(mHead, mQueue4);
        mHead = insertIntoRunnableList(mHead, mQueue3);
        mHead = insertIntoRunnableList(mHead, mQueue2);
        mHead = insertIntoRunnableList(mHead, mQueue1);
        assertRunnableList(List.of(mQueue1, mQueue2, mQueue3, mQueue4), mHead);
    }

    @Test
    public void testRunnableAt_InsertMiddle() {
        mHead = insertIntoRunnableList(mHead, mQueue1);
        mHead = insertIntoRunnableList(mHead, mQueue3);
        mHead = insertIntoRunnableList(mHead, mQueue2);
        assertRunnableList(List.of(mQueue1, mQueue2, mQueue3), mHead);
    }

    @Test
    public void testRunnableAt_Remove() {
        mHead = insertIntoRunnableList(mHead, mQueue1);
        mHead = insertIntoRunnableList(mHead, mQueue2);
        mHead = insertIntoRunnableList(mHead, mQueue3);
        mHead = insertIntoRunnableList(mHead, mQueue4);

        mHead = removeFromRunnableList(mHead, mQueue3);
        assertRunnableList(List.of(mQueue1, mQueue2, mQueue4), mHead);

        mHead = removeFromRunnableList(mHead, mQueue1);
        assertRunnableList(List.of(mQueue2, mQueue4), mHead);

        mHead = removeFromRunnableList(mHead, mQueue4);
        assertRunnableList(List.of(mQueue2), mHead);

        mHead = removeFromRunnableList(mHead, mQueue2);
        assertRunnableList(List.of(), mHead);

        // Verify all links cleaned up during removal
        assertOrphan(mQueue1);
        assertOrphan(mQueue2);
        assertOrphan(mQueue3);
        assertOrphan(mQueue4);
    }
}