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

Commit 8136c6cf authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement ProcessStateController SyncBatchSession" into main

parents cf4b112b d8872b95
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;
@@ -166,9 +167,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. */
@@ -229,66 +237,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";
        };
    }

    ActivityManagerConstants mConstants;
+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.
     */
@@ -95,12 +98,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;
    }
@@ -127,6 +153,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);
    }
@@ -136,6 +173,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);
    }
@@ -145,6 +192,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);
    }
@@ -870,7 +927,7 @@ public class ProcessStateController {
            if (!Flags.pushActivityStateToOomadjuster()) return null;

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

@@ -1016,141 +1073,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