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

Commit 6843f762 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

BroadcastQueue: clean up cold, empty queues.

When we notice that a process has been killed, and they have no
remaining pending broadcasts, clean up the BroadcastProcessQueue
instance that we had allocated for them, to reduce memory pressure.

Tests to verify.

Bug: 245771249
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Change-Id: Ia0aadbe1d684ea2f5babff0174753aee8d27b55a
parent 19fa890b
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -311,6 +311,10 @@ class BroadcastProcessQueue {
        return mActive.receivers.get(mActiveIndex);
    }

    public boolean isEmpty() {
        return (mActive != null) && mPending.isEmpty();
    }

    public boolean isActive() {
        return mActive != null;
    }
+48 −6
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;

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

@@ -114,8 +115,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    // TODO: pause queues when background services are running
    // TODO: pause queues when processes are frozen

    // TODO: clean up queues for removed apps

    /**
     * Maximum number of process queues to dispatch broadcasts to
     * simultaneously.
@@ -269,6 +268,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        } else if (inQueue) {
            mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
        }

        // If app isn't running, and there's nothing in the queue, clean up
        if (queue.isEmpty() && !queue.isProcessWarm()) {
            removeProcessQueue(queue.processName, queue.uid);
        }
    }

    /**
@@ -420,6 +424,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
                didSomething = true;
            }

            // If queue has nothing else pending, consider cleaning it
            if (queue.isEmpty()) {
                updateRunnableList(queue);
            }
        }

        return didSomething;
@@ -873,11 +882,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                mService.getUidStateLocked(app.uid));
    }

    private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) {
    @VisibleForTesting
    @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) {
        return getOrCreateProcessQueue(app.processName, app.info.uid);
    }

    private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName,
    @VisibleForTesting
    @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName,
            int uid) {
        BroadcastProcessQueue leaf = mProcessQueues.get(uid);
        while (leaf != null) {
@@ -900,11 +911,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        return created;
    }

    private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) {
    @VisibleForTesting
    @Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) {
        return getProcessQueue(app.processName, app.info.uid);
    }

    private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) {
    @VisibleForTesting
    @Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) {
        BroadcastProcessQueue leaf = mProcessQueues.get(uid);
        while (leaf != null) {
            if (Objects.equals(leaf.processName, processName)) {
@@ -915,6 +928,35 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        return null;
    }

    @VisibleForTesting
    @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull ProcessRecord app) {
        return removeProcessQueue(app.processName, app.info.uid);
    }

    @VisibleForTesting
    @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull String processName,
            int uid) {
        BroadcastProcessQueue prev = null;
        BroadcastProcessQueue leaf = mProcessQueues.get(uid);
        while (leaf != null) {
            if (Objects.equals(leaf.processName, processName)) {
                if (prev != null) {
                    prev.processNameNext = leaf.processNameNext;
                } else {
                    if (leaf.processNameNext != null) {
                        mProcessQueues.put(uid, leaf.processNameNext);
                    } else {
                        mProcessQueues.remove(uid);
                    }
                }
                return leaf;
            }
            prev = leaf;
            leaf = leaf.processNameNext;
        }
        return null;
    }

    @Override
    public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
        long token = proto.start(fieldId);
+45 −0
Original line number Diff line number Diff line
@@ -18,6 +18,10 @@ package com.android.server.am;

import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
import static com.android.server.am.BroadcastQueueTest.PACKAGE_BLUE;
import static com.android.server.am.BroadcastQueueTest.PACKAGE_GREEN;
import static com.android.server.am.BroadcastQueueTest.PACKAGE_RED;
import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -41,6 +45,8 @@ import java.util.List;
@SmallTest
@RunWith(MockitoJUnitRunner.class)
public class BroadcastQueueModernImplTest {
    private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;

    @Mock ActivityManagerService mAms;

    @Mock BroadcastProcessQueue mQueue1;
@@ -156,4 +162,43 @@ public class BroadcastQueueModernImplTest {
        assertOrphan(mQueue3);
        assertOrphan(mQueue4);
    }

    @Test
    public void testProcessQueue_Complex() {
        BroadcastProcessQueue red = mImpl.getOrCreateProcessQueue(PACKAGE_RED, TEST_UID);
        BroadcastProcessQueue green = mImpl.getOrCreateProcessQueue(PACKAGE_GREEN, TEST_UID);
        BroadcastProcessQueue blue = mImpl.getOrCreateProcessQueue(PACKAGE_BLUE, TEST_UID);

        assertEquals(PACKAGE_RED, red.processName);
        assertEquals(PACKAGE_GREEN, green.processName);
        assertEquals(PACKAGE_BLUE, blue.processName);

        // Verify that removing middle queue works
        mImpl.removeProcessQueue(PACKAGE_GREEN, TEST_UID);
        assertEquals(red, mImpl.getProcessQueue(PACKAGE_RED, TEST_UID));
        assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID));
        assertEquals(blue, mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID));
        assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));

        // Verify that removing head queue works
        mImpl.removeProcessQueue(PACKAGE_RED, TEST_UID);
        assertNull(mImpl.getProcessQueue(PACKAGE_RED, TEST_UID));
        assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID));
        assertEquals(blue, mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID));
        assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));

        // Verify that removing last queue works
        mImpl.removeProcessQueue(PACKAGE_BLUE, TEST_UID);
        assertNull(mImpl.getProcessQueue(PACKAGE_RED, TEST_UID));
        assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID));
        assertNull(mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID));
        assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));

        // Verify that removing missing doesn't crash
        mImpl.removeProcessQueue(PACKAGE_YELLOW, TEST_UID);

        // Verify that we can start all over again safely
        BroadcastProcessQueue yellow = mImpl.getOrCreateProcessQueue(PACKAGE_YELLOW, TEST_UID);
        assertEquals(yellow, mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID));
    }
}
+10 −10
Original line number Diff line number Diff line
@@ -450,19 +450,19 @@ public class BroadcastQueueTest {
                anyBoolean(), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
    }

    private static final int USER_GUEST = 11;
    static final int USER_GUEST = 11;

    private static final String PACKAGE_RED = "com.example.red";
    private static final String PACKAGE_GREEN = "com.example.green";
    private static final String PACKAGE_BLUE = "com.example.blue";
    private static final String PACKAGE_YELLOW = "com.example.yellow";
    static final String PACKAGE_RED = "com.example.red";
    static final String PACKAGE_GREEN = "com.example.green";
    static final String PACKAGE_BLUE = "com.example.blue";
    static final String PACKAGE_YELLOW = "com.example.yellow";

    private static final String CLASS_RED = "com.example.red.Red";
    private static final String CLASS_GREEN = "com.example.green.Green";
    private static final String CLASS_BLUE = "com.example.blue.Blue";
    private static final String CLASS_YELLOW = "com.example.yellow.Yellow";
    static final String CLASS_RED = "com.example.red.Red";
    static final String CLASS_GREEN = "com.example.green.Green";
    static final String CLASS_BLUE = "com.example.blue.Blue";
    static final String CLASS_YELLOW = "com.example.yellow.Yellow";

    private static int getUidForPackage(String packageName) {
    static int getUidForPackage(@NonNull String packageName) {
        switch (packageName) {
            case PACKAGE_RED: return android.os.Process.FIRST_APPLICATION_UID + 1;
            case PACKAGE_GREEN: return android.os.Process.FIRST_APPLICATION_UID + 2;