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

Commit 94184f89 authored by Lee Shombert's avatar Lee Shombert
Browse files

Native ANR timer implementation

This provides a native ANR timer service.  The service depends on the
timer_fd Linux utility, which does not exist on Windows, so the
service is unavailable there. The AnrTimer service is disabled on
systems that cannot support the native service.

Test: atest
 * FrameworksServicesTests:com.android.server.am
 * FrameworksMockingServicesTests:com.android.server.am
 * CtsAppTestCases
 * FrameworksServicesTests:AnrTimerTest

Bug: 282428924

Change-Id: Ie360232c090255487cd48aeedd19841fdb9ddab3
parent 5df012e4
Loading
Loading
Loading
Loading
+430 −51
Original line number Diff line number Diff line
@@ -16,21 +16,30 @@

package com.android.server.utils;

import static android.text.TextUtils.formatSimple;

import android.annotation.NonNull;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.os.Trace;
import android.text.TextUtils;
import android.text.format.TimeMigrationUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Keep;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.RingBuffer;

import java.lang.ref.WeakReference;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Objects;

/**
@@ -60,9 +69,14 @@ import java.util.Objects;
 * is restarted with the extension timeout.  If extensions are disabled or if the extension is zero,
 * the client process is notified of the expiration.
 *
 * <p>Instances use native resources but not system resources when the feature is enabled.
 * Instances should be explicitly closed unless they are being closed as part of process
 * exit. (So, instances in system server generally need not be explicitly closed since they are
 * created during process start and will last until process exit.)
 *
 * @hide
 */
public class AnrTimer<V> {
public class AnrTimer<V> implements AutoCloseable {

    /**
     * The log tag.
@@ -86,6 +100,12 @@ public class AnrTimer<V> {
     */
    private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;

    /**
     * Enable tracing from the time a timer expires until it is accepted or discarded.  This is
     * used to diagnose long latencies in the client.
     */
    private static final boolean ENABLE_TRACING = false;

    /**
     * Return true if the feature is enabled.  By default, the value is take from the Flags class
     * but it can be changed for local testing.
@@ -103,6 +123,9 @@ public class AnrTimer<V> {
        }
    }

    /** The default injector. */
    private static final Injector sDefaultInjector = new Injector();

    /**
     * An error is defined by its issue, the operation that detected the error, the tag of the
     * affected service, a short stack of the bad call, and the stringified arg associated with
@@ -160,41 +183,46 @@ public class AnrTimer<V> {
    /** A lock for the AnrTimer instance. */
    private final Object mLock = new Object();

    /**
     * The total number of timers started.
     */
    /** The map from client argument to the associated timer ID. */
    @GuardedBy("mLock")
    private final ArrayMap<V, Integer> mTimerIdMap = new ArrayMap<>();

    /** Reverse map from timer ID to client argument. */
    @GuardedBy("mLock")
    private final SparseArray<V> mTimerArgMap = new SparseArray<>();

    /** The highwater mark of started, but not closed, timers. */
    @GuardedBy("mLock")
    private int mMaxStarted = 0;

    /** The total number of timers started. */
    @GuardedBy("mLock")
    private int mTotalStarted = 0;

    /**
     * The total number of errors detected.
     */
    /** The total number of errors detected. */
    @GuardedBy("mLock")
    private int mTotalErrors = 0;

    /**
     * The handler for messages sent from this instance.
     */
    /** The total number of timers that have expired. */
    @GuardedBy("mLock")
    private int mTotalExpired = 0;

    /** The handler for messages sent from this instance. */
    private final Handler mHandler;

    /**
     * The message type for messages sent from this interface.
     */
    /** The message type for messages sent from this interface. */
    private final int mWhat;

    /**
     * A label that identifies the AnrTimer associated with a Timer in log messages.
     */
    /** A label that identifies the AnrTimer associated with a Timer in log messages. */
    private final String mLabel;

    /**
     * Whether this timer instance supports extending timeouts.
     */
    /** Whether this timer instance supports extending timeouts. */
    private final boolean mExtend;

    /**
     * The top-level switch for the feature enabled or disabled.
     */
    /** The injector used to create this instance.  This is only used for testing. */
    private final Injector mInjector;

    /** The top-level switch for the feature enabled or disabled. */
    private final FeatureSwitch mFeature;

    /**
@@ -223,7 +251,27 @@ public class AnrTimer<V> {
        mWhat = what;
        mLabel = label;
        mExtend = extend;
        mFeature = new FeatureDisabled();
        mInjector = injector;
        boolean enabled = mInjector.anrTimerServiceEnabled() && nativeTimersSupported();
        mFeature = createFeatureSwitch(enabled);
    }

    // Return the correct feature.  FeatureEnabled is returned if and only if the feature is
    // flag-enabled and if the native shadow was successfully created.  Otherwise, FeatureDisabled
    // is returned.
    private FeatureSwitch createFeatureSwitch(boolean enabled) {
        if (!enabled) {
            return new FeatureDisabled();
        } else {
            try {
                return new FeatureEnabled();
            } catch (RuntimeException e) {
                // Something went wrong in the native layer.  Log the error and fall back on the
                // feature-disabled logic.
                Log.e(TAG, e.toString());
                return new FeatureDisabled();
            }
        }
    }

    /**
@@ -245,7 +293,7 @@ public class AnrTimer<V> {
     * @param extend A flag to indicate if expired timers can be granted extensions.
     */
    public AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
        this(handler, what, label, extend, new Injector());
        this(handler, what, label, extend, sDefaultInjector);
    }

    /**
@@ -271,6 +319,27 @@ public class AnrTimer<V> {
        return mFeature.enabled();
    }

    /**
     * Start a trace on the timer.  The trace is laid down in the AnrTimerTrack.
     */
    private void traceBegin(int timerId, int pid, int uid, String what) {
        if (ENABLE_TRACING) {
            final String label = formatSimple("%s(%d,%d,%s)", what, pid, uid, mLabel);
            final int cookie = timerId;
            Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
        }
    }

    /**
     * End a trace on the timer.
     */
    private void traceEnd(int timerId) {
        if (ENABLE_TRACING) {
            final int cookie = timerId;
            Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
        }
    }

    /**
     * The FeatureSwitch class provides a quick switch between feature-enabled behavior and
     * feature-disabled behavior.
@@ -278,13 +347,17 @@ public class AnrTimer<V> {
    private abstract class FeatureSwitch {
        abstract void start(@NonNull V arg, int pid, int uid, long timeoutMs);

        abstract void cancel(@NonNull V arg);
        abstract boolean cancel(@NonNull V arg);

        abstract void accept(@NonNull V arg);
        abstract boolean accept(@NonNull V arg);

        abstract void discard(@NonNull V arg);
        abstract boolean discard(@NonNull V arg);

        abstract boolean enabled();

        abstract void dump(PrintWriter pw, boolean verbose);

        abstract void close();
    }

    /**
@@ -301,18 +374,21 @@ public class AnrTimer<V> {

        /** Cancel a timer by removing the message from the client's handler. */
        @Override
        void cancel(@NonNull V arg) {
        boolean cancel(@NonNull V arg) {
            mHandler.removeMessages(mWhat, arg);
            return true;
        }

        /** accept() is a no-op when the feature is disabled. */
        @Override
        void accept(@NonNull V arg) {
        boolean accept(@NonNull V arg) {
            return true;
        }

        /** discard() is a no-op when the feature is disabled. */
        @Override
        void discard(@NonNull V arg) {
        boolean discard(@NonNull V arg) {
            return true;
        }

        /** The feature is not enabled. */
@@ -320,12 +396,179 @@ public class AnrTimer<V> {
        boolean enabled() {
            return false;
        }

        /** dump() is a no-op when the feature is disabled. */
        @Override
        void dump(PrintWriter pw, boolean verbose) {
        }

        /** close() is a no-op when the feature is disabled. */
        @Override
        void close() {
        }
    }

    /**
     * A static list of AnrTimer instances.  The list is traversed by dumpsys.  Only instances
     * using native resources are included.
     */
    @GuardedBy("sAnrTimerList")
    private static final LongSparseArray<WeakReference<AnrTimer>> sAnrTimerList =
        new LongSparseArray<>();

    /**
     * The FeatureEnabled class enables the AnrTimer logic.  It is used when the AnrTimer service
     * is enabled via Flags.anrTimerServiceEnabled.
     */
    private class FeatureEnabled extends FeatureSwitch {

        /**
         * The native timer that supports this instance. The value is set to non-zero when the
         * native timer is created and it is set back to zero when the native timer is freed.
         */
        private long mNative = 0;

        /** Fetch the native tag (an integer) for the given label. */
        FeatureEnabled() {
            mNative = nativeAnrTimerCreate(mLabel);
            if (mNative == 0) throw new IllegalArgumentException("unable to create native timer");
            synchronized (sAnrTimerList) {
                sAnrTimerList.put(mNative, new WeakReference(AnrTimer.this));
            }
        }

        /**
         * Start a timer.
         */
        @Override
        void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
            synchronized (mLock) {
                if (mTimerIdMap.containsKey(arg)) {
                    // There is an existing timer.  Cancel it.
                    cancel(arg);
                }
                int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs, mExtend);
                if (timerId > 0) {
                    mTimerIdMap.put(arg, timerId);
                    mTimerArgMap.put(timerId, arg);
                    mTotalStarted++;
                    mMaxStarted = Math.max(mMaxStarted, mTimerIdMap.size());
                } else {
                    throw new RuntimeException("unable to start timer");
                }
            }
        }

        /**
         * Cancel a timer.  No error is reported if the timer is not found because some clients
         * cancel timers from common code that runs even if a timer was never started.
         */
        @Override
        boolean cancel(@NonNull V arg) {
            synchronized (mLock) {
                Integer timer = removeLocked(arg);
                if (timer == null) {
                    return false;
                }
                if (!nativeAnrTimerCancel(mNative, timer)) {
                    // There may be an expiration message in flight.  Cancel it.
                    mHandler.removeMessages(mWhat, arg);
                    return false;
                }
                return true;
            }
        }

        /**
         * Accept a timer in the framework-level handler.  The timeout has been accepted and the
         * timeout handler is executing.
         */
        @Override
        boolean accept(@NonNull V arg) {
            synchronized (mLock) {
                Integer timer = removeLocked(arg);
                if (timer == null) {
                    notFoundLocked("accept", arg);
                    return false;
                }
                nativeAnrTimerAccept(mNative, timer);
                traceEnd(timer);
                return true;
            }
        }

        /**
         * Discard a timer in the framework-level handler.  For whatever reason, the timer is no
         * longer interesting.  No statistics are collected.  Return false if the time was not
         * found.
         */
        @Override
        boolean discard(@NonNull V arg) {
            synchronized (mLock) {
                Integer timer = removeLocked(arg);
                if (timer == null) {
                    notFoundLocked("discard", arg);
                    return false;
                }
                nativeAnrTimerDiscard(mNative, timer);
                traceEnd(timer);
                return true;
            }
        }

        /** The feature is enabled. */
        @Override
        boolean enabled() {
            return true;
        }

        /** Dump statistics from the native layer. */
        @Override
        void dump(PrintWriter pw, boolean verbose) {
            synchronized (mLock) {
                if (mNative != 0) {
                    nativeAnrTimerDump(mNative, verbose);
                } else {
                    pw.println("closed");
                }
            }
        }

        /** Free native resources. */
        @Override
        void close() {
            // Remove self from the list of active timers.
            synchronized (sAnrTimerList) {
                sAnrTimerList.remove(mNative);
            }
            synchronized (mLock) {
                if (mNative != 0) nativeAnrTimerClose(mNative);
                mNative = 0;
            }
        }

        /**
         * Delete the entries associated with arg from the maps and return the ID of the timer, if
         * any.
         */
        @GuardedBy("mLock")
        private Integer removeLocked(V arg) {
            Integer r = mTimerIdMap.remove(arg);
            if (r != null) {
                synchronized (mTimerArgMap) {
                    mTimerArgMap.remove(r);
                }
            }
            return r;
        }
    }

    /**
     * Start a timer associated with arg.  The same object must be used to cancel, accept, or
     * discard a timer later.  If a timer already exists with the same arg, then the existing timer
     * is canceled and a new timer is created.
     * is canceled and a new timer is created.  The timeout is signed but negative delays are
     * nonsensical.  Rather than throw an exception, timeouts less than 0ms are forced to 0ms.  This
     * allows a client to deliver an immediate timeout via the AnrTimer.
     *
     * @param arg The key by which the timer is known.  This is never examined or modified.
     * @param pid The Linux process ID of the target being timed.
@@ -333,25 +576,39 @@ public class AnrTimer<V> {
     * @param timeoutMs The timer timeout, in milliseconds.
     */
    public void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
        if (timeoutMs < 0) timeoutMs = 0;
        mFeature.start(arg, pid, uid, timeoutMs);
    }

    /**
     * Cancel the running timer associated with arg.  The timer is forgotten.  If the timer has
     * expired, the call is treated as a discard.  No errors are reported if the timer does not
     * exist or if the timer has expired.
     * expired, the call is treated as a discard.  The function returns true if a running timer was
     * found, and false if an expired timer was found or if no timer was found.  After this call,
     * the timer does not exist.
     *
     * Note: the return value is always true if the feature is not enabled.
     *
     * @param arg The key by which the timer is known.  This is never examined or modified.
     * @return True if a running timer was canceled.
     */
    public void cancel(@NonNull V arg) {
        mFeature.cancel(arg);
    public boolean cancel(@NonNull V arg) {
        return mFeature.cancel(arg);
    }

    /**
     * Accept the expired timer associated with arg.  This indicates that the caller considers the
     * timer expiration to be a true ANR.  (See {@link #discard} for an alternate response.)  It is
     * an error to accept a running timer, however the running timer will be canceled.
     * timer expiration to be a true ANR.  (See {@link #discard} for an alternate response.)  The
     * function returns true if an expired timer was found and false if a running timer was found or
     * if no timer was found.  After this call, the timer does not exist.  It is an error to accept
     * a running timer, however, the running timer will be canceled.
     *
     * Note: the return value is always true if the feature is not enabled.
     *
     * @param arg The key by which the timer is known.  This is never examined or modified.
     * @return True if an expired timer was accepted.
     */
    public void accept(@NonNull V arg) {
        mFeature.accept(arg);
    public boolean accept(@NonNull V arg) {
        return mFeature.accept(arg);
    }

    /**
@@ -359,11 +616,57 @@ public class AnrTimer<V> {
     * timer expiration to be a false ANR.  ((See {@link #accept} for an alternate response.)  One
     * reason to discard an expired timer is if the process being timed was also being debugged:
     * such a process could be stopped at a breakpoint and its failure to respond would not be an
     * error.  It is an error to discard a running timer, however the running timer will be
     * canceled.
     * error.  After this call thie timer does not exist. It is an error to discard a running timer,
     * however the running timer will be canceled.
     *
     * Note: the return value is always true if the feature is not enabled.
     *
     * @param arg The key by which the timer is known.  This is never examined or modified.
     * @return True if an expired timer was discarded.
     */
    public boolean discard(@NonNull V arg) {
        return mFeature.discard(arg);
    }

    /**
     * The notifier that a timer has fired.  The timerId and original pid/uid are supplied.  This
     * method is called from native code.  This method takes mLock so that a timer cannot expire
     * in the middle of another operation (like start or cancel).
     */
    @Keep
    private boolean expire(int timerId, int pid, int uid) {
        traceBegin(timerId, pid, uid, "expired");
        V arg = null;
        synchronized (mLock) {
            arg = mTimerArgMap.get(timerId);
            if (arg == null) {
                Log.e(TAG, formatSimple("failed to expire timer %s:%d : arg not found",
                                mLabel, timerId));
                mTotalErrors++;
                return false;
            }
            mTotalExpired++;
        }
        mHandler.sendMessage(Message.obtain(mHandler, mWhat, arg));
        return true;
    }

    /**
     * Close the object and free any native resources.
     */
    public void close() {
        mFeature.close();
    }

    /**
     * Ensure any native resources are freed when the object is GC'ed.  Best practice is to close
     * the object explicitly, but overriding finalize() avoids accidental leaks.
     */
    public void discard(@NonNull V arg) {
        mFeature.discard(arg);
    @SuppressWarnings("Finalize")
    @Override
    protected void finalize() throws Throwable {
        close();
        super.finalize();
    }

    /**
@@ -373,8 +676,11 @@ public class AnrTimer<V> {
        synchronized (mLock) {
            pw.format("timer: %s\n", mLabel);
            pw.increaseIndent();
            pw.format("started=%d errors=%d\n", mTotalStarted, mTotalErrors);
            pw.format("started=%d maxStarted=%d running=%d expired=%d errors=%d\n",
                    mTotalStarted, mMaxStarted, mTimerIdMap.size(),
                    mTotalExpired, mTotalErrors);
            pw.decreaseIndent();
            mFeature.dump(pw, false);
        }
    }

@@ -385,6 +691,13 @@ public class AnrTimer<V> {
        DEBUG = f;
    }

    /**
     * The current time in milliseconds.
     */
    private static long now() {
        return SystemClock.uptimeMillis();
    }

    /**
     * Dump all errors to the output stream.
     */
@@ -422,23 +735,89 @@ public class AnrTimer<V> {
        mTotalErrors++;
    }

    /**
     * Log an error about  a timer not found.
     */
    /** Record an error about a timer not found. */
    @GuardedBy("mLock")
    private void notFoundLocked(String operation, Object arg) {
        recordErrorLocked(operation, "notFound", arg);
    }

    /**
     * Dumpsys output.
     */
    public static void dump(@NonNull PrintWriter pw, boolean verbose) {
    /** Dumpsys output, allowing for overrides. */
    @VisibleForTesting
    static void dump(@NonNull PrintWriter pw, boolean verbose, @NonNull Injector injector) {
        if (!injector.anrTimerServiceEnabled()) return;

        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
        ipw.println("AnrTimer statistics");
        ipw.increaseIndent();
        synchronized (sAnrTimerList) {
            final int size = sAnrTimerList.size();
            ipw.println("reporting " + size + " timers");
            for (int i = 0; i < size; i++) {
                AnrTimer a = sAnrTimerList.valueAt(i).get();
                if (a != null) a.dump(ipw);
            }
        }
        if (verbose) dumpErrors(ipw);
        ipw.format("AnrTimerEnd\n");
        ipw.decreaseIndent();
    }

    /** Dumpsys output.  There is no output if the feature is not enabled. */
    public static void dump(@NonNull PrintWriter pw, boolean verbose) {
        dump(pw, verbose, sDefaultInjector);
    }

    /**
     * Return true if the native timers are supported.  Native timers are supported if the method
     * nativeAnrTimerSupported() can be executed and it returns true.
     */
    private static boolean nativeTimersSupported() {
        try {
            return nativeAnrTimerSupported();
        } catch (java.lang.UnsatisfiedLinkError e) {
            return false;
        }
    }

    /**
     * Native methods
     */

    /** Return true if the native AnrTimer code is operational. */
    private static native boolean nativeAnrTimerSupported();

    /**
     * Create a new native timer with the given key and name.  The key is not used by the native
     * code but it is returned to the Java layer in the expiration handler.  The name is only for
     * logging.  Unlike the other methods, this is an instance method: the "this" parameter is
     * passed into the native layer.
     */
    private native long nativeAnrTimerCreate(String name);

    /** Release the native resources.  No further operations are premitted. */
    private static native int nativeAnrTimerClose(long service);

    /** Start a timer and return its ID.  Zero is returned on error. */
    private static native int nativeAnrTimerStart(long service, int pid, int uid, long timeoutMs,
            boolean extend);

    /**
     * Cancel a timer by ID.  Return true if the timer was running and canceled.  Return false if
     * the timer was not found or if the timer had already expired.
     */
    private static native boolean nativeAnrTimerCancel(long service, int timerId);

    /** Accept an expired timer by ID.  Return true if the timer was found. */
    private static native boolean nativeAnrTimerAccept(long service, int timerId);

    /** Discard an expired timer by ID.  Return true if the timer was found.  */
    private static native boolean nativeAnrTimerDiscard(long service, int timerId);

    /** Prod the native library to log a few statistics. */
    private static native void nativeAnrTimerDump(long service, boolean verbose);

    // This is not a native method but it is a native interface, in the sense that it is called from
    // the native layer to report timer expiration.  The function must return true if the expiration
    // message is delivered to the upper layers and false if it could not be delivered.
    // private boolean expire(int timerId, int pid, int uid);
}
+8 −0
Original line number Diff line number Diff line
@@ -79,6 +79,7 @@ cc_library_static {
        ":lib_cachedAppOptimizer_native",
        ":lib_gameManagerService_native",
        ":lib_oomConnection_native",
        ":lib_anrTimer_native",
    ],

    include_dirs: [
@@ -246,3 +247,10 @@ filegroup {
    name: "lib_oomConnection_native",
    srcs: ["com_android_server_am_OomConnection.cpp"],
}

filegroup {
    name: "lib_anrTimer_native",
    srcs: [
        "com_android_server_utils_AnrTimer.cpp",
    ],
}
+918 −0

File added.

Preview size limit exceeded, changes collapsed.

+2 −0
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
int register_android_server_utils_AnrTimer(JNIEnv *env);
int register_android_server_am_OomConnection(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
@@ -113,6 +114,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
    register_android_server_storage_AppFuse(env);
    register_android_server_SyntheticPasswordManager(env);
    register_android_hardware_display_DisplayViewport(env);
    register_android_server_utils_AnrTimer(env);
    register_android_server_am_OomConnection(env);
    register_android_server_am_CachedAppOptimizer(env);
    register_android_server_am_LowMemDetector(env);
+2 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ cc_library_shared {
        ":lib_cachedAppOptimizer_native",
        ":lib_gameManagerService_native",
        ":lib_oomConnection_native",
        ":lib_anrTimer_native",
        "onload.cpp",
    ],

Loading