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

Commit a6805188 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Relax broadcast ANR timeouts based on run_delay.

When the majority of CPU resources are being consumed by other
more-important tasks on the device, a broadcast receiver running at
a lower priority could end up triggering the ANR deadline due to
being starved for resources, which is unfair to that app.

This change relaxes the broadcast ANR timeout relative to the
amount of time a process was runnable-but-waiting, up to double the
normal ANR timeout.

Bug: 241829443
Test: atest FrameworksCoreTests:ProcessCpuTrackerTest
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Test: atest FrameworksMockingServicesTests:BroadcastQueueModernImplTest
Change-Id: I903c3fa16e4bd64f1660bd0fb2f221b7b82f3661
parent 1740af50
Loading
Loading
Loading
Loading
+26 −2
Original line number Diff line number Diff line
@@ -119,6 +119,14 @@ public class ProcessCpuTracker {
    private final String[] mProcessFullStatsStringData = new String[6];
    private final long[] mProcessFullStatsData = new long[6];

    private static final int[] PROCESS_SCHEDSTATS_FORMAT = new int[] {
            PROC_SPACE_TERM|PROC_OUT_LONG,
            PROC_SPACE_TERM|PROC_OUT_LONG,
    };

    static final int PROCESS_SCHEDSTAT_CPU_TIME = 0;
    static final int PROCESS_SCHEDSTAT_CPU_DELAY_TIME = 1;

    private static final int[] SYSTEM_CPU_FORMAT = new int[] {
        PROC_SPACE_TERM|PROC_COMBINE,
        PROC_SPACE_TERM|PROC_OUT_LONG,                  // 1: user time
@@ -617,8 +625,8 @@ public class ProcessCpuTracker {
    }

    /**
     * Returns the total time (in milliseconds) spent executing in
     * both user and system code.  Safe to call without lock held.
     * Returns the total time (in milliseconds) the given PID has spent
     * executing in both user and system code. Safe to call without lock held.
     */
    public long getCpuTimeForPid(int pid) {
        synchronized (mSinglePidStatsData) {
@@ -634,6 +642,22 @@ public class ProcessCpuTracker {
        }
    }

    /**
     * Returns the total time (in milliseconds) the given PID has spent waiting
     * in the runqueue. Safe to call without lock held.
     */
    public long getCpuDelayTimeForPid(int pid) {
        synchronized (mSinglePidStatsData) {
            final String statFile = "/proc/" + pid + "/schedstat";
            final long[] statsData = mSinglePidStatsData;
            if (Process.readProcFile(statFile, PROCESS_SCHEDSTATS_FORMAT,
                    null, statsData, null)) {
                return statsData[PROCESS_SCHEDSTAT_CPU_DELAY_TIME] / 1_000_000;
            }
            return 0;
        }
    }

    /**
     * @return time in milliseconds.
     */
+41 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.internal.os;

import static com.google.common.truth.Truth.assertThat;

import androidx.test.filters.SmallTest;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@SmallTest
@RunWith(JUnit4.class)
public class ProcessCpuTrackerTest {
    @Test
    public void testGetCpuTime() throws Exception {
        final ProcessCpuTracker tracker = new ProcessCpuTracker(false);
        assertThat(tracker.getCpuTimeForPid(android.os.Process.myPid())).isGreaterThan(0L);
    }

    @Test
    public void testGetCpuDelayTime() throws Exception {
        final ProcessCpuTracker tracker = new ProcessCpuTracker(false);
        assertThat(tracker.getCpuDelayTimeForPid(android.os.Process.myPid())).isGreaterThan(0L);
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -1892,6 +1892,12 @@ public class AppProfiler {
        }
    }

    long getCpuDelayTimeForPid(int pid) {
        synchronized (mProcessCpuTracker) {
            return mProcessCpuTracker.getCpuDelayTimeForPid(pid);
        }
    }

    List<ProcessCpuTracker.Stats> getCpuStats(Predicate<ProcessCpuTracker.Stats> predicate) {
        synchronized (mProcessCpuTracker) {
            return mProcessCpuTracker.getStats(st -> predicate.test(st));
+6 −0
Original line number Diff line number Diff line
@@ -84,6 +84,12 @@ class BroadcastProcessQueue {
     */
    @Nullable String traceTrackName;

    /**
     * Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically
     * used when deciding if we should extend the soft ANR timeout.
     */
    long lastCpuDelayTime;

    /**
     * Ordered collection of broadcasts that are waiting to be dispatched to
     * this process, as a pair of {@link BroadcastRecord} and the index into
+37 −9
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.MathUtils;
import android.util.Pair;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -211,8 +212,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    private final BroadcastConstants mBgConstants;

    private static final int MSG_UPDATE_RUNNING_LIST = 1;
    private static final int MSG_DELIVERY_TIMEOUT = 2;
    private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 3;
    private static final int MSG_DELIVERY_TIMEOUT_SOFT = 2;
    private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
    private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4;

    private void enqueueUpdateRunningList() {
        mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -225,14 +227,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        switch (msg.what) {
            case MSG_UPDATE_RUNNING_LIST: {
                synchronized (mService) {
                    updateRunningList();
                    updateRunningListLocked();
                }
                return true;
            }
            case MSG_DELIVERY_TIMEOUT: {
            case MSG_DELIVERY_TIMEOUT_SOFT: {
                synchronized (mService) {
                    finishReceiverLocked((BroadcastProcessQueue) msg.obj,
                            BroadcastRecord.DELIVERY_TIMEOUT);
                    deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj);
                }
                return true;
            }
            case MSG_DELIVERY_TIMEOUT_HARD: {
                synchronized (mService) {
                    deliveryTimeoutHardLocked((BroadcastProcessQueue) msg.obj);
                }
                return true;
            }
@@ -327,7 +334,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
     * processes and only one pending cold-start.
     */
    @GuardedBy("mService")
    private void updateRunningList() {
    private void updateRunningListLocked() {
        int avail = mRunning.length - getRunningSize();
        if (avail == 0) return;

@@ -673,9 +680,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        }

        if (mService.mProcessesReady && !r.timeoutExempt) {
            queue.lastCpuDelayTime = queue.app.getCpuDelayTime();

            final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
            mLocalHandler.sendMessageDelayed(
                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout);
                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, queue), timeout);
        }

        if (r.allowBackgroundActivityStarts) {
@@ -757,6 +766,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        }
    }

    private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue) {
        if (queue.app != null) {
            // Instead of immediately triggering an ANR, extend the timeout by
            // the amount of time the process was runnable-but-waiting; we're
            // only willing to do this once before triggering an hard ANR
            final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
            final long timeout = MathUtils.constrain(cpuDelayTime, 0, mConstants.TIMEOUT);
            mLocalHandler.sendMessageDelayed(
                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), timeout);
        } else {
            deliveryTimeoutHardLocked(queue);
        }
    }

    private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT);
    }

    @Override
    public boolean finishReceiverLocked(@NonNull ProcessRecord app, int resultCode,
            @Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort,
@@ -805,7 +832,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                        .forBroadcastReceiver("Broadcast of " + r.toShortString()));
            }
        } else {
            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT, queue);
            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
        }

        // Even if we have more broadcasts, if we've made reasonable progress
Loading