Merge "Adding buffer time for periodic jobs run near the end of their windows." into qt-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
abe2321f1f
@@ -1625,6 +1625,15 @@ public class JobSchedulerService extends com.android.server.SystemService
|
|||||||
return newJob;
|
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
|
* 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
|
* 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 period = periodicToReschedule.getJob().getIntervalMillis();
|
||||||
final long latestRunTimeElapsed = periodicToReschedule.getOriginalLatestRunTimeElapsed();
|
final long latestRunTimeElapsed = periodicToReschedule.getOriginalLatestRunTimeElapsed();
|
||||||
final long flex = periodicToReschedule.getJob().getFlexMillis();
|
final long flex = periodicToReschedule.getJob().getFlexMillis();
|
||||||
|
long rescheduleBuffer = 0;
|
||||||
|
|
||||||
|
final long diffMs = Math.abs(elapsedNow - latestRunTimeElapsed);
|
||||||
if (elapsedNow > latestRunTimeElapsed) {
|
if (elapsedNow > latestRunTimeElapsed) {
|
||||||
// The job ran past its expected run window. Have it count towards the current window
|
// The job ran past its expected run window. Have it count towards the current window
|
||||||
// and schedule a new job for the next window.
|
// and schedule a new job for the next window.
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.i(TAG, "Periodic job ran after its intended window.");
|
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
|
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) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "Custom flex job ran too close to next window.");
|
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);
|
newLatestRuntimeElapsed = latestRunTimeElapsed + (period * numSkippedWindows);
|
||||||
} else {
|
} else {
|
||||||
newLatestRuntimeElapsed = latestRunTimeElapsed + period;
|
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) {
|
if (DEBUG) {
|
||||||
Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
|
Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
|
||||||
|
|||||||
@@ -183,15 +183,188 @@ public class JobSchedulerServiceTest {
|
|||||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||||
|
|
||||||
advanceElapsedClock(45 * MINUTE_IN_MILLIS); // now + 55 minutes
|
advanceElapsedClock(20 * MINUTE_IN_MILLIS); // now + 30 minutes
|
||||||
|
|
||||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
||||||
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
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
|
advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 59 minutes
|
||||||
|
|
||||||
rescheduledJob = mService.getRescheduleJobForPeriodic(job);
|
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(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
|
||||||
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||||
}
|
}
|
||||||
@@ -265,7 +438,9 @@ public class JobSchedulerServiceTest {
|
|||||||
advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes
|
advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes
|
||||||
|
|
||||||
rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
|
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());
|
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||||
|
|
||||||
advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes
|
advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes
|
||||||
@@ -273,7 +448,9 @@ public class JobSchedulerServiceTest {
|
|||||||
advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes
|
advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes
|
||||||
|
|
||||||
rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
|
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());
|
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user