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

Commit d6d8091d authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

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

parents 1e3ee721 966e72b8
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