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

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

Handle ANR in a separate thread

So the ActivityManager and InputDispatcher thread won't be delayed,
e.g. process next broadcast, input event.

The thread handles ANR sequentially and is only alive if there is ANR.
If system is very slow to handle ANR which has delayed over 1 minute,
only the traces of no response process will be dumped to reduce load.

Bug: 143573504
Test: atest FrameworksServicesTests:AnrHelperTest
            FrameworksServicesTests:ProcessRecordTests
            CtsAppTestCases:ActivityManagerTest#testAppNotResponding

Change-Id: I892ea60665f072bf7673f7af96f5f1a734aa540c
parent 8927986f
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -4213,7 +4213,7 @@ public final class ActiveServices {
        }

        if (anrMessage != null) {
            proc.appNotResponding(null, null, null, null, false, anrMessage);
            mAm.mAnrHelper.appNotResponding(proc, anrMessage);
        }
    }

@@ -4238,7 +4238,7 @@ public final class ActiveServices {
        }

        if (app != null) {
            app.appNotResponding(null, null, null, null, false,
            mAm.mAnrHelper.appNotResponding(app,
                    "Context.startForegroundService() did not then call Service.startForeground(): "
                        + r);
        }
+7 −19
Original line number Diff line number Diff line
@@ -1522,6 +1522,8 @@ public class ActivityManagerService extends IActivityManager.Stub
        void onOomAdjMessage(String msg);
    }
    final AnrHelper mAnrHelper = new AnrHelper();
    /**
     * Runtime CPU use collection thread.  This object's lock is used to
     * perform synchronization with the thread (notifying it to run).
@@ -7797,13 +7799,7 @@ public class ActivityManagerService extends IActivityManager.Stub
            return;
        }
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                host.appNotResponding(
                        null, null, null, null, false, "ContentProvider not responding");
            }
        });
        mAnrHelper.appNotResponding(host, "ContentProvider not responding");
    }
    @Override
@@ -7816,13 +7812,8 @@ public class ActivityManagerService extends IActivityManager.Stub
                throw new SecurityException("Unknown process: " + callingPid);
            }
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    app.appNotResponding(
                            null, app.info, null, null, false, "App requested: " + reason);
                }
            });
            mAnrHelper.appNotResponding(app, null, app.info, null, null, false,
                    "App requested: " + reason);
        }
    }
@@ -19267,10 +19258,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        @Override
        public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) {
            synchronized (ActivityManagerService.this) {
                return ActivityManagerService.this.inputDispatchingTimedOut(
                        pid, aboveSystem, reason);
            }
            return ActivityManagerService.this.inputDispatchingTimedOut(pid, aboveSystem, reason);
        }
        @Override
@@ -19561,7 +19549,7 @@ public class ActivityManagerService extends IActivityManager.Stub
                    return true;
                }
            }
            proc.appNotResponding(activityShortComponentName, aInfo,
            mAnrHelper.appNotResponding(proc, activityShortComponentName, aInfo,
                    parentShortComponentName, parentProcess, aboveSystem, annotation);
        }
+142 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.content.pm.ApplicationInfo;
import android.os.SystemClock;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.server.wm.WindowProcessController;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * The helper class to handle no response process. An independent thread will be created on demand
 * so the caller can report the ANR without worrying about taking long time.
 */
class AnrHelper {
    private static final String TAG = TAG_WITH_CLASS_NAME ? "AnrHelper" : TAG_AM;

    /**
     * If the system is extremely slow somehow that the ANR has been pending too long for more than
     * this time, the information might be outdated. So we only the dump the unresponsive process
     * instead of including other processes to avoid making the system more busy.
     */
    private static final long EXPIRED_REPORT_TIME_MS = TimeUnit.MINUTES.toMillis(1);

    @GuardedBy("mAnrRecords")
    private final ArrayList<AnrRecord> mAnrRecords = new ArrayList<>();
    private final AtomicBoolean mRunning = new AtomicBoolean(false);

    void appNotResponding(ProcessRecord anrProcess, String annotation) {
        appNotResponding(anrProcess, null /* activityShortComponentName */, null /* aInfo */,
                null /* parentShortComponentName */, null /* parentProcess */,
                false /* aboveSystem */, annotation);
    }

    void appNotResponding(ProcessRecord anrProcess, String activityShortComponentName,
            ApplicationInfo aInfo, String parentShortComponentName,
            WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
        synchronized (mAnrRecords) {
            mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
                    parentShortComponentName, parentProcess, aboveSystem, annotation));
        }
        startAnrConsumerIfNeeded();
    }

    private void startAnrConsumerIfNeeded() {
        if (mRunning.compareAndSet(false, true)) {
            new AnrConsumerThread().start();
        }
    }

    /**
     * The thread to execute {@link ProcessRecord#appNotResponding}. It will terminate if all
     * records are handled.
     */
    private class AnrConsumerThread extends Thread {
        AnrConsumerThread() {
            super("AnrConsumer");
        }

        private AnrRecord next() {
            synchronized (mAnrRecords) {
                return mAnrRecords.isEmpty() ? null : mAnrRecords.remove(0);
            }
        }

        @Override
        public void run() {
            AnrRecord r;
            while ((r = next()) != null) {
                final long startTime = SystemClock.uptimeMillis();
                // If there are many ANR at the same time, the latency may be larger. If the latency
                // is too large, the stack trace might not be meaningful.
                final long reportLatency = startTime - r.mTimestamp;
                final boolean onlyDumpSelf = reportLatency > EXPIRED_REPORT_TIME_MS;
                r.appNotResponding(onlyDumpSelf);
                final long endTime = SystemClock.uptimeMillis();
                Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in "
                        + (endTime - startTime) + "ms, latency " + reportLatency
                        + (onlyDumpSelf ? "ms" : "ms (expired, only dump ANR app)"));
            }

            mRunning.set(false);
            synchronized (mAnrRecords) {
                // The race should be unlikely to happen. Just to make sure we don't miss.
                if (!mAnrRecords.isEmpty()) {
                    startAnrConsumerIfNeeded();
                }
            }
        }
    }

    private static class AnrRecord {
        final ProcessRecord mApp;
        final String mActivityShortComponentName;
        final String mParentShortComponentName;
        final String mAnnotation;
        final ApplicationInfo mAppInfo;
        final WindowProcessController mParentProcess;
        final boolean mAboveSystem;
        final long mTimestamp = SystemClock.uptimeMillis();

        AnrRecord(ProcessRecord anrProcess, String activityShortComponentName,
                ApplicationInfo aInfo, String parentShortComponentName,
                WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
            mApp = anrProcess;
            mActivityShortComponentName = activityShortComponentName;
            mParentShortComponentName = parentShortComponentName;
            mAnnotation = annotation;
            mAppInfo = aInfo;
            mParentProcess = parentProcess;
            mAboveSystem = aboveSystem;
        }

        void appNotResponding(boolean onlyDumpSelf) {
            mApp.appNotResponding(mActivityShortComponentName, mAppInfo,
                    mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
                    onlyDumpSelf);
        }
    }
}
+1 −18
Original line number Diff line number Diff line
@@ -198,21 +198,6 @@ public final class BroadcastQueue {
        }
    }

    private final class AppNotResponding implements Runnable {
        private final ProcessRecord mApp;
        private final String mAnnotation;

        public AppNotResponding(ProcessRecord app, String annotation) {
            mApp = app;
            mAnnotation = annotation;
        }

        @Override
        public void run() {
            mApp.appNotResponding(null, null, null, null, false, mAnnotation);
        }
    }

    BroadcastQueue(ActivityManagerService service, Handler handler,
            String name, BroadcastConstants constants, boolean allowDelayBehindServices) {
        mService = service;
@@ -1808,9 +1793,7 @@ public final class BroadcastQueue {
        scheduleBroadcastsLocked();

        if (!debugging && anrMessage != null) {
            // Post the ANR to the handler since we do not want to process ANRs while
            // potentially holding our lock.
            mHandler.post(new AppNotResponding(app, anrMessage));
            mService.mAnrHelper.appNotResponding(app, anrMessage);
        }
    }

+7 −5
Original line number Diff line number Diff line
@@ -1502,7 +1502,7 @@ class ProcessRecord implements WindowProcessListener {

    void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
            String parentShortComponentName, WindowProcessController parentProcess,
            boolean aboveSystem, String annotation) {
            boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
        ArrayList<Integer> firstPids = new ArrayList<>(5);
        SparseArray<Boolean> lastPids = new SparseArray<>(20);

@@ -1514,6 +1514,7 @@ class ProcessRecord implements WindowProcessListener {
            mService.updateCpuStatsNow();
        }

        final boolean isSilentAnr;
        synchronized (mService) {
            // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
            if (mService.mAtmInternal.isShuttingDown()) {
@@ -1544,8 +1545,9 @@ class ProcessRecord implements WindowProcessListener {
            // Dump thread traces as quickly as we can, starting with "interesting" processes.
            firstPids.add(pid);

            // Don't dump other PIDs if it's a background ANR
            if (!isSilentAnr()) {
            // Don't dump other PIDs if it's a background ANR or is requested to only dump self.
            isSilentAnr = isSilentAnr();
            if (!isSilentAnr && !onlyDumpSelf) {
                int parentPid = pid;
                if (parentProcess != null && parentProcess.getPid() > 0) {
                    parentPid = parentProcess.getPid();
@@ -1598,7 +1600,7 @@ class ProcessRecord implements WindowProcessListener {

        // don't dump native PIDs for background ANRs unless it is the process of interest
        String[] nativeProcs = null;
        if (isSilentAnr()) {
        if (isSilentAnr || onlyDumpSelf) {
            for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
                if (NATIVE_STACKS_OF_INTEREST[i].equals(processName)) {
                    nativeProcs = new String[] { processName };
@@ -1625,7 +1627,7 @@ class ProcessRecord implements WindowProcessListener {
        // To hold the start and end offset to the ANR trace file respectively.
        final long[] offsets = new long[2];
        File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
                (isSilentAnr()) ? null : processCpuTracker, (isSilentAnr()) ? null : lastPids,
                isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
                nativePids, tracesFileException, offsets);

        if (isMonitorCpuUsage()) {
Loading