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

Commit e839c2ca authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Move defer-process-config for cached process to client side

Previously the cached state highly depends on the path of oom-adj
update, that causes potential lock contention between WMS and AMS.

Because the original major problem might be the heavy IO operations
by accessing the resources when handling configuration change from
dozens of non-important processes. The client side config deferring
could be enough help for the case.

This is also the last removal of holding WM lock in the path of
oom-adj update.

Bug: 143432064
Bug: 159104503
Test: ActivityThreadTest# \
      testHandleProcessConfigurationChanged_DependOnProcessState
Change-Id: Iefc9e02aa374bc74f263cf0c10612f89b864530b
parent b7804795
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -3144,12 +3144,25 @@ public final class ActivityThread extends ClientTransactionHandler {
        }
    }

    /**
     * Returns {@code true} if the {@link android.app.ActivityManager.ProcessState} of the current
     * process is cached.
     */
    @VisibleForTesting
    public boolean isCachedProcessState() {
        synchronized (mAppThread) {
            return mLastProcessState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
        }
    }

    @Override
    public void updateProcessState(int processState, boolean fromIpc) {
        final boolean wasCached;
        synchronized (mAppThread) {
            if (mLastProcessState == processState) {
                return;
            }
            wasCached = isCachedProcessState();
            mLastProcessState = processState;
            // Defer the top state for VM to avoid aggressive JIT compilation affecting activity
            // launch time.
@@ -3166,6 +3179,24 @@ public final class ActivityThread extends ClientTransactionHandler {
                        + (fromIpc ? " (from ipc" : ""));
            }
        }

        // Handle the pending configuration if the process state is changed from cached to
        // non-cached. Except the case where there is a launching activity because the
        // LaunchActivityItem will handle it.
        if (wasCached && !isCachedProcessState() && mNumLaunchingActivities.get() == 0) {
            final Configuration pendingConfig;
            synchronized (mResourcesManager) {
                pendingConfig = mPendingConfiguration;
            }
            if (pendingConfig == null) {
                return;
            }
            if (Looper.myLooper() == mH.getLooper()) {
                handleConfigurationChanged(pendingConfig);
            } else {
                sendMessage(H.CONFIGURATION_CHANGED, pendingConfig);
            }
        }
    }

    /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
@@ -5687,6 +5718,12 @@ public final class ActivityThread extends ClientTransactionHandler {

    @Override
    public void handleConfigurationChanged(Configuration config) {
        if (isCachedProcessState()) {
            updatePendingConfiguration(config);
            // If the process is in a cached state, delay the handling until the process is no
            // longer cached.
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
        mCurDefaultDisplayDpi = config.densityDpi;
        handleConfigurationChanged(config, null /* compat */);
+49 −1
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import static org.junit.Assert.assertTrue;

import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.IApplicationThread;
@@ -57,9 +58,9 @@ import android.util.MergedConfiguration;
import android.view.Display;
import android.view.View;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;

@@ -541,6 +542,53 @@ public class ActivityThreadTest {
        });
    }

    @Test
    public void testHandleProcessConfigurationChanged_DependOnProcessState() {
        final ActivityThread activityThread = ActivityThread.currentActivityThread();
        final Configuration origConfig = activityThread.getConfiguration();
        final int newDpi = origConfig.densityDpi + 10;
        final Configuration newConfig = new Configuration(origConfig);
        newConfig.seq++;
        newConfig.densityDpi = newDpi;

        activityThread.updateProcessState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
                false /* fromIPC */);

        applyProcessConfiguration(activityThread, newConfig);
        try {
            // In the cached state, the configuration is only set as pending and not applied.
            assertEquals(origConfig.densityDpi, activityThread.getConfiguration().densityDpi);
            assertTrue(activityThread.isCachedProcessState());
        } finally {
            // The foreground state is the default state of instrumentation.
            activityThread.updateProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
                    false /* fromIPC */);
        }
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();

        try {
            // The state becomes non-cached, the pending configuration should be applied.
            assertEquals(newConfig.densityDpi, activityThread.getConfiguration().densityDpi);
            assertFalse(activityThread.isCachedProcessState());
        } finally {
            // Restore to the original configuration.
            activityThread.getConfiguration().seq = origConfig.seq - 1;
            applyProcessConfiguration(activityThread, origConfig);
        }
    }

    private static void applyProcessConfiguration(ActivityThread thread, Configuration config) {
        final ClientTransaction clientTransaction = newTransaction(thread,
                null /* activityToken */);
        clientTransaction.addCallback(ConfigurationChangeItem.obtain(config));
        final IApplicationThread appThread = thread.getApplicationThread();
        try {
            appThread.scheduleTransaction(clientTransaction);
        } catch (Exception ignored) {
        }
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
    }

    @Test
    public void testResumeAfterNewIntent() {
        final Activity activity = mActivityTestRule.launchActivity(new Intent());
+2 −5
Original line number Diff line number Diff line
@@ -246,7 +246,7 @@ class ProcessRecord implements WindowProcessListener {
    long lastTopTime;           // The last time the process was in the TOP state or greater.
    boolean reportLowMemory;    // Set to true when waiting to report low mem
    boolean empty;              // Is this an empty background process?
    private volatile boolean mCached;    // Is this a cached process?
    private boolean mCached;    // Is this a cached process?
    String adjType;             // Debugging: primary thing impacting oom_adj.
    int adjTypeCode;            // Debugging: adj code to report to app.
    Object adjSource;           // Debugging: option dependent object.
@@ -794,10 +794,7 @@ class ProcessRecord implements WindowProcessListener {
    }

    void setCached(boolean cached) {
        if (mCached != cached) {
        mCached = cached;
            mWindowProcessController.onProcCachedStateChanged(cached);
        }
    }

    @Override
+0 −27
Original line number Diff line number Diff line
@@ -1397,17 +1397,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
            return;
        }

        if (mListener.isCached()) {
            // This process is in a cached state. We will delay delivering the config change to the
            // process until the process is no longer cached.
            mHasPendingConfigurationChange = true;
            return;
        }

        dispatchConfigurationChange(config);
    }

    private void dispatchConfigurationChange(Configuration config) {
        if (mPauseConfigurationDispatchCount > 0) {
            mHasPendingConfigurationChange = true;
            return;
@@ -1553,22 +1542,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
        return false;
    }

    /**
     * Called to notify WindowProcessController of a change in the process's cached state.
     *
     * @param isCached whether or not the process is cached.
     */
    @HotPath(caller = HotPath.OOM_ADJUSTMENT)
    public void onProcCachedStateChanged(boolean isCached) {
        if (!isCached) {
            synchronized (mAtm.mGlobalLockWithoutBoost) {
                if (mHasPendingConfigurationChange) {
                    dispatchConfigurationChange(getConfiguration());
                }
            }
        }
    }

    /**
     * Called to notify {@link WindowProcessController} of a started service.
     *
+0 −26
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;

import android.Manifest;
import android.app.IApplicationThread;
@@ -159,31 +158,6 @@ public class WindowProcessControllerTests extends WindowTestsBase {
        assertEquals(expectedConfig, mWpc.getConfiguration());
    }

    @Test
    public void testDelayingConfigurationChange() {
        when(mMockListener.isCached()).thenReturn(false);

        Configuration tmpConfig = new Configuration(mWpc.getConfiguration());
        invertOrientation(tmpConfig);
        mWpc.onConfigurationChanged(tmpConfig);

        // The last reported config should be the current config as the process is not cached.
        Configuration originalConfig = new Configuration(mWpc.getConfiguration());
        assertEquals(mWpc.getLastReportedConfiguration(), originalConfig);

        when(mMockListener.isCached()).thenReturn(true);
        invertOrientation(tmpConfig);
        mWpc.onConfigurationChanged(tmpConfig);

        Configuration newConfig = new Configuration(mWpc.getConfiguration());

        // Last reported config hasn't changed because the process is in a cached state.
        assertEquals(mWpc.getLastReportedConfiguration(), originalConfig);

        mWpc.onProcCachedStateChanged(false);
        assertEquals(mWpc.getLastReportedConfiguration(), newConfig);
    }

    @Test
    public void testActivityNotOverridingSystemUiProcessConfig() {
        final ComponentName systemUiServiceComponent = mAtm.getSysUiServiceComponentLocked();