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

Commit 34116940 authored by Lee Shombert's avatar Lee Shombert
Browse files

Connect the JobService timeout to AnrTimer

JobServiceContext now uses AnrTimer to timeout ANRs instead of a plain
MessageQueue.  There is one AnrTimer instance per JobServiceContext,
because each JobServiceContext has its own Handler.  This adds about
350 bytes to each JobServiceContext.

Timers created by JobServiceContext have a valid UID but a zero PID.
The PID is difficult to find and is not necessary at this time.

AnrTimer has been slightly simplified (it is no longer abstract) but
the changes do not affect any existing clients.  The native side no
longer generates an informational message when a timer is started with
a zero pid.

Tested with a manual build that induced a timeout

Flag: com.android.server.utils.anr_timer_job_service
Bug: 408440679
Test: atest
 * FrameworksMockingServicesTests_com_android_server_job

Change-Id: I0f80ce8b0939fc81eb67c13a8d0b279e4686c4ea
parent a88fe5ca
Loading
Loading
Loading
Loading
+49 −7
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ import com.android.modules.expresslog.Histogram;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus;
import com.android.server.utils.AnrTimer;

import java.util.Objects;

@@ -319,6 +320,40 @@ public final class JobServiceContext implements ServiceConnection {
        }
    }

    // All instances of JobAnrTimer share the same arguments.
    private static final AnrTimer.Args sAnrTimerArgs =
            new AnrTimer.Args().enable(com.android.server.utils.Flags.anrTimerJobService());

    /**
     * An AnrTimer for the JobServiceContext.  There is one instance for each JobServiceContext
     * (these objects are not large).  For convenience, simple no-argument methods are provided
     * which use 'this'.
     */
    private class JobAnrTimer extends AnrTimer<JobCallback> {
        JobAnrTimer() {
            super(mCallbackHandler, MSG_TIMEOUT, "JobScheduler", sAnrTimerArgs);
        }

        public void start(long timeout) {
            start(mRunningCallback, /* pid */ 0, mRunningJob.getUid(), timeout);
        }

        public boolean cancel() {
            return cancel(mRunningCallback);
        }

        public void accept(TimeoutRecord tr) {
            accept(mRunningCallback, tr);
        }

        public boolean discard() {
            return discard(mRunningCallback);
        }
    }

    // The AnrTimer for this instance.
    private final JobAnrTimer mAnrTimer;

    JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
            JobNotificationCoordinator notificationCoordinator,
            IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) {
@@ -337,6 +372,7 @@ public final class JobServiceContext implements ServiceConnection {
        mAvailable = true;
        mVerb = VERB_FINISHED;
        mPreferredUid = NO_PREFERRED_UID;
        mAnrTimer = new JobAnrTimer();
    }

    /**
@@ -1178,6 +1214,7 @@ public final class JobServiceContext implements ServiceConnection {
                            handleOpTimeoutLocked();
                        } else {
                            JobCallback jc = (JobCallback)message.obj;
                            mAnrTimer.discard(jc);
                            StringBuilder sb = new StringBuilder(128);
                            sb.append("Ignoring timeout of no longer active job");
                            if (jc.mStoppedReason != null) {
@@ -1434,6 +1471,8 @@ public final class JobServiceContext implements ServiceConnection {
                            mRunningJob.getUid()));
                break;
            case VERB_EXECUTING:
                // This is the tricky one.  Some of banches here accept the timeout and some
                // discard it.
                if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) {
                    if (mService.isReadyToBeExecutedLocked(mRunningJob, false)) {
                        // Job became ready again while we were waiting to stop it (for example,
@@ -1448,6 +1487,7 @@ public final class JobServiceContext implements ServiceConnection {
                        mParams.setStopReason(mPendingStopReason, mPendingInternalStopReason,
                                mPendingDebugStopReason);
                        sendStopMessageLocked(mPendingDebugStopReason);
                        mAnrTimer.discard();
                        break;
                    }
                }
@@ -1482,6 +1522,7 @@ public final class JobServiceContext implements ServiceConnection {
                    mParams.setStopReason(stopReason,
                                    internalStopReason, debugStopReason.toString());
                    sendStopMessageLocked(stopMessage.toString());
                    mAnrTimer.discard();
                } else if (nowElapsed >= earliestStopTimeElapsed) {
                    // We've given the app the minimum execution time. See if we should stop it or
                    // let it continue running
@@ -1523,6 +1564,7 @@ public final class JobServiceContext implements ServiceConnection {
            default:
                Slog.e(TAG, "Handling timeout for an invalid job state: "
                        + getRunningJobNameLocked() + ", dropping.");
                mAnrTimer.discard();
                closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout");
        }
    }
@@ -1566,9 +1608,12 @@ public final class JobServiceContext implements ServiceConnection {
                    debugReason);
        }
        if (triggerAnr) {
            final TimeoutRecord tr = TimeoutRecord.forJobService(anrMessage);
            mAnrTimer.accept(tr);
            mActivityManagerInternal.appNotResponding(
                    mRunningJob.serviceProcessName, mRunningJob.getUid(),
                    TimeoutRecord.forJobService(anrMessage));
                    mRunningJob.serviceProcessName, mRunningJob.getUid(), tr);
        } else {
            mAnrTimer.discard();
        }
        closeAndCleanupJobLocked(reschedule, debugReason);
    }
@@ -1765,8 +1810,6 @@ public final class JobServiceContext implements ServiceConnection {
     * on with life.
     */
    private void scheduleOpTimeOutLocked() {
        removeOpTimeOutLocked();

        final long timeoutMillis;
        switch (mVerb) {
            case VERB_EXECUTING:
@@ -1801,13 +1844,12 @@ public final class JobServiceContext implements ServiceConnection {
                    mRunningJob.getServiceComponent().getShortClassName() + "' jId: " +
                    mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s");
        }
        Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback);
        mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
        mAnrTimer.start(timeoutMillis);
        mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;
    }

    private void removeOpTimeOutLocked() {
        mCallbackHandler.removeMessages(MSG_TIMEOUT);
        mAnrTimer.cancel();
    }

    void dumpLocked(IndentingPrintWriter pw, final long nowElapsed) {
+25 −4
Original line number Diff line number Diff line
@@ -84,7 +84,7 @@ import java.util.Objects;
 *
 * @hide
 */
public abstract class AnrTimer<V> implements AutoCloseable {
public class AnrTimer<V> implements AutoCloseable {

    /**
     * The log tag.
@@ -113,14 +113,18 @@ public abstract class AnrTimer<V> implements AutoCloseable {
     * is no valid pid available.
     * @return a valid pid or zero.
     */
    public abstract int getPid(V obj);
    public int getPid(V obj) {
        return 0;
    }

    /**
     * Fetch the Linux uid from the object. The returned value may be zero to indicate that there
     * is no valid uid available.
     * @return a valid uid or zero.
     */
    public abstract int getUid(V obj);
    public int getUid(V obj) {
        return 0;
    }

    /**
     * Return true if tracing is feature-enabled.  This has no effect unless tracing is configured.
@@ -437,6 +441,7 @@ public abstract class AnrTimer<V> implements AutoCloseable {
        /** Start a timer by sending a message to the client's handler. */
        @Override
        void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
            cancel(arg);
            final Message msg = mHandler.obtainMessage(mWhat, arg);
            mHandler.sendMessageDelayed(msg, timeoutMs);
        }
@@ -667,8 +672,24 @@ public abstract class AnrTimer<V> implements AutoCloseable {
     * @param timeoutMs The timer timeout, in milliseconds.
     */
    public void start(@NonNull V arg, long timeoutMs) {
        start(arg, getPid(arg), getUid(arg), timeoutMs);
    }

    /**
     * Start a timer associated with arg, pid, and uid.  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.  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 process ID of the process that is being timed.
     * @param uid The UID of the process that is being timed.
     * @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, getPid(arg), getUid(arg), timeoutMs);
        mFeature.start(arg, pid, uid, timeoutMs);
    }

    /**
+8 −0
Original line number Diff line number Diff line
@@ -8,3 +8,11 @@ flag {
     description: "When true, start a trace if an ANR timer reaches 50%"
     bug: "352085328"
}

flag {
     name: "anr_timer_job_service"
     namespace: "system_performance"
     is_fixed_read_only: true
     description: "Use AnrTimer to signal ANRs in JobScheduler"
     bug: "408440679"
}
+0 −4
Original line number Diff line number Diff line
@@ -700,10 +700,6 @@ class AnrTimerService::Timer {
        if (extend && pid != 0) {
            initial.fill(pid);
        }

        // A zero-pid is odd but it means the upper layers will never ANR the process.  Freezing
        // is always disabled.  (It won't work anyway, but disabling it avoids error messages.)
        ALOGI_IF(DEBUG_ERROR && pid == 0, "error: zero-pid %s", toString().c_str());
    }

    // Start a timer.  This interface exists to generate log messages, if enabled.