Merge "Adding checks for bad periodic job scheduling." into qt-dev am: d6d8091d51 am: 624208db6e

am: 2bb58794af

Change-Id: Iffbdf285d30ca5ae9c4448fb7f6601b45ec3a300
This commit is contained in:
Kweku Adams
2019-05-31 12:01:32 -07:00
committed by android-build-merger
3 changed files with 88 additions and 6 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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