Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 37c5375b authored by Jing Ji's avatar Jing Ji
Browse files

Don't freeze bindee process if bound with BIND_ALLOW_OOM_MANAGEMENT

Also fixed app freezer's settings change observer, added a device
config item for the debounce timeout, as well as a lock ordering issue

Bug: 183735766
Test: atest FrameworksServicesTests:ActivityManagerTest
Test: atest CachedAppOptimizerTest
Change-Id: Idfdc90fd55172103eb6bb1ab1f64e5c4f17c89c3
parent f45997c0
Loading
Loading
Loading
Loading
+65 −17
Original line number Diff line number Diff line
@@ -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,7 +658,10 @@ public final class CachedAppOptimizer {
            mUseFreezer = isFreezerSupported();
        }

        if (mUseFreezer && mFreezeHandler == null) {
        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);

@@ -635,13 +669,17 @@ public final class CachedAppOptimizer {
                    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);
            }
        });
    }

    @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"})
+4 −0
Original line number Diff line number Diff line
@@ -2013,6 +2013,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()) {
+5 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -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",
+101 −0
Original line number Diff line number Diff line
@@ -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.
     */
Loading