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

Commit e4cfe35e authored by Ben Miles's avatar Ben Miles
Browse files

Add TimeoutRecord

This class contains structured data about a timeout. In this context a timeout is something about to become an ANR (as long as it passes the various ANR deduping/skipping checks). Types of timeout include input dispatch timeout, broadcast receiver timeout etc.

It wraps the ANR subject and contains useful metadata like the value of the system uptime clock when the timeout actually triggered. This is foundational work so that ANR stack latency can be measured and tracked. It will also enable us to log more useful ANR information in perfetto traces.

go/timeout-record

Bug: b/239390223
Test: atest FrameworkServicesTests
Change-Id: I6541b13b4862f6878383d17ba96058b39d36718c
parent b6e84328
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -41,6 +41,8 @@ import android.os.WorkSource;
import android.util.ArraySet;
import android.util.Pair;

import com.android.internal.os.TimeoutRecord;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -440,10 +442,13 @@ public abstract class ActivityManagerInternal {

    /** Input dispatch timeout to a window, start the ANR process. Return the timeout extension,
     * in milliseconds, or 0 to abort dispatch. */
    public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason);
    public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem,
            TimeoutRecord timeoutRecord);

    public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
            ApplicationInfo aInfo, String parentShortComponentName, Object parentProc,
            boolean aboveSystem, String reason);
            boolean aboveSystem, TimeoutRecord timeoutRecord);

    /**
     * App started responding to input events. This signal can be used to abort the ANR process and
     * hide the ANR dialog.
+130 −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 android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.SystemClock;

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

/**
 * A timeout that has triggered on the system.
 *
 * @hide
 */
public class TimeoutRecord {
    /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
    @IntDef(value = {
            TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW,
            TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE,
            TimeoutKind.BROADCAST_RECEIVER,
            TimeoutKind.SERVICE_START,
            TimeoutKind.SERVICE_EXEC,
            TimeoutKind.CONTENT_PROVIDER,
            TimeoutKind.APP_REGISTERED})

    @Retention(RetentionPolicy.SOURCE)
    private @interface TimeoutKind {
        int INPUT_DISPATCH_NO_FOCUSED_WINDOW = 1;
        int INPUT_DISPATCH_WINDOW_UNRESPONSIVE = 2;
        int BROADCAST_RECEIVER = 3;
        int SERVICE_START = 4;
        int SERVICE_EXEC = 5;
        int CONTENT_PROVIDER = 6;
        int APP_REGISTERED = 7;
    }

    /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
    @TimeoutKind
    public final int mKind;

    /** Reason for the timeout. */
    public final String mReason;

    /** System uptime in millis when the timeout was triggered. */
    public final long mEndUptimeMillis;

    /**
     * Was the end timestamp taken right after the timeout triggered, before any potentially
     * expensive operations such as taking locks?
     */
    public final boolean mEndTakenBeforeLocks;

    private TimeoutRecord(@TimeoutKind int kind, @NonNull String reason, long endUptimeMillis,
            boolean endTakenBeforeLocks) {
        this.mKind = kind;
        this.mReason = reason;
        this.mEndUptimeMillis = endUptimeMillis;
        this.mEndTakenBeforeLocks = endTakenBeforeLocks;
    }

    private static TimeoutRecord endingNow(@TimeoutKind int kind, String reason) {
        long endUptimeMillis = SystemClock.uptimeMillis();
        return new TimeoutRecord(kind, reason, endUptimeMillis, /* endTakenBeforeLocks */ true);
    }

    private static TimeoutRecord endingApproximatelyNow(@TimeoutKind int kind, String reason) {
        long endUptimeMillis = SystemClock.uptimeMillis();
        return new TimeoutRecord(kind, reason, endUptimeMillis, /* endTakenBeforeLocks */ false);
    }

    /** Record for a broadcast receiver timeout. */
    @NonNull
    public static TimeoutRecord forBroadcastReceiver(@NonNull String reason) {
        return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
    }

    /** Record for an input dispatch no focused window timeout */
    @NonNull
    public static TimeoutRecord forInputDispatchNoFocusedWindow(@NonNull String reason) {
        return TimeoutRecord.endingNow(TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW, reason);
    }

    /** Record for an input dispatch window unresponsive timeout. */
    @NonNull
    public static TimeoutRecord forInputDispatchWindowUnresponsive(@NonNull String reason) {
        return TimeoutRecord.endingNow(TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE, reason);
    }

    /** Record for a service exec timeout. */
    @NonNull
    public static TimeoutRecord forServiceExec(@NonNull String reason) {
        return TimeoutRecord.endingNow(TimeoutKind.SERVICE_EXEC, reason);
    }

    /** Record for a service start timeout. */
    @NonNull
    public static TimeoutRecord forServiceStartWithEndTime(@NonNull String reason,
            long endUptimeMillis) {
        return new TimeoutRecord(TimeoutKind.SERVICE_START, reason,
                endUptimeMillis, /* endTakenBeforeLocks */ true);
    }

    /** Record for a content provider timeout. */
    @NonNull
    public static TimeoutRecord forContentProvider(@NonNull String reason) {
        return TimeoutRecord.endingApproximatelyNow(TimeoutKind.CONTENT_PROVIDER, reason);
    }

    /** Record for an app registered timeout. */
    @NonNull
    public static TimeoutRecord forApp(@NonNull String reason) {
        return TimeoutRecord.endingApproximatelyNow(TimeoutKind.APP_REGISTERED, reason);
    }
}
+13 −7
Original line number Diff line number Diff line
@@ -166,6 +166,7 @@ import com.android.internal.app.procstats.ServiceState;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
@@ -5761,7 +5762,7 @@ public final class ActiveServices {
    }

    void serviceTimeout(ProcessRecord proc) {
        String anrMessage = null;
        TimeoutRecord timeoutRecord = null;
        synchronized(mAm) {
            if (proc.isDebugging()) {
                // The app's being debugged, ignore timeout.
@@ -5796,7 +5797,8 @@ public final class ActiveServices {
                mLastAnrDump = sw.toString();
                mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
                mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
                anrMessage = "executing service " + timeout.shortInstanceName;
                String anrMessage = "executing service " + timeout.shortInstanceName;
                timeoutRecord = TimeoutRecord.forServiceExec(anrMessage);
            } else {
                Message msg = mAm.mHandler.obtainMessage(
                        ActivityManagerService.SERVICE_TIMEOUT_MSG);
@@ -5806,13 +5808,15 @@ public final class ActiveServices {
            }
        }

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

    void serviceForegroundTimeout(ServiceRecord r) {
        ProcessRecord app;
        // Grab a timestamp before lock is taken.
        long timeoutEndMs = SystemClock.uptimeMillis();
        synchronized (mAm) {
            if (!r.fgRequired || !r.fgWaiting || r.destroying) {
                return;
@@ -5836,17 +5840,19 @@ public final class ActiveServices {
                    + "Service.startForeground(): " + r;
            Message msg = mAm.mHandler.obtainMessage(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_ANR_MSG);
            TimeoutRecord timeoutRecord = TimeoutRecord.forServiceStartWithEndTime(annotation,
                    timeoutEndMs);
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = app;
            args.arg2 = annotation;
            args.arg2 = timeoutRecord;
            msg.obj = args;
            mAm.mHandler.sendMessageDelayed(msg,
                    mAm.mConstants.mServiceStartForegroundAnrDelayMs);
        }
    }

    void serviceForegroundTimeoutANR(ProcessRecord app, String annotation) {
        mAm.mAnrHelper.appNotResponding(app, annotation);
    void serviceForegroundTimeoutANR(ProcessRecord app, TimeoutRecord timeoutRecord) {
        mAm.mAnrHelper.appNotResponding(app, timeoutRecord);
    }

    public void updateServiceApplicationInfoLocked(ApplicationInfo applicationInfo) {
+16 −19
Original line number Diff line number Diff line
@@ -357,6 +357,7 @@ import com.android.internal.os.ByteTransferPipe;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.TransferPipe;
import com.android.internal.os.Zygote;
import com.android.internal.policy.AttributeCache;
@@ -1700,7 +1701,7 @@ public class ActivityManagerService extends IActivityManager.Stub
            case SERVICE_FOREGROUND_TIMEOUT_ANR_MSG: {
                SomeArgs args = (SomeArgs) msg.obj;
                mServices.serviceForegroundTimeoutANR((ProcessRecord) args.arg1,
                        (String) args.arg2);
                        (TimeoutRecord) args.arg2);
                args.recycle();
            } break;
            case SERVICE_FOREGROUND_CRASH_MSG: {
@@ -6416,6 +6417,7 @@ public class ActivityManagerService extends IActivityManager.Stub
    @Override
    public void appNotResponding(final String reason) {
        TimeoutRecord timeoutRecord = TimeoutRecord.forApp("App requested: " + reason);
        final int callingPid = Binder.getCallingPid();
        synchronized (mPidsSelfLocked) {
@@ -6425,7 +6427,7 @@ public class ActivityManagerService extends IActivityManager.Stub
            }
            mAnrHelper.appNotResponding(app, null, app.info, null, null, false,
                    "App requested: " + reason);
                    timeoutRecord);
        }
    }
@@ -17169,18 +17171,19 @@ public class ActivityManagerService extends IActivityManager.Stub
        }
        @Override
        public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) {
            return ActivityManagerService.this.inputDispatchingTimedOut(pid, aboveSystem, reason);
        public long inputDispatchingTimedOut(int pid, boolean aboveSystem,
                TimeoutRecord timeoutRecord) {
            return ActivityManagerService.this.inputDispatchingTimedOut(pid, aboveSystem,
                    timeoutRecord);
        }
        @Override
        public boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
                ApplicationInfo aInfo, String parentShortComponentName, Object parentProc,
                boolean aboveSystem, String reason) {
                boolean aboveSystem, TimeoutRecord timeoutRecord) {
            return ActivityManagerService.this.inputDispatchingTimedOut((ProcessRecord) proc,
                    activityShortComponentName, aInfo, parentShortComponentName,
                    (WindowProcessController) parentProc, aboveSystem, reason);
                    (WindowProcessController) parentProc, aboveSystem, timeoutRecord);
        }
        @Override
@@ -17742,7 +17745,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        }
    }
    long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
    long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
        if (checkCallingPermission(FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Requires permission " + FILTER_EVENTS);
        }
@@ -17753,7 +17756,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        final long timeoutMillis = proc != null ? proc.getInputDispatchingTimeoutMillis() :
                DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
        if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, reason)) {
        if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, timeoutRecord)) {
            return 0;
        }
@@ -17766,18 +17769,12 @@ public class ActivityManagerService extends IActivityManager.Stub
     */
    boolean inputDispatchingTimedOut(ProcessRecord proc, String activityShortComponentName,
            ApplicationInfo aInfo, String parentShortComponentName,
            WindowProcessController parentProcess, boolean aboveSystem, String reason) {
            WindowProcessController parentProcess, boolean aboveSystem,
            TimeoutRecord timeoutRecord) {
        if (checkCallingPermission(FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Requires permission " + FILTER_EVENTS);
        }
        final String annotation;
        if (reason == null) {
            annotation = "Input dispatching timed out";
        } else {
            annotation = "Input dispatching timed out (" + reason + ")";
        }
        if (proc != null) {
            synchronized (this) {
                if (proc.isDebugging()) {
@@ -17787,13 +17784,13 @@ public class ActivityManagerService extends IActivityManager.Stub
                if (proc.getActiveInstrumentation() != null) {
                    Bundle info = new Bundle();
                    info.putString("shortMsg", "keyDispatchingTimedOut");
                    info.putString("longMsg", annotation);
                    info.putString("longMsg", timeoutRecord.mReason);
                    finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info);
                    return true;
                }
            }
            mAnrHelper.appNotResponding(proc, activityShortComponentName, aInfo,
                    parentShortComponentName, parentProcess, aboveSystem, annotation);
                    parentShortComponentName, parentProcess, aboveSystem, timeoutRecord);
        }
        return true;
+16 −10
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.os.SystemClock;
import android.util.Slog;

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

import java.util.ArrayList;
@@ -68,15 +69,16 @@ class AnrHelper {
        mService = service;
    }

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

    void appNotResponding(ProcessRecord anrProcess, String activityShortComponentName,
            ApplicationInfo aInfo, String parentShortComponentName,
            WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
            WindowProcessController parentProcess, boolean aboveSystem,
            TimeoutRecord timeoutRecord) {
        final int incomingPid = anrProcess.mPid;
        synchronized (mAnrRecords) {
            if (incomingPid == 0) {
@@ -85,17 +87,19 @@ class AnrHelper {
                return;
            }
            if (mProcessingPid == incomingPid) {
                Slog.i(TAG, "Skip duplicated ANR, pid=" + incomingPid + " " + annotation);
                Slog.i(TAG,
                        "Skip duplicated ANR, pid=" + incomingPid + " " + timeoutRecord.mReason);
                return;
            }
            for (int i = mAnrRecords.size() - 1; i >= 0; i--) {
                if (mAnrRecords.get(i).mPid == incomingPid) {
                    Slog.i(TAG, "Skip queued ANR, pid=" + incomingPid + " " + annotation);
                    Slog.i(TAG,
                            "Skip queued ANR, pid=" + incomingPid + " " + timeoutRecord.mReason);
                    return;
                }
            }
            mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
                    parentShortComponentName, parentProcess, aboveSystem, annotation));
                    parentShortComponentName, parentProcess, aboveSystem, timeoutRecord));
        }
        startAnrConsumerIfNeeded();
    }
@@ -175,7 +179,7 @@ class AnrHelper {
        final int mPid;
        final String mActivityShortComponentName;
        final String mParentShortComponentName;
        final String mAnnotation;
        final TimeoutRecord mTimeoutRecord;
        final ApplicationInfo mAppInfo;
        final WindowProcessController mParentProcess;
        final boolean mAboveSystem;
@@ -183,12 +187,13 @@ class AnrHelper {

        AnrRecord(ProcessRecord anrProcess, String activityShortComponentName,
                ApplicationInfo aInfo, String parentShortComponentName,
                WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
                WindowProcessController parentProcess, boolean aboveSystem,
                TimeoutRecord timeoutRecord) {
            mApp = anrProcess;
            mPid = anrProcess.mPid;
            mActivityShortComponentName = activityShortComponentName;
            mParentShortComponentName = parentShortComponentName;
            mAnnotation = annotation;
            mTimeoutRecord = timeoutRecord;
            mAppInfo = aInfo;
            mParentProcess = parentProcess;
            mAboveSystem = aboveSystem;
@@ -196,7 +201,8 @@ class AnrHelper {

        void appNotResponding(boolean onlyDumpSelf) {
            mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,
                    mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
                    mParentShortComponentName, mParentProcess, mAboveSystem,
                    mTimeoutRecord,
                    onlyDumpSelf);
        }
    }
Loading