Loading core/java/android/app/ActivityManagerInternal.java +5 −0 Original line number Diff line number Diff line Loading @@ -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, Loading ravenwood/texts/ravenwood-annotation-allowed-classes.txt +4 −0 Original line number Diff line number Diff line Loading @@ -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 Loading services/core/java/com/android/server/am/OomAdjuster.java +50 −60 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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; Loading services/core/java/com/android/server/am/ProcessStateController.java +59 −137 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. */ Loading @@ -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; } Loading @@ -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); } Loading @@ -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); } Loading @@ -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); } Loading Loading @@ -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; } Loading Loading @@ -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 Loading services/core/java/com/android/server/am/psc/AsyncBatchSession.java 0 → 100644 +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
core/java/android/app/ActivityManagerInternal.java +5 −0 Original line number Diff line number Diff line Loading @@ -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, Loading
ravenwood/texts/ravenwood-annotation-allowed-classes.txt +4 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
services/core/java/com/android/server/am/OomAdjuster.java +50 −60 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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; Loading
services/core/java/com/android/server/am/ProcessStateController.java +59 −137 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. */ Loading @@ -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; } Loading @@ -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); } Loading @@ -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); } Loading @@ -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); } Loading Loading @@ -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; } Loading Loading @@ -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 Loading
services/core/java/com/android/server/am/psc/AsyncBatchSession.java 0 → 100644 +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); } } }