diff options
| author | 2021-04-06 02:59:48 +0000 | |
|---|---|---|
| committer | 2021-04-06 02:59:48 +0000 | |
| commit | d11fdfbe3006ea768dbbc64de79629327e6532ef (patch) | |
| tree | fcba8909299982e4e4f76881a0be5a1e893cc523 | |
| parent | 3a5a8b856ded85df1964accf2df139e3cdaa7563 (diff) | |
| parent | 37c5375bf2f94c0f1669ae58f23853e8be54933b (diff) | |
Merge "Don't freeze bindee process if bound with BIND_ALLOW_OOM_MANAGEMENT" into sc-dev
6 files changed, 248 insertions, 17 deletions
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 1839e2a9cee4..e4cb15fe8d56 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -23,6 +23,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.ApplicationExitInfo; +import android.database.ContentObserver; +import android.net.Uri; import android.os.Debug; import android.os.Handler; import android.os.Message; @@ -80,6 +82,8 @@ public final class CachedAppOptimizer { "compact_full_delta_rss_throttle_kb"; @VisibleForTesting static final String KEY_COMPACT_PROC_STATE_THROTTLE = "compact_proc_state_throttle"; + @VisibleForTesting static final String KEY_FREEZER_DEBOUNCE_TIMEOUT = + "freeze_debounce_timeout"; // Phenotype sends int configurations and we map them to the strings we'll use on device, // preventing a weird string value entering the kernel. @@ -116,6 +120,10 @@ public final class CachedAppOptimizer { // Format of this string should be a comma separated list of integers. @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE = String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER); + @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 600_000L; + + @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor( + Settings.Global.CACHED_APPS_FREEZER_ENABLED); @VisibleForTesting interface PropertyChangedCallbackForTest { @@ -141,9 +149,6 @@ public final class CachedAppOptimizer { static final int SET_FROZEN_PROCESS_MSG = 3; static final int REPORT_UNFREEZE_MSG = 4; - //TODO:change this static definition into a configurable flag. - static final long FREEZE_TIMEOUT_MS = 600000; - static final int DO_FREEZE = 1; static final int REPORT_UNFREEZE = 2; @@ -198,6 +203,8 @@ public final class CachedAppOptimizer { updateMinOomAdjThrottle(); } else if (KEY_COMPACT_THROTTLE_MAX_OOM_ADJ.equals(name)) { updateMaxOomAdjThrottle(); + } else if (KEY_FREEZER_DEBOUNCE_TIMEOUT.equals(name)) { + updateFreezerDebounceTimeout(); } } } @@ -207,6 +214,23 @@ public final class CachedAppOptimizer { } }; + private final class SettingsContentObserver extends ContentObserver { + SettingsContentObserver() { + super(mAm.mHandler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (CACHED_APP_FREEZER_ENABLED_URI.equals(uri)) { + synchronized (mPhenotypeFlagLock) { + updateUseFreezer(); + } + } + } + } + + private final SettingsContentObserver mSettingsObserver; + private final Object mPhenotypeFlagLock = new Object(); // Configured by phenotype. Updates from the server take effect immediately. @@ -259,6 +283,8 @@ public final class CachedAppOptimizer { @GuardedBy("mProcLock") private boolean mFreezerOverride = false; + @VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT; + // Maps process ID to last compaction statistics for processes that we've fully compacted. Used // when evaluating throttles that we only consider for "full" compaction, so we don't store // data for "some" compactions. Uses LinkedHashMap to ensure insertion order is kept and @@ -293,6 +319,7 @@ public final class CachedAppOptimizer { mProcStateThrottle = new HashSet<>(); mProcessDependencies = processDependencies; mTestCallback = callback; + mSettingsObserver = new SettingsContentObserver(); } /** @@ -303,6 +330,8 @@ public final class CachedAppOptimizer { // TODO: initialize flags to default and only update them if values are set in DeviceConfig DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener); + mAm.mContext.getContentResolver().registerContentObserver( + CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver); synchronized (mPhenotypeFlagLock) { updateUseCompaction(); updateCompactionActions(); @@ -315,6 +344,7 @@ public final class CachedAppOptimizer { updateUseFreezer(); updateMinOomAdjThrottle(); updateMaxOomAdjThrottle(); + updateFreezerDebounceTimeout(); } } @@ -367,6 +397,7 @@ public final class CachedAppOptimizer { + " processes."); pw.println(" " + KEY_USE_FREEZER + "=" + mUseFreezer); pw.println(" " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate); + pw.println(" " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout); if (DEBUG_COMPACTION) { for (Map.Entry<Integer, LastCompactionStats> entry : mLastCompactionStats.entrySet()) { @@ -627,21 +658,28 @@ public final class CachedAppOptimizer { mUseFreezer = isFreezerSupported(); } - if (mUseFreezer && mFreezeHandler == null) { - Slog.d(TAG_AM, "Freezer enabled"); - enableFreezer(true); + final boolean useFreezer = mUseFreezer; + // enableFreezer() would need the global ActivityManagerService lock, post it. + mAm.mHandler.post(() -> { + if (useFreezer) { + Slog.d(TAG_AM, "Freezer enabled"); + enableFreezer(true); - if (!mCachedAppOptimizerThread.isAlive()) { - mCachedAppOptimizerThread.start(); - } + if (!mCachedAppOptimizerThread.isAlive()) { + mCachedAppOptimizerThread.start(); + } - mFreezeHandler = new FreezeHandler(); + if (mFreezeHandler == null) { + mFreezeHandler = new FreezeHandler(); + } - Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(), - Process.THREAD_GROUP_SYSTEM); - } else { - enableFreezer(false); - } + Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(), + Process.THREAD_GROUP_SYSTEM); + } else { + Slog.d(TAG_AM, "Freezer disabled"); + enableFreezer(false); + } + }); } @GuardedBy("mPhenotypeFlagLock") @@ -794,6 +832,16 @@ public final class CachedAppOptimizer { } } + @GuardedBy("mPhenotypeFlagLock") + private void updateFreezerDebounceTimeout() { + mFreezerDebounceTimeout = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_FREEZER_DEBOUNCE_TIMEOUT, DEFAULT_FREEZER_DEBOUNCE_TIMEOUT); + + if (mFreezerDebounceTimeout < 0) { + mFullDeltaRssThrottleKb = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT; + } + } + private boolean parseProcStateThrottle(String procStateThrottleString) { String[] procStates = TextUtils.split(procStateThrottleString, ","); mProcStateThrottle.clear(); @@ -818,7 +866,7 @@ public final class CachedAppOptimizer { return COMPACT_ACTION_STRING[action]; } - // This will ensure app will be out of the freezer for at least FREEZE_TIMEOUT_MS + // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout. @GuardedBy("mAm") void unfreezeTemporarily(ProcessRecord app) { if (mUseFreezer) { @@ -838,7 +886,7 @@ public final class CachedAppOptimizer { mFreezeHandler.sendMessageDelayed( mFreezeHandler.obtainMessage( SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app), - FREEZE_TIMEOUT_MS); + mFreezerDebounceTimeout); } @GuardedBy({"mAm", "mProcLock"}) diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 7c5936720feb..c8721cc209ce 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2017,6 +2017,10 @@ public final class OomAdjuster { } String adjType = null; if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { + // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen. + if (clientAdj < ProcessList.CACHED_APP_MIN_ADJ) { + app.mOptRecord.setShouldNotFreeze(true); + } // Not doing bind OOM management, so treat // this guy more like a started service. if (state.hasShownUi() && !state.getCachedIsHomeProcess()) { diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index f4ce7230f72a..026c1d3fd204 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -17,6 +17,7 @@ package com.android.server.am; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; @@ -28,6 +29,9 @@ final class ProcessCachedOptimizerRecord { private final ActivityManagerGlobalLock mProcLock; + @VisibleForTesting + static final String IS_FROZEN = "isFrozen"; + /** * The last time that this process was compacted. */ @@ -169,5 +173,6 @@ final class ProcessCachedOptimizerRecord { void dump(PrintWriter pw, String prefix, long nowUptime) { pw.print(prefix); pw.print("lastCompactTime="); pw.print(mLastCompactTime); pw.print(" lastCompactAction="); pw.println(mLastCompactAction); + pw.print(" " + IS_FROZEN + "="); pw.println(mFrozen); } } diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 68f547979ce0..d7fbd4913b2c 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -42,6 +42,7 @@ android_test { "androidx.test.ext.truth", "androidx.test.runner", "androidx.test.rules", + "cts-wm-util", "platform-compat-test-rules", "mockito-target-minus-junit4", "platform-test-annotations", diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java index e04841bf2335..6bca5e449b34 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java @@ -46,6 +46,9 @@ import android.os.Messenger; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.provider.DeviceConfig; +import android.provider.Settings; +import android.server.wm.settings.SettingsSession; import android.support.test.uiautomator.UiDevice; import android.test.suitebuilder.annotation.LargeTest; import android.text.TextUtils; @@ -61,6 +64,8 @@ import java.io.IOException; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Tests for {@link ActivityManager}. @@ -316,6 +321,102 @@ public class ActivityManagerTest { } } + @LargeTest + @Test + public void testAppFreezerWithAllowOomAdj() throws Exception { + final long waitFor = 5000; + boolean freezerWasEnabled = isFreezerEnabled(); + SettingsSession<String> freezerEnabled = null; + SettingsSession<String> amConstantsSettings = null; + DeviceConfigSession<Long> freezerDebounceTimeout = null; + MyServiceConnection autoConnection = null; + try { + if (!freezerWasEnabled) { + freezerEnabled = new SettingsSession<>( + Settings.Global.getUriFor(Settings.Global.CACHED_APPS_FREEZER_ENABLED), + Settings.Global::getString, Settings.Global::putString); + freezerEnabled.set("enabled"); + Thread.sleep(waitFor); + if (!isFreezerEnabled()) { + // Still not enabled? Probably because the device doesn't support it. + return; + } + } + freezerDebounceTimeout = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + CachedAppOptimizer.KEY_FREEZER_DEBOUNCE_TIMEOUT, + DeviceConfig::getLong, CachedAppOptimizer.DEFAULT_FREEZER_DEBOUNCE_TIMEOUT); + freezerDebounceTimeout.set(waitFor); + + final String activityManagerConstants = Settings.Global.ACTIVITY_MANAGER_CONSTANTS; + amConstantsSettings = new SettingsSession<>( + Settings.Global.getUriFor(activityManagerConstants), + Settings.Global::getString, Settings.Global::putString); + + amConstantsSettings.set( + ActivityManagerConstants.KEY_MAX_SERVICE_INACTIVITY + "=" + waitFor); + + final Intent intent = new Intent(); + intent.setClassName(TEST_APP, TEST_CLASS); + + final CountDownLatch latch = new CountDownLatch(1); + autoConnection = new MyServiceConnection(latch); + mContext.bindService(intent, autoConnection, + Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT); + try { + assertTrue("Timeout to bind to service " + intent.getComponent(), + latch.await(AWAIT_TIMEOUT, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail("Unable to bind to service " + intent.getComponent()); + } + assertFalse(TEST_APP + " shouldn't be frozen now.", isAppFrozen(TEST_APP)); + + // Trigger oomAdjUpdate/ + toggleScreenOn(false); + toggleScreenOn(true); + + // Wait for the freezer kick in if there is any. + Thread.sleep(waitFor * 4); + + // It still shouldn't be frozen, although it's been in cached state. + assertFalse(TEST_APP + " shouldn't be frozen now.", isAppFrozen(TEST_APP)); + } finally { + toggleScreenOn(true); + if (amConstantsSettings != null) { + amConstantsSettings.close(); + } + if (freezerEnabled != null) { + freezerEnabled.close(); + } + if (freezerDebounceTimeout != null) { + freezerDebounceTimeout.close(); + } + if (autoConnection != null) { + mContext.unbindService(autoConnection); + } + } + } + + private boolean isFreezerEnabled() throws Exception { + final String output = runShellCommand("dumpsys activity settings"); + final Matcher matcher = Pattern.compile("\\b" + CachedAppOptimizer.KEY_USE_FREEZER + + "\\b=\\b(true|false)\\b").matcher(output); + if (matcher.find()) { + return Boolean.parseBoolean(matcher.group(1)); + } + return false; + } + + private boolean isAppFrozen(String packageName) throws Exception { + final String output = runShellCommand("dumpsys activity p " + packageName); + final Matcher matcher = Pattern.compile("\\b" + ProcessCachedOptimizerRecord.IS_FROZEN + + "\\b=\\b(true|false)\\b").matcher(output); + if (matcher.find()) { + return Boolean.parseBoolean(matcher.group(1)); + } + return false; + } + /** * Make sure the screen state. */ diff --git a/services/tests/servicestests/src/com/android/server/am/DeviceConfigSession.java b/services/tests/servicestests/src/com/android/server/am/DeviceConfigSession.java new file mode 100644 index 000000000000..03cf17c37647 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/DeviceConfigSession.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 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.provider.DeviceConfig; + +import com.android.compatibility.common.util.SystemUtil; +import com.android.internal.util.function.TriFunction; + +/** + * An utility class to set/restore the given device_config item. + */ +public class DeviceConfigSession<T> implements AutoCloseable { + private final TriFunction<String, String, T, T> mGetter; + + private final String mNamespace; + private final String mKey; + private final T mInitialValue; + private final T mDefaultValue; + private boolean mHasInitalValue; + + DeviceConfigSession(String namespace, String key, + TriFunction<String, String, T, T> getter, T defaultValue) { + mNamespace = namespace; + mKey = key; + mGetter = getter; + mDefaultValue = defaultValue; + // Try {@DeviceConfig#getString} firstly since the DeviceConfig API doesn't + // support "not found" exception. + final String initialStringValue = DeviceConfig.getString(namespace, key, null); + if (initialStringValue == null) { + mHasInitalValue = false; + mInitialValue = defaultValue; + } else { + mHasInitalValue = true; + mInitialValue = getter.apply(namespace, key, defaultValue); + } + } + + public void set(final @NonNull T value) { + DeviceConfig.setProperty(mNamespace, mKey, + value == null ? null : value.toString(), false); + } + + public T get() { + return mGetter.apply(mNamespace, mKey, mDefaultValue); + } + + @Override + public void close() throws Exception { + if (mHasInitalValue) { + set(mInitialValue); + } else { + SystemUtil.runShellCommand("device_config delete " + mNamespace + " " + mKey); + } + } +} |