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

Commit c9793092 authored by Kweku Adams's avatar Kweku Adams
Browse files

Fix periodic job scheduling.

If a periodic job was run outside of its expected window (eg: because of
Doze mode or its constraints were unmet for a long time), then
JobSchedulerService would schedule the next one with a window starting
right now, which could cause the job to run again immediately.
This change addresses the issue by:
1. Switching to having consistent windows based on when the job was
scheduled. This also helps developers know when to expect jobs to run.
2. If a job runs in a different window, its run counts toward that
window instead.
3. For jobs with a custom flex setting, the next window is skipped if
the job is run to close to the next window.

Bug: 123248627
Test: atest com.android.server.job.JobSchedulerServiceTest
Test: atest CtsJobSchedulerTestCases
Change-Id: Ic18929e9d03e704ec43aa50fe37c64cd6807cf68
parent ebc87475
Loading
Loading
Loading
Loading
+37 −13
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.job;

import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;

import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -1801,7 +1802,8 @@ public class JobSchedulerService extends com.android.server.SystemService
     *
     * @see #maybeQueueReadyJobsForExecutionLocked
     */
    private JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
    @VisibleForTesting
    JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
        final long elapsedNowMillis = sElapsedRealtimeClock.millis();
        final JobInfo job = failureToReschedule.getJob();

@@ -1848,6 +1850,10 @@ public class JobSchedulerService extends com.android.server.SystemService
                elapsedNowMillis + delayMillis,
                JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
                failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
        if (job.isPeriodic()) {
            newJob.setOriginalLatestRunTimeElapsed(
                    failureToReschedule.getOriginalLatestRunTimeElapsed());
        }
        for (int ic=0; ic<mControllers.size(); ic++) {
            StateController controller = mControllers.get(ic);
            controller.rescheduleForFailureLocked(newJob, failureToReschedule);
@@ -1868,23 +1874,41 @@ public class JobSchedulerService extends com.android.server.SystemService
     * @return A new job representing the execution criteria for this instantiation of the
     * recurring job.
     */
    private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
    @VisibleForTesting
    JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
        final long elapsedNow = sElapsedRealtimeClock.millis();
        // Compute how much of the period is remaining.
        long runEarly = 0L;

        // If this periodic was rescheduled it won't have a deadline.
        if (periodicToReschedule.hasDeadlineConstraint()) {
            runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
        final long newLatestRuntimeElapsed;
        final long period = periodicToReschedule.getJob().getIntervalMillis();
        final long latestRunTimeElapsed = periodicToReschedule.getOriginalLatestRunTimeElapsed();
        final long flex = periodicToReschedule.getJob().getFlexMillis();

        if (elapsedNow > latestRunTimeElapsed) {
            // The job ran past its expected run window. Have it count towards the current window
            // and schedule a new job for the next window.
            if (DEBUG) {
                Slog.i(TAG, "Periodic job ran after its intended window.");
            }
        long flex = periodicToReschedule.getJob().getFlexMillis();
        long period = periodicToReschedule.getJob().getIntervalMillis();
        long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
        long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
            final long diffMs = (elapsedNow - latestRunTimeElapsed);
            int numSkippedWindows = (int) (diffMs / period) + 1; // +1 to include original window
            if (period != flex && diffMs > Math.min(30 * MINUTE_IN_MILLIS, (period - flex) / 2)) {
                if (DEBUG) {
                    Slog.d(TAG, "Custom flex job ran too close to next window.");
                }
                // For custom flex periods, if the job was run too close to the next window,
                // skip the next window and schedule for the following one.
                numSkippedWindows += 1;
            }
            newLatestRuntimeElapsed = latestRunTimeElapsed + (period * numSkippedWindows);
        } else {
            newLatestRuntimeElapsed = latestRunTimeElapsed + period;
        }

        final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;

        if (DEBUG) {
            Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
                    newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
                    newEarliestRunTimeElapsed / 1000 + ", " + newLatestRuntimeElapsed / 1000
                    + "]s");
        }
        return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
                newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
+15 −0
Original line number Diff line number Diff line
@@ -161,6 +161,12 @@ public final class JobStatus {
     */
    private final long latestRunTimeElapsedMillis;

    /**
     * Valid only for periodic jobs. The original latest point in the future at which this
     * job was expected to run.
     */
    private long mOriginalLatestRunTimeElapsedMillis;

    /** How many times this job has failed, used to compute back-off. */
    private final int numFailures;

@@ -394,6 +400,7 @@ public final class JobStatus {

        this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
        this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
        this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
        this.numFailures = numFailures;

        int requiredConstraints = job.getConstraintFlags();
@@ -871,6 +878,14 @@ public final class JobStatus {
        return latestRunTimeElapsedMillis;
    }

    public long getOriginalLatestRunTimeElapsed() {
        return mOriginalLatestRunTimeElapsedMillis;
    }

    public void setOriginalLatestRunTimeElapsed(long latestRunTimeElapsed) {
        mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
    }

    /**
     * Return the fractional position of "now" within the "run time" window of
     * this job.
+437 −0

File added.

Preview size limit exceeded, changes collapsed.