Merge "Don't freeze bindee process if bound with BIND_ALLOW_OOM_MANAGEMENT" into sc-dev

This commit is contained in:
Jing Ji
2021-04-06 02:59:48 +00:00
committed by Android (Google) Code Review
6 changed files with 249 additions and 18 deletions

View File

@@ -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"})

View File

@@ -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()) {

View File

@@ -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);
}
}

View File

@@ -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",

View File

@@ -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.
*/

View File

@@ -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);
}
}
}