summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java23
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java183
2 files changed, 200 insertions, 6 deletions
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index eecbb16a38a2..35a82aef51b5 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -1626,6 +1626,15 @@ public class JobSchedulerService extends com.android.server.SystemService
}
/**
+ * 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
* called).
@@ -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 [" +
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index f7edf65a499f..18c524ad7a94 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -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());
}