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

Commit 41a4236e authored by Kweku Adams's avatar Kweku Adams Committed by android-build-merger
Browse files

Merge "Adding checks for bad periodic job scheduling." into qt-dev

am: d6d8091d

Change-Id: I3c1a5f274998ec1d450aef3ed61edd97fb527c82
parents 120ae295 d6d8091d
Loading
Loading
Loading
Loading
+27 −4
Original line number Diff line number Diff line
@@ -1635,6 +1635,9 @@ public class JobSchedulerService extends com.android.server.SystemService
     */
    private static final long PERIODIC_JOB_WINDOW_BUFFER = 30 * MINUTE_IN_MILLIS;

    /** The maximum period a periodic job can have. Anything higher will be clamped down to this. */
    public static final long MAX_ALLOWED_PERIOD_MS = 365 * 24 * 60 * 60 * 1000L;

    /**
     * Called after a periodic has executed so we can reschedule it. We take the last execution
     * time of the job to be the time of completion (i.e. the time at which this function is
@@ -1652,11 +1655,21 @@ public class JobSchedulerService extends com.android.server.SystemService
    JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
        final long elapsedNow = sElapsedRealtimeClock.millis();
        final long newLatestRuntimeElapsed;
        final long period = periodicToReschedule.getJob().getIntervalMillis();
        final long latestRunTimeElapsed = periodicToReschedule.getOriginalLatestRunTimeElapsed();
        final long flex = periodicToReschedule.getJob().getFlexMillis();
        // Make sure period is in the interval [min_possible_period, max_possible_period].
        final long period = Math.max(JobInfo.getMinPeriodMillis(),
                Math.min(MAX_ALLOWED_PERIOD_MS, periodicToReschedule.getJob().getIntervalMillis()));
        // Make sure flex is in the interval [min_possible_flex, period].
        final long flex = Math.max(JobInfo.getMinFlexMillis(),
                Math.min(period, periodicToReschedule.getJob().getFlexMillis()));
        long rescheduleBuffer = 0;

        long olrte = periodicToReschedule.getOriginalLatestRunTimeElapsed();
        if (olrte < 0 || olrte == JobStatus.NO_LATEST_RUNTIME) {
            Slog.wtf(TAG, "Invalid periodic job original latest run time: " + olrte);
            olrte = elapsedNow;
        }
        final long latestRunTimeElapsed = olrte;

        final long diffMs = Math.abs(elapsedNow - latestRunTimeElapsed);
        if (elapsedNow > latestRunTimeElapsed) {
            // The job ran past its expected run window. Have it count towards the current window
@@ -1664,7 +1677,7 @@ public class JobSchedulerService extends com.android.server.SystemService
            if (DEBUG) {
                Slog.i(TAG, "Periodic job ran after its intended window.");
            }
            int numSkippedWindows = (int) (diffMs / period) + 1; // +1 to include original window
            long numSkippedWindows = (diffMs / period) + 1; // +1 to include original window
            if (period != flex && diffMs > Math.min(PERIODIC_JOB_WINDOW_BUFFER,
                    (period - flex) / 2)) {
                if (DEBUG) {
@@ -1684,6 +1697,16 @@ public class JobSchedulerService extends com.android.server.SystemService
            }
        }

        if (newLatestRuntimeElapsed < elapsedNow) {
            Slog.wtf(TAG, "Rescheduling calculated latest runtime in the past: "
                    + newLatestRuntimeElapsed);
            return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
                    elapsedNow + period - flex, elapsedNow + period,
                    0 /* backoffAttempt */,
                    sSystemClock.millis() /* lastSuccessfulRunTime */,
                    periodicToReschedule.getLastFailedRunTime());
        }

        final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed
                - Math.min(flex, period - rescheduleBuffer);

+10 −2
Original line number Diff line number Diff line
@@ -511,8 +511,13 @@ public final class JobStatus {
        final long elapsedNow = sElapsedRealtimeClock.millis();
        final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
        if (job.isPeriodic()) {
            latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
            earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis();
            // Make sure period is in the interval [min_possible_period, max_possible_period].
            final long period = Math.max(JobInfo.getMinPeriodMillis(),
                    Math.min(JobSchedulerService.MAX_ALLOWED_PERIOD_MS, job.getIntervalMillis()));
            latestRunTimeElapsedMillis = elapsedNow + period;
            earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis
                    // Make sure flex is in the interval [min_possible_flex, period].
                    - Math.max(JobInfo.getMinFlexMillis(), Math.min(period, job.getFlexMillis()));
        } else {
            earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
                    elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
@@ -1631,6 +1636,9 @@ public final class JobStatus {
        formatRunTime(pw, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, elapsedRealtimeMillis);
        pw.print(", latest=");
        formatRunTime(pw, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, elapsedRealtimeMillis);
        pw.print(", original latest=");
        formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis,
                NO_LATEST_RUNTIME, elapsedRealtimeMillis);
        pw.println();
        if (numFailures != 0) {
            pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures);
+51 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.job;

import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;

@@ -160,6 +161,56 @@ public class JobSchedulerServiceTest {
                jobInfoBuilder.build(), 1234, "com.android.test", 0, testTag);
    }

    /**
     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
     * with the correct delay and deadline constraints if the periodic job is scheduled with the
     * minimum possible period.
     */
    @Test
    public void testGetRescheduleJobForPeriodic_minPeriod() {
        final long now = sElapsedRealtimeClock.millis();
        JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow",
                createJobInfo().setPeriodic(15 * MINUTE_IN_MILLIS));
        final long nextWindowStartTime = now + 15 * MINUTE_IN_MILLIS;
        final long nextWindowEndTime = now + 30 * MINUTE_IN_MILLIS;

        for (int i = 0; i < 25; i++) {
            JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
            assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
            assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
            advanceElapsedClock(30_000); // 30 seconds
        }

        for (int i = 0; i < 5; i++) {
            // Window buffering in last 1/6 of window.
            JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
            assertEquals(nextWindowStartTime + i * 30_000, rescheduledJob.getEarliestRunTime());
            assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
            advanceElapsedClock(30_000); // 30 seconds
        }
    }

    /**
     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
     * with the correct delay and deadline constraints if the periodic job is scheduled with a
     * period that's too large.
     */
    @Test
    public void testGetRescheduleJobForPeriodic_largePeriod() {
        final long now = sElapsedRealtimeClock.millis();
        JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow",
                createJobInfo().setPeriodic(2 * 365 * DAY_IN_MILLIS));
        assertEquals(now, job.getEarliestRunTime());
        // Periods are capped at 365 days (1 year).
        assertEquals(now + 365 * DAY_IN_MILLIS, job.getLatestRunTimeElapsed());
        final long nextWindowStartTime = now + 365 * DAY_IN_MILLIS;
        final long nextWindowEndTime = nextWindowStartTime + 365 * DAY_IN_MILLIS;

        JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
    }

    /**
     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
     * with the correct delay and deadline constraints if the periodic job is completed and