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

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

Implement ProcessStateController SyncBatchSession

Flag: com.android.server.am.psc_batch_update
Bug: 399680824
Test: atest MockingOomAdjusterTests
Test: atest ProcessStateControllerTest

Change-Id: I58ddbb40e3b3d586c05821459f19b32386bdd8b9
parent 2f68e904
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -527,6 +527,11 @@ public abstract class ActivityManagerInternal {
     */
    public static final int OOM_ADJ_REASON_BATCH_UPDATE_REQUEST = 26;

    /**
     * Number of Oom Adj Reasons
     */
    public static final int OOM_ADJ_REASON_COUNT = 27;

    @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
        OOM_ADJ_REASON_NONE,
        OOM_ADJ_REASON_ACTIVITY,
+4 −0
Original line number Diff line number Diff line
@@ -687,7 +687,11 @@ com.android.server.SystemServiceManager

com.android.server.am.BoundServiceSession
com.android.server.am.ConnectionRecord
com.android.server.am.OomAdjuster
com.android.server.am.psc.AsyncBatchSession
com.android.server.am.psc.BatchSession
com.android.server.am.psc.ConnectionRecordInternal
com.android.server.am.psc.SyncBatchSession
com.android.server.utils.TimingsTraceAndSlog

com.google.android.collect.Lists
+50 −60
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BATCH_UPDATE_REQUEST;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COUNT;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FOLLOW_UP;
@@ -167,9 +168,16 @@ import java.util.List;
/**
 * All of the code required to compute proc states and oom_adj values.
 */
@android.ravenwood.annotation.RavenwoodKeepPartialClass
public abstract class OomAdjuster {
    static final String TAG = "OomAdjuster";

    public static final String[] OOM_ADJ_REASON_TAGS = new String[OOM_ADJ_REASON_COUNT];
    static {
        Arrays.setAll(OOM_ADJ_REASON_TAGS,
                i -> "updateOomAdj_" + oomAdjReasonToStringSuffix(i));
    }

    /** To be used when the process does not have PROCESS_CAPABILITY_CPU_TIME. */
    public static final int CPU_TIME_REASON_NONE = 0;
    /** The process has PROCESS_CAPABILITY_CPU_TIME, but the reason is not interesting for logs. */
@@ -230,66 +238,48 @@ public abstract class OomAdjuster {
    public @interface ImplicitCpuTimeReasons {
    }

    public static final String oomAdjReasonToString(@OomAdjReason int oomReason) {
        final String OOM_ADJ_REASON_METHOD = "updateOomAdj";
        switch (oomReason) {
            case OOM_ADJ_REASON_NONE:
                return OOM_ADJ_REASON_METHOD + "_meh";
            case OOM_ADJ_REASON_ACTIVITY:
                return OOM_ADJ_REASON_METHOD + "_activityChange";
            case OOM_ADJ_REASON_FINISH_RECEIVER:
                return OOM_ADJ_REASON_METHOD + "_finishReceiver";
            case OOM_ADJ_REASON_START_RECEIVER:
                return OOM_ADJ_REASON_METHOD + "_startReceiver";
            case OOM_ADJ_REASON_BIND_SERVICE:
                return OOM_ADJ_REASON_METHOD + "_bindService";
            case OOM_ADJ_REASON_UNBIND_SERVICE:
                return OOM_ADJ_REASON_METHOD + "_unbindService";
            case OOM_ADJ_REASON_START_SERVICE:
                return OOM_ADJ_REASON_METHOD + "_startService";
            case OOM_ADJ_REASON_GET_PROVIDER:
                return OOM_ADJ_REASON_METHOD + "_getProvider";
            case OOM_ADJ_REASON_REMOVE_PROVIDER:
                return OOM_ADJ_REASON_METHOD + "_removeProvider";
            case OOM_ADJ_REASON_UI_VISIBILITY:
                return OOM_ADJ_REASON_METHOD + "_uiVisibility";
            case OOM_ADJ_REASON_ALLOWLIST:
                return OOM_ADJ_REASON_METHOD + "_allowlistChange";
            case OOM_ADJ_REASON_PROCESS_BEGIN:
                return OOM_ADJ_REASON_METHOD + "_processBegin";
            case OOM_ADJ_REASON_PROCESS_END:
                return OOM_ADJ_REASON_METHOD + "_processEnd";
            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
                return OOM_ADJ_REASON_METHOD + "_shortFgs";
            case OOM_ADJ_REASON_SYSTEM_INIT:
                return OOM_ADJ_REASON_METHOD + "_systemInit";
            case OOM_ADJ_REASON_BACKUP:
                return OOM_ADJ_REASON_METHOD + "_backup";
            case OOM_ADJ_REASON_SHELL:
                return OOM_ADJ_REASON_METHOD + "_shell";
            case OOM_ADJ_REASON_REMOVE_TASK:
                return OOM_ADJ_REASON_METHOD + "_removeTask";
            case OOM_ADJ_REASON_UID_IDLE:
                return OOM_ADJ_REASON_METHOD + "_uidIdle";
            case OOM_ADJ_REASON_STOP_SERVICE:
                return OOM_ADJ_REASON_METHOD + "_stopService";
            case OOM_ADJ_REASON_EXECUTING_SERVICE:
                return OOM_ADJ_REASON_METHOD + "_executingService";
            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
                return OOM_ADJ_REASON_METHOD + "_restrictionChange";
            case OOM_ADJ_REASON_COMPONENT_DISABLED:
                return OOM_ADJ_REASON_METHOD + "_componentDisabled";
            case OOM_ADJ_REASON_FOLLOW_UP:
                return OOM_ADJ_REASON_METHOD + "_followUp";
            case OOM_ADJ_REASON_RECONFIGURATION:
                return OOM_ADJ_REASON_METHOD + "_reconfiguration";
            case OOM_ADJ_REASON_SERVICE_BINDER_CALL:
                return OOM_ADJ_REASON_METHOD + "_serviceBinderCall";
            case OOM_ADJ_REASON_BATCH_UPDATE_REQUEST:
                return OOM_ADJ_REASON_METHOD + "_batchUpdateRequest";
            default:
                return "_unknown";
    /**
     * Return a human readable string for OomAdjuster updates with {@link OomAdjReason}.
     */
    public static String oomAdjReasonToString(@OomAdjReason int oomReason) {
        return OOM_ADJ_REASON_TAGS[oomReason];
    }

    /**
     * Return a human readable string for {@link OomAdjReason} to append to debug messages.
     */
    @android.ravenwood.annotation.RavenwoodKeep
    public static String oomAdjReasonToStringSuffix(@OomAdjReason int oomReason) {
        return switch (oomReason) {
            case OOM_ADJ_REASON_NONE -> "meh";
            case OOM_ADJ_REASON_ACTIVITY -> "activityChange";
            case OOM_ADJ_REASON_FINISH_RECEIVER -> "finishReceiver";
            case OOM_ADJ_REASON_START_RECEIVER -> "startReceiver";
            case OOM_ADJ_REASON_BIND_SERVICE -> "bindService";
            case OOM_ADJ_REASON_UNBIND_SERVICE -> "unbindService";
            case OOM_ADJ_REASON_START_SERVICE -> "startService";
            case OOM_ADJ_REASON_GET_PROVIDER -> "getProvider";
            case OOM_ADJ_REASON_REMOVE_PROVIDER -> "removeProvider";
            case OOM_ADJ_REASON_UI_VISIBILITY -> "uiVisibility";
            case OOM_ADJ_REASON_ALLOWLIST -> "allowlistChange";
            case OOM_ADJ_REASON_PROCESS_BEGIN -> "processBegin";
            case OOM_ADJ_REASON_PROCESS_END -> "processEnd";
            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT -> "shortFgs";
            case OOM_ADJ_REASON_SYSTEM_INIT -> "systemInit";
            case OOM_ADJ_REASON_BACKUP -> "backup";
            case OOM_ADJ_REASON_SHELL -> "shell";
            case OOM_ADJ_REASON_REMOVE_TASK -> "removeTask";
            case OOM_ADJ_REASON_UID_IDLE -> "uidIdle";
            case OOM_ADJ_REASON_STOP_SERVICE -> "stopService";
            case OOM_ADJ_REASON_EXECUTING_SERVICE -> "executingService";
            case OOM_ADJ_REASON_RESTRICTION_CHANGE -> "restrictionChange";
            case OOM_ADJ_REASON_COMPONENT_DISABLED -> "componentDisabled";
            case OOM_ADJ_REASON_FOLLOW_UP -> "followUp";
            case OOM_ADJ_REASON_RECONFIGURATION -> "reconfiguration";
            case OOM_ADJ_REASON_SERVICE_BINDER_CALL -> "serviceBinderCall";
            case OOM_ADJ_REASON_BATCH_UPDATE_REQUEST -> "batchUpdateRequest";
            default -> "unknown";
        };
    }

    /**
+59 −137
Original line number Diff line number Diff line
@@ -40,12 +40,13 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceThread;
import com.android.server.am.psc.AsyncBatchSession;
import com.android.server.am.psc.ProcessRecordInternal;
import com.android.server.am.psc.ServiceRecordInternal;
import com.android.server.am.psc.SyncBatchSession;
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;
@@ -71,6 +72,8 @@ public class ProcessStateController {

    private final GlobalState mGlobalState = new GlobalState();

    private SyncBatchSession mBatchSession;

    /**
     * Queue for staging asynchronous events. The queue will be drained before each update.
     */
@@ -96,12 +99,35 @@ public class ProcessStateController {
                }
            }
        });

    }

    /**
     * Start a batch session. ProcessStateController updates will not be triggered until the
     * returned SyncBatchSession is closed.
     */
    @GuardedBy("mLock")
    public SyncBatchSession startBatchSession(@OomAdjReason int reason) {
        if (!Flags.pscBatchUpdate()) return null;

        final SyncBatchSession batchSession = getBatchSession();
        batchSession.start(reason);
        return batchSession;
    }

    private SyncBatchSession getBatchSession() {
        if (mBatchSession == null) {
            mBatchSession = new SyncBatchSession(this::runFullUpdateImpl,
                    this::runPendingUpdateImpl);
        }
        return mBatchSession;
    }

    /**
     * Get the instance of OomAdjuster that ProcessStateController is using.
     * Must only be interacted with while holding the ActivityManagerService lock.
     */
    @GuardedBy("mLock")
    public OomAdjuster getOomAdjuster() {
        return mOomAdjuster;
    }
@@ -128,6 +154,17 @@ public class ProcessStateController {
     */
    @GuardedBy("mLock")
    public boolean runUpdate(@NonNull ProcessRecord proc, @OomAdjReason int oomAdjReason) {
        if (mBatchSession != null && mBatchSession.isActive()) {
            // BatchSession is active, just enqueue the proc for now. The update will happen
            // at the end of the session.
            enqueueUpdateTarget(proc);
            return false;
        }
        return runUpdateimpl(proc, oomAdjReason);
    }

    @GuardedBy("mLock")
    private boolean runUpdateimpl(@NonNull ProcessRecord proc, @OomAdjReason int oomAdjReason) {
        commitStagedEvents();
        return mOomAdjuster.updateOomAdjLocked(proc, oomAdjReason);
    }
@@ -137,6 +174,16 @@ public class ProcessStateController {
     */
    @GuardedBy("mLock")
    public void runPendingUpdate(@OomAdjReason int oomAdjReason) {
        if (mBatchSession != null && mBatchSession.isActive()) {
            // BatchSession is active, don't trigger the update, it will happen at the end of the
            // session.
            return;
        }
        runPendingUpdateImpl(oomAdjReason);
    }

    @GuardedBy("mLock")
    private void runPendingUpdateImpl(@OomAdjReason int oomAdjReason) {
        commitStagedEvents();
        mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
    }
@@ -146,6 +193,16 @@ public class ProcessStateController {
     */
    @GuardedBy("mLock")
    public void runFullUpdate(@OomAdjReason int oomAdjReason) {
        if (mBatchSession != null && mBatchSession.isActive()) {
            // BatchSession is active, just mark the session to run a full update at the end of
            // the session.
            getBatchSession().setFullUpdate();
            return;
        }
        runFullUpdateImpl(oomAdjReason);
    }

    private void runFullUpdateImpl(@OomAdjReason int oomAdjReason) {
        commitStagedEvents();
        mOomAdjuster.updateOomAdjLocked(oomAdjReason);
    }
@@ -871,7 +928,7 @@ public class ProcessStateController {
            if (!Flags.pushActivityStateToOomadjuster()) return null;

            final AsyncBatchSession session = getBatchSession();
            session.start();
            session.start(OOM_ADJ_REASON_ACTIVITY);
            return session;
        }

@@ -1017,141 +1074,6 @@ 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;
        private boolean mBoostPriority = false;
        private int mNestedStartCount = 0;

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

        AsyncBatchSession(Handler handler, Object lock,
                ConcurrentLinkedQueue<Runnable> stagingQueue, Runnable updateRunnable) {
            mHandler = handler;
            mLock = lock;
            mStagingQueue = stagingQueue;
            mUpdateRunnable = updateRunnable;
            mLockedUpdateRunnable = () -> {
                synchronized (lock) {
                    updateRunnable.run();
                }
            };
        }

        /**
         * If the BatchSession is currently active, posting the batched work to the front of the
         * Handler queue when the session is closed.
         */
        public void postToHead() {
            if (isActive()) {
                mBoostPriority = true;
            }
        }

        /**
         * 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.
         * Otherwise, the work will be immediately enqueued on to the Handler thread.
         */
        public void enqueue(Runnable runnable) {
            if (isActive()) {
                mBatchList.add(runnable);
            } else {
                // Not in session, just post to the handler immediately.
                mHandler.post(() -> {
                    synchronized (mLock) {
                        runnable.run();
                    }
                });
            }
        }

        /**
         * Trigger an update to be asynchronously done on a Handler thread.
         * If batch session is currently active, the update will be run at the end of the batched
         * work.
         * Otherwise, the update will be immediately enqueued on to the Handler thread (and any
         * previously posted update will be removed in favor of this most recent trigger).
         */
        public void runUpdate() {
            if (isActive()) {
                // Mark that an update should be done after the batched work is done.
                mRunUpdate = true;
            } else {
                // Not in session, just post to the handler immediately (and clear any existing
                // posted update).
                mHandler.removeCallbacks(mLockedUpdateRunnable);
                mHandler.post(mLockedUpdateRunnable);
            }
        }

        void start() {
            mNestedStartCount++;
        }

        private boolean isActive() {
            return mNestedStartCount > 0;
        }

        @Override
        public void close() {
            if (mNestedStartCount == 0) {
                Slog.wtfStack(TAG, "close() called on an unstarted BatchSession!");
                return;
            }

            mNestedStartCount--;

            if (isActive()) {
                // Still in an active batch session.
                return;
            }

            final ArrayList<Runnable> list = new ArrayList<>(mBatchList);
            final boolean runUpdate = mRunUpdate;

            // Return if there is nothing to do.
            if (list.isEmpty() && !runUpdate) return;

            mBatchList.clear();
            mRunUpdate = false;

            // offload all of the queued up work to the ActivityStateHandler thread.
            final Runnable batchedWorkload = () -> {
                synchronized (mLock) {
                    for (int i = 0, size = list.size(); i < size; i++) {
                        list.get(i).run();
                    }
                    if (runUpdate) {
                        mUpdateRunnable.run();
                    }
                }
            };

            if (mBoostPriority) {
                // The priority of this BatchSession has been boosted. Post to the front of the
                // Handler queue.
                mBoostPriority = false;
                mHandler.postAtFrontOfQueue(batchedWorkload);
            } else {
                mHandler.post(batchedWorkload);
            }
        }
    }

    /**
     * Interface for injecting LRU management into ProcessStateController
     * TODO(b/430385382): This should be remove when LRU is managed entirely within
+143 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.psc;

import android.os.Handler;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;

import com.android.server.am.ProcessStateController;

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

/**
 * A {@link BatchSession} that manages work to be done on another thread. The session will
 * trigger a {@link ProcessStateController} update at the end of the session if prompted to do so.
 */
@RavenwoodKeepWholeClass
public class AsyncBatchSession extends BatchSession {
    final Handler mHandler;
    final Object mLock;
    final ConcurrentLinkedQueue<Runnable> mStagingQueue;
    private final Runnable mUpdateRunnable;
    private final Runnable mLockedUpdateRunnable;
    private boolean mRunUpdate = false;
    private boolean mBoostPriority = false;

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

    public AsyncBatchSession(Handler handler, Object lock,
            ConcurrentLinkedQueue<Runnable> stagingQueue, Runnable updateRunnable) {
        mHandler = handler;
        mLock = lock;
        mStagingQueue = stagingQueue;
        mUpdateRunnable = updateRunnable;
        mLockedUpdateRunnable = () -> {
            synchronized (lock) {
                updateRunnable.run();
            }
        };
    }

    /**
     * If the BatchSession is currently active, posting the batched work to the front of the
     * Handler queue when the session is closed.
     */
    public void postToHead() {
        if (isActive()) {
            mBoostPriority = true;
        }
    }

    /**
     * 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.
     * Otherwise, the work will be immediately enqueued on to the Handler thread.
     */
    public void enqueue(Runnable runnable) {
        if (isActive()) {
            mBatchList.add(runnable);
        } else {
            // Not in session, just post to the handler immediately.
            mHandler.post(() -> {
                synchronized (mLock) {
                    runnable.run();
                }
            });
        }
    }

    /**
     * Trigger an update to be asynchronously done on a Handler thread.
     * If batch session is currently active, the update will be run at the end of the batched
     * work.
     * Otherwise, the update will be immediately enqueued on to the Handler thread (and any
     * previously posted update will be removed in favor of this most recent trigger).
     */
    public void runUpdate() {
        if (isActive()) {
            // Mark that an update should be done after the batched work is done.
            mRunUpdate = true;
        } else {
            // Not in session, just post to the handler immediately (and clear any existing
            // posted update).
            mHandler.removeCallbacks(mLockedUpdateRunnable);
            mHandler.post(mLockedUpdateRunnable);
        }
    }

    @Override
    protected void onClose() {
        final ArrayList<Runnable> list = new ArrayList<>(mBatchList);
        final boolean runUpdate = mRunUpdate;

        // Return if there is nothing to do.
        if (list.isEmpty() && !runUpdate) return;

        mBatchList.clear();
        mRunUpdate = false;

        // offload all of the queued up work to the Handler thread.
        final Runnable batchedWorkload = () -> {
            synchronized (mLock) {
                for (int i = 0, size = list.size(); i < size; i++) {
                    list.get(i).run();
                }
                if (runUpdate) {
                    mUpdateRunnable.run();
                }
            }
        };

        if (mBoostPriority) {
            // The priority of this BatchSession has been boosted. Post to the front of the
            // Handler queue.
            mBoostPriority = false;
            mHandler.postAtFrontOfQueue(batchedWorkload);
        } else {
            mHandler.post(batchedWorkload);
        }
    }
}
Loading