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

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

Merge "Adding buffer time for periodic jobs run near the end of their windows." into qt-dev

parents e7c34cd7 b6722783
Loading
Loading
Loading
Loading
+20 −3
Original line number Diff line number Diff line
@@ -1625,6 +1625,15 @@ public class JobSchedulerService extends com.android.server.SystemService
        return newJob;
    }

    /**
     * Maximum time buffer in which JobScheduler will try to optimize periodic job scheduling. This
     * does not cause a job's period to be larger than requested (eg: if the requested period is
     * shorter than this buffer). This is used to put a limit on when JobScheduler will intervene
     * and try to optimize scheduling if the current job finished less than this amount of time to
     * the start of the next period
     */
    private static final long PERIODIC_JOB_WINDOW_BUFFER = 30 * MINUTE_IN_MILLIS;

    /**
     * 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
@@ -1645,16 +1654,18 @@ public class JobSchedulerService extends com.android.server.SystemService
        final long period = periodicToReschedule.getJob().getIntervalMillis();
        final long latestRunTimeElapsed = periodicToReschedule.getOriginalLatestRunTimeElapsed();
        final long flex = periodicToReschedule.getJob().getFlexMillis();
        long rescheduleBuffer = 0;

        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
            // and schedule a new job for the next window.
            if (DEBUG) {
                Slog.i(TAG, "Periodic job ran after its intended window.");
            }
            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 (period != flex && diffMs > Math.min(PERIODIC_JOB_WINDOW_BUFFER,
                    (period - flex) / 2)) {
                if (DEBUG) {
                    Slog.d(TAG, "Custom flex job ran too close to next window.");
                }
@@ -1665,9 +1676,15 @@ public class JobSchedulerService extends com.android.server.SystemService
            newLatestRuntimeElapsed = latestRunTimeElapsed + (period * numSkippedWindows);
        } else {
            newLatestRuntimeElapsed = latestRunTimeElapsed + period;
            if (diffMs < PERIODIC_JOB_WINDOW_BUFFER && diffMs < period / 6) {
                // Add a little buffer to the start of the next window so the job doesn't run
                // too soon after this completed one.
                rescheduleBuffer = Math.min(PERIODIC_JOB_WINDOW_BUFFER, period / 6 - diffMs);
            }
        }

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

        if (DEBUG) {
            Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
+180 −3
Original line number Diff line number Diff line
@@ -183,15 +183,188 @@ public class JobSchedulerServiceTest {
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        advanceElapsedClock(45 * MINUTE_IN_MILLIS); // now + 55 minutes
        advanceElapsedClock(20 * MINUTE_IN_MILLIS); // now + 30 minutes

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

        advanceElapsedClock(25 * MINUTE_IN_MILLIS); // now + 55 minutes

        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
        // Shifted because it's close to the end of the window.
        assertEquals(nextWindowStartTime + 5 * MINUTE_IN_MILLIS,
                rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 59 minutes

        rescheduledJob = mService.getRescheduleJobForPeriodic(job);
        // Shifted because it's close to the end of the window.
        assertEquals(nextWindowStartTime + 9 * MINUTE_IN_MILLIS,
                rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
    }

    /**
     * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
     * with an extra delay and correct deadline constraint if the periodic job is completed near the
     * end of its expected running window.
     */
    @Test
    public void testGetRescheduleJobForPeriodic_closeToEndOfWindow() {
        JobStatus frequentJob = createJobStatus(
                "testGetRescheduleJobForPeriodic_closeToEndOfWindow",
                createJobInfo().setPeriodic(15 * MINUTE_IN_MILLIS));
        long now = sElapsedRealtimeClock.millis();
        long nextWindowStartTime = now + 15 * MINUTE_IN_MILLIS;
        long nextWindowEndTime = now + 30 * MINUTE_IN_MILLIS;

        // At the beginning of the window. Next window should be unaffected.
        JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // Halfway through window. Next window should be unaffected.
        advanceElapsedClock((long) (7.5 * MINUTE_IN_MILLIS));
        rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // In last 1/6 of window. Next window start time should be shifted slightly.
        advanceElapsedClock(6 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
        assertEquals(nextWindowStartTime + MINUTE_IN_MILLIS,
                rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        JobStatus mediumJob = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
                createJobInfo().setPeriodic(HOUR_IN_MILLIS));
        now = sElapsedRealtimeClock.millis();
        nextWindowStartTime = now + HOUR_IN_MILLIS;
        nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;

        // At the beginning of the window. Next window should be unaffected.
        rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // Halfway through window. Next window should be unaffected.
        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // At the edge 1/6 of window. Next window should be unaffected.
        advanceElapsedClock(20 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // In last 1/6 of window. Next window start time should be shifted slightly.
        advanceElapsedClock(6 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
        assertEquals(nextWindowStartTime + (6 * MINUTE_IN_MILLIS),
                rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        JobStatus longJob = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
                createJobInfo().setPeriodic(6 * HOUR_IN_MILLIS));
        now = sElapsedRealtimeClock.millis();
        nextWindowStartTime = now + 6 * HOUR_IN_MILLIS;
        nextWindowEndTime = now + 12 * HOUR_IN_MILLIS;

        // At the beginning of the window. Next window should be unaffected.
        rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // Halfway through window. Next window should be unaffected.
        advanceElapsedClock(3 * HOUR_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // At the edge 1/6 of window. Next window should be unaffected.
        advanceElapsedClock(2 * HOUR_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // In last 1/6 of window. Next window should be unaffected since we're over the shift cap.
        advanceElapsedClock(15 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // In last 1/6 of window. Next window start time should be shifted slightly.
        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
        assertEquals(nextWindowStartTime + (30 * MINUTE_IN_MILLIS),
                rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // Flex duration close to period duration.
        JobStatus gameyFlex = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
                createJobInfo().setPeriodic(HOUR_IN_MILLIS, 59 * MINUTE_IN_MILLIS));
        now = sElapsedRealtimeClock.millis();
        nextWindowStartTime = now + HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
        nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
        advanceElapsedClock(MINUTE_IN_MILLIS);

        // At the beginning of the window. Next window should be unaffected.
        rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // Halfway through window. Next window should be unaffected.
        advanceElapsedClock(29 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // At the edge 1/6 of window. Next window should be unaffected.
        advanceElapsedClock(20 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // In last 1/6 of window. Next window start time should be shifted slightly.
        advanceElapsedClock(6 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
        assertEquals(nextWindowStartTime + (5 * MINUTE_IN_MILLIS),
                rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // Very short flex duration compared to period duration.
        JobStatus superFlex = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
                createJobInfo().setPeriodic(HOUR_IN_MILLIS, 10 * MINUTE_IN_MILLIS));
        now = sElapsedRealtimeClock.millis();
        nextWindowStartTime = now + HOUR_IN_MILLIS + 50 * MINUTE_IN_MILLIS;
        nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
        advanceElapsedClock(MINUTE_IN_MILLIS);

        // At the beginning of the window. Next window should be unaffected.
        rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // Halfway through window. Next window should be unaffected.
        advanceElapsedClock(29 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // At the edge 1/6 of window. Next window should be unaffected.
        advanceElapsedClock(20 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        // In last 1/6 of window. Next window should be unaffected since the flex duration pushes
        // the next window start time far enough away.
        advanceElapsedClock(6 * MINUTE_IN_MILLIS);
        rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
    }
@@ -265,7 +438,9 @@ public class JobSchedulerServiceTest {
        advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes

        rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        // Shifted because it's close to the end of the window.
        assertEquals(nextWindowStartTime + 5 * MINUTE_IN_MILLIS,
                rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());

        advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes
@@ -273,7 +448,9 @@ public class JobSchedulerServiceTest {
        advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes

        rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
        assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
        // Shifted because it's close to the end of the window.
        assertEquals(nextWindowStartTime + 9 * MINUTE_IN_MILLIS,
                rescheduledJob.getEarliestRunTime());
        assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
    }