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

Commit f865a69f authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Support expired timer information in AnrTimer" into main

parents 5695f39e d8ee8184
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -220,6 +220,14 @@ public class TimeoutRecord {
        return this;
    }

    /**
     * Return the expired timer.
     */
    @Nullable
    public AutoCloseable getExpiredTimer() {
        return mExpiredTimer;
    }

    /** Close the ExpiredTimer, if one is present. getExpiredTimer will return null after this. */
    public void closeExpiredTimer() {
        try {
+64 −22
Original line number Diff line number Diff line
@@ -301,10 +301,10 @@ public class AnrTimer<V> implements AutoCloseable {
    /**
     * A target process may be modified when its timer expires.  The modification (if any) will be
     * undone if the expiration is discarded, but is persisted if the expiration is accepted.  If
     * the expiration is accepted, then a TimerLock is returned to the client.  The client must
     * close the TimerLock to complete the state machine.
     * the expiration is accepted, then a ExpiredTimer is returned to the client.  The client must
     * close the ExpiredTimer to complete the state machine.
     */
    private class TimerLock implements AutoCloseable {
    public static class ExpiredTimer implements AutoCloseable {
        // Detect failures to close.
        private final CloseGuard mGuard = new CloseGuard();

@@ -314,7 +314,11 @@ public class AnrTimer<V> implements AutoCloseable {
        // Allow multiple calls to close().
        private boolean mClosed = false;

        TimerLock() {
        // The timer ID.
        final int mTimerId;

        ExpiredTimer(int id) {
            mTimerId = id;
            mGuard.open("AnrTimer.release");
        }

@@ -401,10 +405,14 @@ public class AnrTimer<V> implements AutoCloseable {
    @GuardedBy("mLock")
    private final ArrayMap<V, Integer> mTimerIdMap = new ArrayMap<>();

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

    /** Map from timer ID to ExpiredTimer. */
    @GuardedBy("mLock")
    private final SparseArray<ExpiredTimer> mExpiredTimers = new SparseArray<>();

    /** The highwater mark of started, but not closed, timers. */
    @GuardedBy("mLock")
    private int mMaxStarted = 0;
@@ -528,7 +536,7 @@ public class AnrTimer<V> implements AutoCloseable {
        abstract boolean cancel(@NonNull V arg);

        @Nullable
        abstract TimerLock accept(@NonNull V arg);
        abstract ExpiredTimer accept(@NonNull V arg);

        abstract boolean discard(@NonNull V arg);

@@ -564,7 +572,7 @@ public class AnrTimer<V> implements AutoCloseable {
        /** accept() is a no-op when the feature is disabled. */
        @Override
        @Nullable
        TimerLock accept(@NonNull V arg) {
        ExpiredTimer accept(@NonNull V arg) {
            return null;
        }

@@ -684,28 +692,31 @@ public class AnrTimer<V> implements AutoCloseable {

        /**
         * Accept a timer in the framework-level handler.  The timeout has been accepted and the
         * client's timeout handler is executing.  If the function returns a non-null TimerLock then
         * the associated process may have been paused (or otherwise modified in preparation for
         * debugging). The TimerLock must be closed to allow the process to continue, or to be
         * dumped in an AnrReport.
         * client's timeout handler is executing.  If the function returns a non-null ExpiredTimer
         * then the associated process may have been paused (or otherwise modified in preparation
         * for debugging). The ExpiredTimer must be closed.
         */
        @Override
        @Nullable
        TimerLock accept(@NonNull V arg) {
        ExpiredTimer accept(@NonNull V arg) {
            synchronized (mLock) {
                Integer timer = removeLocked(arg);
                ExpiredTimer timer = removeLockedTimer(arg);
                if (timer == null) {
                    notFoundLocked("accept", arg);
                    return null;
                }
                // Race conditions may lead to timer acceptance after the service was closed.
                if (mNative == 0) return null;
                boolean accepted = nativeAnrTimerAccept(mNative, timer);
                boolean accepted = nativeAnrTimerAccept(mNative, timer.mTimerId);
                trace("accept", timer);
                // If "accepted" is true then the native layer has pending operations against this
                // timer.  Wrap the timer ID in a TimerLock and return it to the caller.  If
                // timer.  Wrap the timer ID in a ExpiredTimer and return it to the caller.  If
                // "accepted" is false then the native later does not have any pending operations.
                return accepted ? new TimerLock() : null;
                if (!accepted) {
                    timer.close();
                    timer = null;
                }
                return timer;
            }
        }

@@ -770,15 +781,32 @@ public class AnrTimer<V> implements AutoCloseable {
            }
        }

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

        /**
         * Delete the entries associated with arg from the maps and return the ID of the timer, if
         * any.
         * any.  If there is a ExpiredTimer present, it is closed.
         */
        @GuardedBy("mLock")
        private Integer removeLocked(V arg) {
            Integer r = mTimerIdMap.remove(arg);
            final Integer r = mTimerIdMap.remove(arg);
            if (r != null) {
                mTimerArgMap.remove(r);
                ExpiredTimer l = mExpiredTimers.removeReturnOld(r);
                if (l != null) l.close();
            }
            return r;
        }
@@ -843,9 +871,9 @@ public class AnrTimer<V> implements AutoCloseable {
    /**
     * 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.)  The
     * function stores a {@link TimerLock} in the {@link TimeoutRecord} argument.  The TimerLock
     * records information about the expired timer for retrieval during ANR report generation.
     * After this call, the timer does not exist.
     * function stores a {@link ExpiredTimer} in the {@link TimeoutRecord} argument.  The
     * ExpiredTimer records information about the expired timer for retrieval during ANR report
     * generation.  After this call, the timer does not exist.
     *
     * It is a protocol error to accept a running timer, however, the running timer will be
     * canceled.
@@ -887,7 +915,7 @@ public class AnrTimer<V> implements AutoCloseable {
    @Keep
    private boolean expire(int timerId, int pid, int uid, long elapsedMs) {
        trace("expired", timerId, pid, uid, mLabel, elapsedMs);
        V arg = null;
        final V arg;
        synchronized (mLock) {
            arg = mTimerArgMap.get(timerId);
            if (arg == null) {
@@ -896,6 +924,7 @@ public class AnrTimer<V> implements AutoCloseable {
                mTotalErrors++;
                return false;
            }
            mExpiredTimers.put(timerId, new ExpiredTimer(timerId));
            mTotalExpired++;
        }
        final Message msg = Message.obtain(mHandler, mWhat, arg);
@@ -965,6 +994,19 @@ public class AnrTimer<V> implements AutoCloseable {
        mFeature.setTime(now);
    }

    /**
     * Return the ExpiredTimer associated with a TimeoutRecord.  The TimeoutRecord is not modified.
     */
    @Nullable
    public static ExpiredTimer expiredTimer(TimeoutRecord tr) {
        AutoCloseable expiredTimer = tr.getExpiredTimer();
        if (expiredTimer instanceof ExpiredTimer lock) {
            return lock;
        } else {
            return null;
        }
    }

    /**
     * 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.
+44 −1
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.util.Log;
import androidx.test.filters.SmallTest;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.TimeoutRecord;

import org.junit.Ignore;
import org.junit.Test;
@@ -52,7 +53,7 @@ public class AnrTimerTest {
    private static final int MSG_TIMEOUT = 1;

    // The test argument includes a pid and uid, and a tag.  The tag is used to distinguish
    // different message instances.  Additional fields (like what) capture delivery information
    // different message instances.  Additional fields (like "what") capture delivery information
    // that is checked by the test.
    private static class TestArg {
        final int pid;
@@ -531,6 +532,48 @@ public class AnrTimerTest {
        }
    }

    /**
     * Test the ExpiredTimer feature.
     */
    @Test
    public void testExpiredTimer() throws Exception {
        assumeTrue(AnrTimer.nativeTimersSupported());
        AnrTimer.Args args =
                new AnrTimer.Args()
                .enable(true)
                .testMode(true);

        Helper helper = new Helper(1);
        TestArg t1 = new TestArg(1, 1);
        TestArg t2 = new TestArg(2, 2);
        try (TestAnrTimer timer = new TestAnrTimer(helper, args)) {
            Stepper stepper = new Stepper(timer, helper);

            timer.start(t1, 100);
            stepper.stepAndWait(100);
            timer.start(t2, 100);
            stepper.stepAndWait(200);
            TestResult[] result = helper.results(2);

            assertThat(timer.discard(t1)).isTrue();
            assertThat(timer.discard(t1)).isFalse();
            TimeoutRecord tr1a = TimeoutRecord.forApp("testing purposes");
            timer.accept(t1, tr1a);
            assertThat(tr1a.getExpiredTimer()).isNull();

            TimeoutRecord tr2a = TimeoutRecord.forApp("testing purposes");
            timer.accept(t2, tr2a);
            assertThat(tr2a.getExpiredTimer()).isNotNull();
            assertThat(tr2a.getExpiredTimer()).isNotNull();
            tr2a.closeExpiredTimer();
            assertThat(tr2a.getExpiredTimer()).isNull();
            assertThat(timer.discard(t2)).isFalse();
            TimeoutRecord tr2b = TimeoutRecord.forApp("testing purposes");
            timer.accept(t2, tr2b);
            assertThat(tr2b.getExpiredTimer()).isNull();
        }
    }

    /**
     * Return the dump string.
     */