Merge "Don't freeze bindee process if bound with BIND_ALLOW_OOM_MANAGEMENT" into sc-dev
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
if (mFreezeHandler == null) {
|
||||
mFreezeHandler = new FreezeHandler();
|
||||
}
|
||||
|
||||
Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
|
||||
Process.THREAD_GROUP_SYSTEM);
|
||||
} else {
|
||||
Slog.d(TAG_AM, "Freezer disabled");
|
||||
enableFreezer(false);
|
||||
}
|
||||
|
||||
mFreezeHandler = new FreezeHandler();
|
||||
|
||||
Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
|
||||
Process.THREAD_GROUP_SYSTEM);
|
||||
} else {
|
||||
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"})
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user