From dd09e2ad7033da293b9d278147d625bfaa8561cc Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Thu, 20 Oct 2022 17:03:55 -0600 Subject: BroadcastQueue: only OOM adjust for manifest. The legacy stack was purposefully lax about OOM adjusting, by only requesting it when delivering to manifest receivers. This change mirrors that behavior in the modern stack, with tests to confirm. Bug: 254720487 Test: atest FrameworksMockingServicesTests:BroadcastRecordTest Test: atest FrameworksMockingServicesTests:BroadcastQueueTest Test: atest FrameworksMockingServicesTests:BroadcastQueueModernImplTest Change-Id: I954d024d15493f02551c6e4a655d76339512430a --- .../android/server/am/BroadcastProcessQueue.java | 62 +++++++++++++++------- .../server/am/BroadcastQueueModernImpl.java | 22 ++++---- .../com/android/server/am/BroadcastQueueTest.java | 37 ++++++++++++- 3 files changed, 91 insertions(+), 30 deletions(-) diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 5123517e272d..093c7b510d77 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -85,9 +85,16 @@ class BroadcastProcessQueue { @Nullable ProcessRecord app; /** - * Track name to use for {@link Trace} events. + * Track name to use for {@link Trace} events, defined as part of upgrading + * into a running slot. */ - @Nullable String traceTrackName; + @Nullable String runningTraceTrackName; + + /** + * Flag indicating if this process should be OOM adjusted, defined as part + * of upgrading into a running slot. + */ + boolean runningOomAdjusted; /** * Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically @@ -141,7 +148,8 @@ class BroadcastProcessQueue { private boolean mActiveViaColdStart; /** - * Count of {@link #mPending} broadcasts of these various flavors. + * Count of {@link #mPending} and {@link #mPendingUrgent} broadcasts of + * these various flavors. */ private int mCountForeground; private int mCountOrdered; @@ -150,6 +158,7 @@ class BroadcastProcessQueue { private int mCountInteractive; private int mCountResultTo; private int mCountInstrumented; + private int mCountManifest; private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE; private @Reason int mRunnableAtReason = REASON_EMPTY; @@ -206,7 +215,7 @@ class BroadcastProcessQueue { // with implicit responsiveness expectations. final ArrayDeque queue = record.isUrgent() ? mPendingUrgent : mPending; queue.addLast(newBroadcastArgs); - onBroadcastEnqueued(record); + onBroadcastEnqueued(record, recordIndex); } /** @@ -224,7 +233,8 @@ class BroadcastProcessQueue { while (it.hasNext()) { final SomeArgs args = it.next(); final BroadcastRecord testRecord = (BroadcastRecord) args.arg1; - final Object testReceiver = testRecord.receivers.get(args.argi1); + final int testRecordIndex = args.argi1; + final Object testReceiver = testRecord.receivers.get(testRecordIndex); if ((record.callingUid == testRecord.callingUid) && (record.userId == testRecord.userId) && record.intent.filterEquals(testRecord.intent) @@ -233,8 +243,8 @@ class BroadcastProcessQueue { args.arg1 = record; args.argi1 = recordIndex; args.argi2 = blockedUntilTerminalCount; - onBroadcastDequeued(testRecord); - onBroadcastEnqueued(record); + onBroadcastDequeued(testRecord, testRecordIndex); + onBroadcastEnqueued(record, recordIndex); return true; } } @@ -284,13 +294,13 @@ class BroadcastProcessQueue { while (it.hasNext()) { final SomeArgs args = it.next(); final BroadcastRecord record = (BroadcastRecord) args.arg1; - final int index = args.argi1; - if (predicate.test(record, index)) { - consumer.accept(record, index); + final int recordIndex = args.argi1; + if (predicate.test(record, recordIndex)) { + consumer.accept(record, recordIndex); if (andRemove) { args.recycle(); it.remove(); - onBroadcastDequeued(record); + onBroadcastDequeued(record, recordIndex); } didSomething = true; } @@ -385,7 +395,7 @@ class BroadcastProcessQueue { mActiveCountSinceIdle++; mActiveViaColdStart = false; next.recycle(); - onBroadcastDequeued(mActive); + onBroadcastDequeued(mActive, mActiveIndex); } /** @@ -403,7 +413,7 @@ class BroadcastProcessQueue { /** * Update summary statistics when the given record has been enqueued. */ - private void onBroadcastEnqueued(@NonNull BroadcastRecord record) { + private void onBroadcastEnqueued(@NonNull BroadcastRecord record, int recordIndex) { if (record.isForeground()) { mCountForeground++; } @@ -425,13 +435,16 @@ class BroadcastProcessQueue { if (record.callerInstrumented) { mCountInstrumented++; } + if (record.receivers.get(recordIndex) instanceof ResolveInfo) { + mCountManifest++; + } invalidateRunnableAt(); } /** * Update summary statistics when the given record has been dequeued. */ - private void onBroadcastDequeued(@NonNull BroadcastRecord record) { + private void onBroadcastDequeued(@NonNull BroadcastRecord record, int recordIndex) { if (record.isForeground()) { mCountForeground--; } @@ -453,34 +466,37 @@ class BroadcastProcessQueue { if (record.callerInstrumented) { mCountInstrumented--; } + if (record.receivers.get(recordIndex) instanceof ResolveInfo) { + mCountManifest--; + } invalidateRunnableAt(); } public void traceProcessStartingBegin() { Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, - traceTrackName, toShortString() + " starting", hashCode()); + runningTraceTrackName, toShortString() + " starting", hashCode()); } public void traceProcessRunningBegin() { Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, - traceTrackName, toShortString() + " running", hashCode()); + runningTraceTrackName, toShortString() + " running", hashCode()); } public void traceProcessEnd() { Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, - traceTrackName, hashCode()); + runningTraceTrackName, hashCode()); } public void traceActiveBegin() { final int cookie = mActive.receivers.get(mActiveIndex).hashCode(); Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, - traceTrackName, mActive.toShortString() + " scheduled", cookie); + runningTraceTrackName, mActive.toShortString() + " scheduled", cookie); } public void traceActiveEnd() { final int cookie = mActive.receivers.get(mActiveIndex).hashCode(); Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, - traceTrackName, cookie); + runningTraceTrackName, cookie); } /** @@ -539,6 +555,14 @@ class BroadcastProcessQueue { return (queue != null) ? (BroadcastRecord) queue.peekFirst().arg1 : null; } + /** + * Quickly determine if this queue has broadcasts waiting to be delivered to + * manifest receivers, which indicates we should request an OOM adjust. + */ + public boolean isPendingManifest() { + return mCountManifest > 0; + } + /** * Quickly determine if this queue has broadcasts that are still waiting to * be delivered at some point in the future. diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 4c831bd47ee4..6b0bbbe00388 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -401,7 +401,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mRunnableHead = removeFromRunnableList(mRunnableHead, queue); // Emit all trace events for this process into a consistent track - queue.traceTrackName = TAG + ".mRunning[" + queueIndex + "]"; + queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]"; + queue.runningOomAdjusted = queue.isPendingManifest(); // If we're already warm, schedule next pending broadcast now; // otherwise we'll wait for the cold start to circle back around @@ -415,9 +416,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { scheduleReceiverColdLocked(queue); } - // We've moved at least one process into running state above, so we - // need to kick off an OOM adjustment pass - updateOomAdj = true; + // Only kick off an OOM adjustment pass if needed + updateOomAdj |= queue.runningOomAdjusted; // Move to considering next runnable queue queue = nextQueue; @@ -1245,8 +1245,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (queue.app != null) { queue.app.mReceivers.incrementCurReceivers(); - queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER); - // Don't bump its LRU position if it's in the background restricted. if (mService.mInternal.getRestrictionLevel( queue.uid) < ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET) { @@ -1256,7 +1254,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(queue.app, OOM_ADJ_REASON_START_RECEIVER); - mService.enqueueOomAdjTargetLocked(queue.app); + if (queue.runningOomAdjusted) { + queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER); + mService.enqueueOomAdjTargetLocked(queue.app); + } } } @@ -1266,10 +1267,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ private void notifyStoppedRunning(@NonNull BroadcastProcessQueue queue) { if (queue.app != null) { - // Update during our next pass; no need for an immediate update - mService.enqueueOomAdjTargetLocked(queue.app); - queue.app.mReceivers.decrementCurReceivers(); + + if (queue.runningOomAdjusted) { + mService.enqueueOomAdjTargetLocked(queue.app); + } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index d9a26c68f3ed..0a428c415e4d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -888,7 +888,7 @@ public class BroadcastQueueTest { }) { // Confirm expected OOM adjustments; we were invoked once to upgrade // and once to downgrade - assertEquals(ActivityManager.PROCESS_STATE_RECEIVER, + assertEquals(String.valueOf(receiverApp), ActivityManager.PROCESS_STATE_RECEIVER, receiverApp.mState.getReportedProcState()); verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp)); @@ -1599,4 +1599,39 @@ public class BroadcastQueueTest { assertTrue(mQueue.isBeyondBarrierLocked(afterFirst)); assertTrue(mQueue.isBeyondBarrierLocked(afterSecond)); } + + /** + * Verify that we OOM adjust for manifest receivers. + */ + @Test + public void testOomAdjust_Manifest() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE), + makeManifestReceiver(PACKAGE_GREEN, CLASS_RED)))); + + waitForIdle(); + verify(mAms, atLeastOnce()).enqueueOomAdjTargetLocked(any()); + } + + /** + * Verify that we never OOM adjust for registered receivers. + */ + @Test + public void testOomAdjust_Registered() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(makeRegisteredReceiver(receiverApp), + makeRegisteredReceiver(receiverApp), + makeRegisteredReceiver(receiverApp)))); + + waitForIdle(); + verify(mAms, never()).enqueueOomAdjTargetLocked(any()); + } } -- cgit v1.2.3-59-g8ed1b From 7e72d90e58ad0c5ad6695bac1951eb0afe89423d Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Tue, 25 Oct 2022 16:31:08 -0600 Subject: BroadcastQueue: make "oneway" calls non-blocking. When an IApplicationThread instance is hosted by the system_server process, our various schedule-broadcast calls are dispatched as blocking methods (with the AMS lock held!!), instead of matching the expected "oneway" contract. This change offers ProcessRecord.getOnewayThread() which dispatches calls through FgThread using SameProcessApplicationThread, which starts with initial support for broadcast-related methods, but could be expanded in the future where needed. Bug: 255532202 Test: atest FrameworksMockingServicesTests:BroadcastRecordTest Test: atest FrameworksMockingServicesTests:BroadcastQueueTest Test: atest FrameworksMockingServicesTests:BroadcastQueueModernImplTest Change-Id: I7fb2a7353c44c29cda94e00fc87b8cff6ffc0008 --- .../android/server/am/BroadcastProcessQueue.java | 2 +- .../server/am/BroadcastQueueModernImpl.java | 4 +- .../java/com/android/server/am/ProcessRecord.java | 19 ++++++ .../server/am/SameProcessApplicationThread.java | 73 ++++++++++++++++++++++ .../server/am/BroadcastQueueModernImplTest.java | 4 ++ .../com/android/server/am/BroadcastQueueTest.java | 4 +- 6 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 services/core/java/com/android/server/am/SameProcessApplicationThread.java diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 093c7b510d77..9ec1fb42ce5a 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -349,7 +349,7 @@ class BroadcastProcessQueue { * Return if we know of an actively running "warm" process for this queue. */ public boolean isProcessWarm() { - return (app != null) && (app.getThread() != null) && !app.isKilled(); + return (app != null) && (app.getOnewayThread() != null) && !app.isKilled(); } public int getPreferredSchedulingGroupLocked() { diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 6b0bbbe00388..2e662b4117f3 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -736,7 +736,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app); setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED); - final IApplicationThread thread = app.getThread(); + final IApplicationThread thread = app.getOnewayThread(); if (thread != null) { try { if (receiver instanceof BroadcastFilter) { @@ -777,7 +777,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void scheduleResultTo(@NonNull BroadcastRecord r) { if ((r.resultToApp == null) || (r.resultTo == null)) return; final ProcessRecord app = r.resultToApp; - final IApplicationThread thread = app.getThread(); + final IApplicationThread thread = app.getOnewayThread(); if (thread != null) { mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily( app, OOM_ADJ_REASON_FINISH_RECEIVER); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 3b04dbb1da98..0a8c6400a6fd 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.Zygote; +import com.android.server.FgThread; import com.android.server.wm.WindowProcessController; import com.android.server.wm.WindowProcessListener; @@ -142,6 +143,13 @@ class ProcessRecord implements WindowProcessListener { @CompositeRWLock({"mService", "mProcLock"}) private IApplicationThread mThread; + /** + * Instance of {@link #mThread} that will always meet the {@code oneway} + * contract, possibly by using {@link SameProcessApplicationThread}. + */ + @CompositeRWLock({"mService", "mProcLock"}) + private IApplicationThread mOnewayThread; + /** * Always keep this application running? */ @@ -603,16 +611,27 @@ class ProcessRecord implements WindowProcessListener { return mThread; } + @GuardedBy(anyOf = {"mService", "mProcLock"}) + IApplicationThread getOnewayThread() { + return mOnewayThread; + } + @GuardedBy({"mService", "mProcLock"}) public void makeActive(IApplicationThread thread, ProcessStatsService tracker) { mProfile.onProcessActive(thread, tracker); mThread = thread; + if (mPid == Process.myPid()) { + mOnewayThread = new SameProcessApplicationThread(thread, FgThread.getHandler()); + } else { + mOnewayThread = thread; + } mWindowProcessController.setThread(thread); } @GuardedBy({"mService", "mProcLock"}) public void makeInactive(ProcessStatsService tracker) { mThread = null; + mOnewayThread = null; mWindowProcessController.setThread(null); mProfile.onProcessInactive(tracker); } diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java new file mode 100644 index 000000000000..a3c011188539 --- /dev/null +++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 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; + +import android.annotation.NonNull; +import android.app.IApplicationThread; +import android.content.IIntentReceiver; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.CompatibilityInfo; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; + +import java.util.Objects; + +/** + * Wrapper around an {@link IApplicationThread} that delegates selected calls + * through a {@link Handler} so they meet the {@code oneway} contract of + * returning immediately after dispatch. + */ +public class SameProcessApplicationThread extends IApplicationThread.Default { + private final IApplicationThread mWrapped; + private final Handler mHandler; + + public SameProcessApplicationThread(@NonNull IApplicationThread wrapped, + @NonNull Handler handler) { + mWrapped = Objects.requireNonNull(wrapped); + mHandler = Objects.requireNonNull(handler); + } + + @Override + public void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, + int resultCode, String data, Bundle extras, boolean sync, int sendingUser, + int processState) { + mHandler.post(() -> { + try { + mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras, sync, + sendingUser, processState); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode, + String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser, + int processState) { + mHandler.post(() -> { + try { + mWrapped.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras, + ordered, sticky, sendingUser, processState); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + }); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index ba414cb593ef..ee3815428b25 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -87,6 +87,10 @@ public class BroadcastQueueModernImplTest { mHandlerThread.start(); mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); + mConstants.DELAY_URGENT_MILLIS = -120_000; + mConstants.DELAY_NORMAL_MILLIS = 10_000; + mConstants.DELAY_CACHED_MILLIS = 120_000; + mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(), mConstants, mConstants); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 0a428c415e4d..e1a4c1dd7256 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -897,8 +897,8 @@ public class BroadcastQueueTest { // cold-started apps to be thawed, but the modern stack does } else { // Confirm that app was thawed - verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(receiverApp), - eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER)); + verify(mAms.mOomAdjuster.mCachedAppOptimizer, atLeastOnce()).unfreezeTemporarily( + eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER)); // Confirm that we added package to process verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName), -- cgit v1.2.3-59-g8ed1b -- cgit v1.2.3-59-g8ed1b