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

Commit d9dc7dd2 authored by Michael Wachenschwanz's avatar Michael Wachenschwanz
Browse files

Switch activity state pushing to ConcurrentLinkedQueue

Activity state changes are time sensitive and updating
ProcessStateController too slowly can cause unexpected behavior.
Migrating the activity state pushing from being posted on a Handler
thread to a ConcurrentLinkedQueue enables ProcessStateController to
oppurtunistically handle the state changes without waiting for the
associated update.

Example of bad behavior due to slowness:
The CameraEvictionTest#testCamera2AccessCallbackInSplitMode test fails
because it has some fairly tight expectation about CameraAccessPriority
timeliness (which is based off of ProcState and OomScore changes)

Flag: com.android.server.am.push_activity_state_to_oomadjuster
Bug: 401350380
Test: atest MockingOomAdjusterTests
Test: atest ProcessStateControllerTest
Test: atest android.hardware.multiprocess.camera.cts.CameraEvictionTest#testCamera2AccessCallbackInSplitMode

Change-Id: Ia50a98fb5c51b386f199f5f689bf8a3fe155e607
parent 26e2563a
Loading
Loading
Loading
Loading
+54 −18
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.android.server.wm.WindowProcessController;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

@@ -65,6 +66,11 @@ public class ProcessStateController {

    private final GlobalState mGlobalState = new GlobalState();

    /**
     * Queue for staging asynchronous events. The queue will be drained before each update.
     */
    private final ConcurrentLinkedQueue<Runnable> mStagingQueue = new ConcurrentLinkedQueue<>();

    private ProcessStateController(ActivityManagerService ams, ProcessList processList,
            ActiveUids activeUids, ServiceThread handlerThread,
            CachedAppOptimizer cachedAppOptimizer, Object lock, Object procLock,
@@ -115,7 +121,7 @@ public class ProcessStateController {
     */
    @GuardedBy("mLock")
    public boolean runUpdate(@NonNull ProcessRecord proc, @OomAdjReason int oomAdjReason) {
        mGlobalState.commitStagedState();
        commitStagedEvents();
        return mOomAdjuster.updateOomAdjLocked(proc, oomAdjReason);
    }

@@ -124,15 +130,16 @@ public class ProcessStateController {
     */
    @GuardedBy("mLock")
    public void runPendingUpdate(@OomAdjReason int oomAdjReason) {
        mGlobalState.commitStagedState();
        commitStagedEvents();
        mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
    }

    /**
     * Trigger an update on all processes.
     */
    @GuardedBy("mLock")
    public void runFullUpdate(@OomAdjReason int oomAdjReason) {
        mGlobalState.commitStagedState();
        commitStagedEvents();
        mOomAdjuster.updateOomAdjLocked(oomAdjReason);
    }

@@ -140,8 +147,9 @@ public class ProcessStateController {
     * Trigger an update on any processes that have been marked for follow up during a previous
     * update.
     */
    @GuardedBy("mLock")
    public void runFollowUpUpdate() {
        mGlobalState.commitStagedState();
        commitStagedEvents();
        mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
    }

@@ -151,7 +159,7 @@ public class ProcessStateController {
     * @param looper which looper to post the async work to.
     */
    public ActivityStateAsyncUpdater createActivityStateAsyncUpdater(Looper looper) {
        return new ActivityStateAsyncUpdater(this, looper);
        return new ActivityStateAsyncUpdater(this, looper, mStagingQueue);
    }

    /**
@@ -776,6 +784,19 @@ public class ProcessStateController {
        }
    }

    @GuardedBy("mLock")
    private void commitStagedEvents() {
        mGlobalState.commitStagedState();

        if (Flags.pushActivityStateToOomadjuster()) {
            // Drain any activity state changes from the staging queue.
            final ConcurrentLinkedQueue<Runnable> queue = mStagingQueue;
            while (!queue.isEmpty()) {
                queue.poll().run();
            }
        }
    }

    /**
     * Helper class for sending Activity related state from Window Manager to
     * ProcessStateController. Because ProcessStateController is guarded by a lock WindowManager
@@ -788,11 +809,14 @@ public class ProcessStateController {
    public static class ActivityStateAsyncUpdater {
        private final ProcessStateController mPsc;
        private final Looper mLooper;
        private ConcurrentLinkedQueue<Runnable> mStagingQueue;
        private AsyncBatchSession mBatchSession;

        private ActivityStateAsyncUpdater(ProcessStateController psc, Looper looper) {
        private ActivityStateAsyncUpdater(ProcessStateController psc, Looper looper,
                ConcurrentLinkedQueue<Runnable> stagingQueue) {
            mPsc = psc;
            mLooper = looper;
            mStagingQueue = stagingQueue;
        }

        /**
@@ -829,7 +853,7 @@ public class ProcessStateController {
        public void setExpandedNotificationShadeAsync(boolean expandedShade) {
            if (!Flags.pushActivityStateToOomadjuster()) return;

            getBatchSession().enqueue(() -> mPsc.setExpandedNotificationShade(expandedShade));
            getBatchSession().stage(() -> mPsc.setExpandedNotificationShade(expandedShade));
        }

        /**
@@ -840,7 +864,7 @@ public class ProcessStateController {
            if (!Flags.pushActivityStateToOomadjuster()) return;

            final ProcessRecord top = wpc != null ? (ProcessRecord) wpc.mOwner : null;
            getBatchSession().enqueue(() -> {
            getBatchSession().stage(() -> {
                mPsc.setTopProcess(top);
                if (clearPrev) {
                    mPsc.setPreviousProcess(null);
@@ -857,7 +881,7 @@ public class ProcessStateController {
        public void setTopProcessStateAsync(@ActivityManager.ProcessState int procState) {
            if (!Flags.pushActivityStateToOomadjuster()) return;

            getBatchSession().enqueue(() -> mPsc.setTopProcessState(procState));
            getBatchSession().stage(() -> mPsc.setTopProcessState(procState));
        }

        /**
@@ -867,7 +891,7 @@ public class ProcessStateController {
            if (!Flags.pushActivityStateToOomadjuster()) return;

            final ProcessRecord prev = wpc != null ? (ProcessRecord) wpc.mOwner : null;
            getBatchSession().enqueue(() -> mPsc.setPreviousProcess(prev));
            getBatchSession().stage(() -> mPsc.setPreviousProcess(prev));
        }


@@ -878,7 +902,7 @@ public class ProcessStateController {
            if (!Flags.pushActivityStateToOomadjuster()) return;

            final ProcessRecord home = wpc != null ? (ProcessRecord) wpc.mOwner : null;
            getBatchSession().enqueue(() -> mPsc.setHomeProcess(home));
            getBatchSession().stage(() -> mPsc.setHomeProcess(home));
        }


@@ -889,7 +913,7 @@ public class ProcessStateController {
            if (!Flags.pushActivityStateToOomadjuster()) return;

            final ProcessRecord heavy = wpc != null ? (ProcessRecord) wpc.mOwner : null;
            getBatchSession().enqueue(() -> mPsc.setHeavyWeightProcess(heavy));
            getBatchSession().stage(() -> mPsc.setHeavyWeightProcess(heavy));
        }

        /**
@@ -899,7 +923,7 @@ public class ProcessStateController {
            if (!Flags.pushActivityStateToOomadjuster()) return;

            final ProcessRecord dozeUi = wpc != null ? (ProcessRecord) wpc.mOwner : null;
            getBatchSession().enqueue(() -> mPsc.setVisibleDozeUiProcess(dozeUi));
            getBatchSession().stage(() -> mPsc.setVisibleDozeUiProcess(dozeUi));
        }

        /**
@@ -909,7 +933,7 @@ public class ProcessStateController {
            if (!Flags.pushActivityStateToOomadjuster()) return;

            final ProcessRecordInternal activity = (ProcessRecordInternal) wpc.mOwner;
            getBatchSession().enqueue(() -> mPsc.setHasActivity(activity, hasActivity));
            getBatchSession().stage(() -> mPsc.setHasActivity(activity, hasActivity));
        }

        /**
@@ -920,7 +944,7 @@ public class ProcessStateController {
            if (!Flags.pushActivityStateToOomadjuster()) return;

            final ProcessRecordInternal activity = (ProcessRecordInternal) wpc.mOwner;
            getBatchSession().enqueue(() -> {
            getBatchSession().stage(() -> {
                mPsc.setActivityStateFlags(activity, flags);
                mPsc.setPerceptibleTaskStoppedTimeMillis(activity, perceptibleStopTimeMs);
            });
@@ -934,14 +958,14 @@ public class ProcessStateController {
            if (!Flags.pushActivityStateToOomadjuster()) return;

            final ProcessRecordInternal proc = (ProcessRecordInternal) wpc.mOwner;
            getBatchSession().enqueue(() -> mPsc.setHasRecentTasks(proc, hasRecentTasks));
            getBatchSession().stage(() -> mPsc.setHasRecentTasks(proc, hasRecentTasks));
        }

        private AsyncBatchSession getBatchSession() {
            if (mBatchSession == null) {
                final Handler h = new Handler(mLooper);
                final Runnable update = () -> mPsc.runFullUpdate(OOM_ADJ_REASON_ACTIVITY);
                mBatchSession = new AsyncBatchSession(h, mPsc.mLock, update);
                mBatchSession = new AsyncBatchSession(h, mPsc.mLock, mStagingQueue, update);
            }
            return mBatchSession;
        }
@@ -950,6 +974,7 @@ public class ProcessStateController {
    public static class AsyncBatchSession implements AutoCloseable {
        final Handler mHandler;
        final Object mLock;
        final ConcurrentLinkedQueue<Runnable> mStagingQueue;
        private final Runnable mUpdateRunnable;
        private final Runnable mLockedUpdateRunnable;
        private boolean mRunUpdate = false;
@@ -958,9 +983,11 @@ public class ProcessStateController {

        private ArrayList<Runnable> mBatchList = new ArrayList<>();

        AsyncBatchSession(Handler handler, Object lock, Runnable updateRunnable) {
        AsyncBatchSession(Handler handler, Object lock,
                ConcurrentLinkedQueue<Runnable> stagingQueue, Runnable updateRunnable) {
            mHandler = handler;
            mLock = lock;
            mStagingQueue = stagingQueue;
            mUpdateRunnable = updateRunnable;
            mLockedUpdateRunnable = () -> {
                synchronized (lock) {
@@ -979,6 +1006,15 @@ public class ProcessStateController {
            }
        }

        /**
         * Stage the runnable to be run on the next ProcessStateController update. The work may be
         * opportunistically run if an update triggers before the WindowManager posted update is
         * handled.
         */
        public void stage(Runnable runnable) {
            mStagingQueue.add(runnable);
        }

        /**
         * Enqueue the work to be run asynchronously done on a Handler thread.
         * If batch session is currently active, queue up the work to be run when the session ends.
+72 −4
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;

@Presubmit
public class ProcessStateControllerTest {
@@ -91,7 +92,7 @@ public class ProcessStateControllerTest {
    public void asyncBatchSession_enqueue() {
        ArrayList<String> list = new ArrayList<>();
        ProcessStateController.AsyncBatchSession session =
                new ProcessStateController.AsyncBatchSession(mManagedHandler, new Object(),
                new ProcessStateController.AsyncBatchSession(mManagedHandler, new Object(), null,
                        () -> list.add("UPDATED"));

        // Enqueue some work and trigger an update mid way, while batching is active.
@@ -118,7 +119,7 @@ public class ProcessStateControllerTest {
    public void asyncBatchSession_enqueue_batched() {
        ArrayList<String> list = new ArrayList<>();
        ProcessStateController.AsyncBatchSession session =
                new ProcessStateController.AsyncBatchSession(mManagedHandler, new Object(),
                new ProcessStateController.AsyncBatchSession(mManagedHandler, new Object(), null,
                        () -> list.add("UPDATED"));

        // Enqueue some work and trigger an update mid way, while batching is active.
@@ -144,7 +145,7 @@ public class ProcessStateControllerTest {
    public void asyncBatchSession_enqueueNoUpdate_batched() {
        ArrayList<String> list = new ArrayList<>();
        ProcessStateController.AsyncBatchSession session =
                new ProcessStateController.AsyncBatchSession(mManagedHandler, new Object(),
                new ProcessStateController.AsyncBatchSession(mManagedHandler, new Object(), null,
                        () -> list.add("UPDATED"));

        // Enqueue some work and trigger an update mid way, while batching is active.
@@ -166,7 +167,7 @@ public class ProcessStateControllerTest {
    public void asyncBatchSession_enqueueBoostPriority_batched() {
        ArrayList<String> list = new ArrayList<>();
        ProcessStateController.AsyncBatchSession session =
                new ProcessStateController.AsyncBatchSession(mManagedHandler, new Object(),
                new ProcessStateController.AsyncBatchSession(mManagedHandler, new Object(), null,
                        () -> list.add("UPDATED"));

        // Enqueue some work , while batching is active and boost the priority of the session.
@@ -185,4 +186,71 @@ public class ProcessStateControllerTest {
        mTestLooperManager.execute(mTestLooperManager.next());
        assertThat(list).containsExactly("A", "B", "X");
    }

    @Test
    public void asyncBatchSession_interlacedEnqueueAndStage() {
        ArrayList<String> list = new ArrayList<>();
        ConcurrentLinkedQueue<Runnable> stagingQueue = new ConcurrentLinkedQueue<>();
        ProcessStateController.AsyncBatchSession session =
                new ProcessStateController.AsyncBatchSession(mManagedHandler, new Object(),
                        stagingQueue, () -> list.add("UPDATED"));

        // Enqueue some work and trigger an update mid way, while batching is active.
        session.stage(() -> list.add("1"));
        session.enqueue(() -> list.add("A"));
        session.runUpdate();
        session.stage(() -> list.add("2"));

        // Run the first staged runnable.
        stagingQueue.poll().run();
        assertThat(list).containsExactly("1");
        // Step through the looper one
        mTestLooperManager.execute(mTestLooperManager.next());
        assertThat(list).containsExactly("1", "A");
        // Run the second staged runnable.
        stagingQueue.poll().run();
        assertThat(list).containsExactly("1", "A", "2");
        // Step through the looper once more.
        mTestLooperManager.execute(mTestLooperManager.next());
        assertThat(list).containsExactly("1", "A", "2", "UPDATED");
    }

    @Test
    public void asyncBatchSession_interlacedEnqueueAndStage_batched() {
        ArrayList<String> list = new ArrayList<>();
        ConcurrentLinkedQueue<Runnable> stagingQueue = new ConcurrentLinkedQueue<>();
        ProcessStateController.AsyncBatchSession session =
                new ProcessStateController.AsyncBatchSession(mManagedHandler, new Object(),
                        stagingQueue, () -> list.add("UPDATED"));

        // Enqueue some work and trigger an update mid way, while batching is active.
        session.start();
        session.stage(() -> list.add("1"));
        session.enqueue(() -> list.add("A"));
        session.stage(() -> list.add("2"));
        session.runUpdate();
        session.enqueue(() -> list.add("B"));
        session.stage(() -> list.add("3"));
        session.stage(() -> list.add("4"));
        session.close();

        // Run the first staged runnable.
        stagingQueue.poll().run();
        // Run the second staged runnable.
        stagingQueue.poll().run();
        // Run the third staged runnable.
        stagingQueue.poll().run();
        // Step through the looper once to run all batched enqueued work.
        mTestLooperManager.execute(mTestLooperManager.next());
        // Run the last staged runnable.
        stagingQueue.poll().run();

        assertThat(list.get(0)).isEqualTo("1");
        assertThat(list.get(1)).isEqualTo("2");
        assertThat(list.get(2)).isEqualTo("3");
        assertThat(list.get(3)).isEqualTo("A");
        assertThat(list.get(4)).isEqualTo("B");
        assertThat(list.get(5)).isEqualTo("UPDATED");
        assertThat(list.get(6)).isEqualTo("4");
    }
}