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

Commit 30838047 authored by Lee Shombert's avatar Lee Shombert Committed by Android (Google) Code Review
Browse files

Merge "Do not deliver notifications to frozen processes" into main

parents 26be9f7d 57d4dd81
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -2076,7 +2076,8 @@ public class ActivityManagerService extends IActivityManager.Stub
                app.setPersistent(true);
                app.setPid(MY_PID);
                app.mState.setMaxAdj(ProcessList.SYSTEM_ADJ);
                app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
                app.makeActive(new ApplicationThreadDeferred(mSystemThread.getApplicationThread()),
                        mProcessStats);
                app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
                addPidLocked(app);
                updateLruProcessLocked(app, false, null);
@@ -4874,7 +4875,7 @@ public class ActivityManagerService extends IActivityManager.Stub
            // Make app active after binding application or client may be running requests (e.g
            // starting activities) before it is ready.
            synchronized (mProcLock) {
                app.makeActive(thread, mProcessStats);
                app.makeActive(new ApplicationThreadDeferred(thread), mProcessStats);
                checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
            }
            app.setPendingFinishAttach(true);
+173 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;

import android.annotation.IntDef;
import android.app.IApplicationThread;
import android.os.RemoteException;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;

/**
 * A subclass of {@link IApplicationThread} that defers certain binder calls while the process is
 * paused (frozen).  Any deferred calls are executed when the process is unpaused.  In some cases,
 * multiple instances of deferred calls are collapsed into a single call when the process is
 * unpaused.
 *
 * {@hide}
 */
class ApplicationThreadDeferred extends ApplicationThreadFilter {

    static final String TAG = TAG_WITH_CLASS_NAME ? "ApplicationThreadDeferred" : TAG_AM;

    // The flag that enables the deferral behavior of this class.  If the flag is disabled then
    // the class behaves exactly like an ApplicationThreadFilter.
    private static boolean deferBindersWhenPaused() {
        return Flags.deferBindersWhenPaused();
    }

    // The list of notifications that may be deferred.
    private static final int CLEAR_DNS_CACHE = 0;
    private static final int UPDATE_TIME_ZONE = 1;
    private static final int SCHEDULE_LOW_MEMORY = 2;
    private static final int UPDATE_HTTP_PROXY = 3;
    private static final int NOTIFICATION_COUNT = 4;

    @IntDef(value = {
                CLEAR_DNS_CACHE,
                UPDATE_TIME_ZONE,
                SCHEDULE_LOW_MEMORY,
                UPDATE_HTTP_PROXY
            })
    @Retention(RetentionPolicy.SOURCE)
    private @interface NotificationType {};

    private final Object mLock = new Object();

    // If this is true, notifications should be queued for later delivery.  If this is false,
    // notifications should be delivered immediately.
    @GuardedBy("mLock")
    private boolean mPaused = false;

    // An operation is a lambda that throws an exception.
    private interface Operation {
        void run() throws RemoteException;
    }

    // The array of operations.
    @GuardedBy("mLock")
    private final Operation[] mOperations = new Operation[NOTIFICATION_COUNT];

    // The array of operations that actually pending right now.
    @GuardedBy("mLock")
    private final boolean[] mPending = new boolean[NOTIFICATION_COUNT];

    // When true, binder calls to paused processes will be deferred until the process is unpaused.
    private final boolean mDefer;

    /** Create an instance with a base thread and a deferral enable flag. */
    @VisibleForTesting
    public ApplicationThreadDeferred(IApplicationThread thread, boolean defer) {
        super(thread);

        mDefer = defer;

        mOperations[CLEAR_DNS_CACHE] = () -> { super.clearDnsCache(); };
        mOperations[UPDATE_TIME_ZONE] = () -> { super.updateTimeZone(); };
        mOperations[SCHEDULE_LOW_MEMORY] = () -> { super.scheduleLowMemory(); };
        mOperations[UPDATE_HTTP_PROXY] = () -> { super.updateHttpProxy(); };
    }

    /** Create an instance with a base flag, using the system deferral enable flag. */
    public ApplicationThreadDeferred(IApplicationThread thread) {
        this(thread, deferBindersWhenPaused());
    }

    /** The process is being paused.  Start deferring calls. */
    void onProcessPaused() {
        synchronized (mLock) {
            mPaused = true;
        }
    }

    /** The process is no longer paused.  Drain any deferred calls. */
    void onProcessUnpaused() {
        synchronized (mLock) {
            mPaused = false;
            try {
                for (int i = 0; i < mOperations.length; i++) {
                    if (mPending[i]) {
                        mOperations[i].run();
                    }
                }
            } catch (RemoteException e) {
                // Swallow the exception.  The caller is not expecting it.  Remote exceptions
                // happen if a has process died; there is no need to report it here.
            } finally {
                Arrays.fill(mPending, false);
            }
        }
    }

    /** The pause operation has been canceled.  Drain any deferred calls. */
    void onProcessPausedCancelled() {
        onProcessUnpaused();
    }

    /**
     * If the thread is not paused, execute the operation.  Otherwise, save it to the pending
     * list.
     */
    private void execute(@NotificationType int tag) throws RemoteException {
        synchronized (mLock) {
            if (mPaused && mDefer) {
                mPending[tag] = true;
                return;
            }
        }
        // Outside the synchronization block to avoid contention.
        mOperations[tag].run();
    }

    @Override
    public void clearDnsCache() throws RemoteException {
        execute(CLEAR_DNS_CACHE);
    }

    @Override
    public void updateTimeZone() throws RemoteException {
        execute(UPDATE_TIME_ZONE);
    }

    @Override
    public void scheduleLowMemory() throws RemoteException {
        execute(SCHEDULE_LOW_MEMORY);
    }

    @Override
    public void updateHttpProxy() throws RemoteException {
        execute(UPDATE_HTTP_PROXY);
    }
}
+603 −0

File added.

Preview size limit exceeded, changes collapsed.

+8 −5
Original line number Diff line number Diff line
@@ -151,7 +151,7 @@ class ProcessRecord implements WindowProcessListener {
     * (in which case we are in the process of launching the app).
     */
    @CompositeRWLock({"mService", "mProcLock"})
    private IApplicationThread mThread;
    private ApplicationThreadDeferred mThread;

    /**
     * Instance of {@link #mThread} that will always meet the {@code oneway}
@@ -737,15 +737,15 @@ class ProcessRecord implements WindowProcessListener {
    }

    @GuardedBy({"mService", "mProcLock"})
    public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
    public void makeActive(ApplicationThreadDeferred thread, ProcessStatsService tracker) {
        mProfile.onProcessActive(thread, tracker);
        mThread = thread;
        if (mPid == Process.myPid()) {
            mOnewayThread = new SameProcessApplicationThread(thread, FgThread.getHandler());
            mOnewayThread = new SameProcessApplicationThread(mThread, FgThread.getHandler());
        } else {
            mOnewayThread = thread;
            mOnewayThread = mThread;
        }
        mWindowProcessController.setThread(thread);
        mWindowProcessController.setThread(mThread);
        if (mWindowProcessController.useFifoUiScheduling()) {
            mService.mSpecifiedFifoProcesses.add(this);
        }
@@ -1436,14 +1436,17 @@ class ProcessRecord implements WindowProcessListener {

    void onProcessFrozen() {
        mProfile.onProcessFrozen();
        if (mThread != null) mThread.onProcessPaused();
    }

    void onProcessUnfrozen() {
        if (mThread != null) mThread.onProcessUnpaused();
        mProfile.onProcessUnfrozen();
        mServices.onProcessUnfrozen();
    }

    void onProcessFrozenCancelled() {
        if (mThread != null) mThread.onProcessPausedCancelled();
        mServices.onProcessFrozenCancelled();
    }

+8 −0
Original line number Diff line number Diff line
@@ -176,3 +176,11 @@ flag {
    bug: "330682397"
    is_fixed_read_only: true
}

flag {
    name: "defer_binders_when_paused"
    namespace: "system_performance"
    is_fixed_read_only: true
    description: "Defer submitting binder calls to paused processes."
    bug: "327038797"
}
Loading