diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 3ca1ad58955dc..09a44a49a81b6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -16,6 +16,7 @@ package com.android.server.job.controllers; +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; @@ -98,8 +99,11 @@ public final class FlexibilityController extends StateController { private long mFallbackFlexibilityDeadlineMs = FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; - @GuardedBy("mLock") + private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; + private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; + @VisibleForTesting + @GuardedBy("mLock") boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED; private long mMinTimeBetweenFlexibilityAlarmsMs = @@ -321,6 +325,12 @@ public final class FlexibilityController extends StateController { // There is no deadline and no estimated launch time. return NO_LIFECYCLE_END; } + if (js.getNumFailures() > 1) { + // Number of failures will not equal one as per restriction in JobStatus constructor. + return earliest + Math.min( + (long) Math.scalb(mRescheduledJobDeadline, js.getNumFailures() - 2), + mMaxRescheduledDeadline); + } return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME ? earliest + mFallbackFlexibilityDeadlineMs : js.getLatestRunTimeElapsed(); } @@ -340,7 +350,6 @@ public final class FlexibilityController extends StateController { return percentInTime; } - /** The elapsed time that marks when the next constraint should be dropped. */ @VisibleForTesting @ElapsedRealtimeLong @GuardedBy("mLock") @@ -351,7 +360,6 @@ public final class FlexibilityController extends StateController { } /** The elapsed time that marks when the next constraint should be dropped. */ - @VisibleForTesting @ElapsedRealtimeLong @GuardedBy("mLock") long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) { @@ -531,6 +539,8 @@ public final class FlexibilityController extends StateController { js.printUniqueId(pw); pw.print(" from "); UserHandle.formatUid(pw, js.getSourceUid()); + pw.print(" Num Required Constraints: "); + pw.print(js.getNumRequiredFlexibleConstraints()); pw.println(); } } @@ -551,20 +561,24 @@ public final class FlexibilityController extends StateController { } public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) { - long nextTimeElapsed; synchronized (mLock) { final long earliest = getLifeCycleBeginningElapsedLocked(js); final long latest = getLifeCycleEndElapsedLocked(js, earliest); - nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js, earliest, latest); + final long nextTimeElapsed = + getNextConstraintDropTimeElapsedLocked(js, earliest, latest); + + if (latest - nowElapsed < mDeadlineProximityLimitMs) { + mFlexibilityTracker.adjustJobsRequiredConstraints(js, + -js.getNumRequiredFlexibleConstraints(), nowElapsed); + return; + } if (nextTimeElapsed == NO_LIFECYCLE_END) { // There is no known or estimated next time to drop a constraint. removeAlarmForKey(js); return; } - if (latest - nextTimeElapsed < mDeadlineProximityLimitMs) { - mFlexibilityTracker.adjustJobsRequiredConstraints( - js, -js.getNumRequiredFlexibleConstraints(), nowElapsed); + addAlarm(js, latest - mDeadlineProximityLimitMs); return; } addAlarm(js, nextTimeElapsed); @@ -579,20 +593,8 @@ public final class FlexibilityController extends StateController { for (int i = 0; i < expired.size(); i++) { JobStatus js = expired.valueAt(i); boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE); - - final long earliest = getLifeCycleBeginningElapsedLocked(js); - final long latest = getLifeCycleEndElapsedLocked(js, earliest); - - if (latest - nowElapsed < mDeadlineProximityLimitMs) { - mFlexibilityTracker.adjustJobsRequiredConstraints(js, - -js.getNumRequiredFlexibleConstraints(), nowElapsed); - } else { - long nextTimeElapsed = - getNextConstraintDropTimeElapsedLocked(js, earliest, latest); - if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1, nowElapsed) - && nextTimeElapsed != NO_LIFECYCLE_END) { - mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed); - } + if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1, nowElapsed)) { + scheduleDropNumConstraintsAlarm(js, nowElapsed); } if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) { changedJobs.add(js); @@ -619,6 +621,10 @@ public final class FlexibilityController extends StateController { FC_CONFIG_PREFIX + "min_alarm_time_flexibility_ms"; static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints"; + static final String KEY_MAX_RESCHEDULED_DEADLINE_MS = + FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms"; + static final String KEY_RESCHEDULED_JOB_DEADLINE_MS = + FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms"; private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false; @VisibleForTesting @@ -628,6 +634,8 @@ public final class FlexibilityController extends StateController { private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS; @VisibleForTesting final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80}; + private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS; + private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS; /** * If false the controller will not track new jobs @@ -643,6 +651,10 @@ public final class FlexibilityController extends StateController { /** The percentages of a jobs' lifecycle to drop the number of required constraints. */ public int[] PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; + /** Initial fallback flexible deadline for rescheduled jobs. */ + public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; + /** The max deadline for rescheduled jobs. */ + public long MAX_RESCHEDULED_DEADLINE_MS = DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; @GuardedBy("mLock") public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @@ -662,6 +674,22 @@ public final class FlexibilityController extends StateController { } } break; + case KEY_RESCHEDULED_JOB_DEADLINE_MS: + RESCHEDULED_JOB_DEADLINE_MS = + properties.getLong(key, DEFAULT_RESCHEDULED_JOB_DEADLINE_MS); + if (mRescheduledJobDeadline != RESCHEDULED_JOB_DEADLINE_MS) { + mRescheduledJobDeadline = RESCHEDULED_JOB_DEADLINE_MS; + mShouldReevaluateConstraints = true; + } + break; + case KEY_MAX_RESCHEDULED_DEADLINE_MS: + MAX_RESCHEDULED_DEADLINE_MS = + properties.getLong(key, DEFAULT_MAX_RESCHEDULED_DEADLINE_MS); + if (mMaxRescheduledDeadline != MAX_RESCHEDULED_DEADLINE_MS) { + mMaxRescheduledDeadline = MAX_RESCHEDULED_DEADLINE_MS; + mShouldReevaluateConstraints = true; + } + break; case KEY_DEADLINE_PROXIMITY_LIMIT: DEADLINE_PROXIMITY_LIMIT_MS = properties.getLong(key, DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS); @@ -733,6 +761,14 @@ public final class FlexibilityController extends StateController { pw.increaseIndent(); pw.print(KEY_FLEXIBILITY_ENABLED, FLEXIBILITY_ENABLED).println(); + pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println(); + pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println(); + pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS, + MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS).println(); + pw.print(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, + PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS).println(); + pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println(); + pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println(); pw.decreaseIndent(); } @@ -748,9 +784,15 @@ public final class FlexibilityController extends StateController { @GuardedBy("mLock") public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate predicate) { pw.println("# Constraints Satisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints)); + pw.print("Satisfied Flexible Constraints: "); + JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints); + pw.println(); pw.println(); mFlexibilityTracker.dump(pw, predicate); + pw.println(); + mFlexibilityAlarmQueue.dump(pw); + pw.println(); mFcConfig.dump(pw); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 4ce6b32166c1a..4320db09aef54 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -572,8 +572,11 @@ public final class JobStatus { (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis) >= MIN_WINDOW_FOR_FLEXIBILITY_MS; + // The first time a job is rescheduled it will not be subject to flexible constraints. + // Otherwise, every consecutive reschedule increases a jobs' flexibility deadline. if (!isRequestedExpeditedJob() && satisfiesMinWindowException + && numFailures != 1 && lacksSomeFlexibleConstraints) { mNumRequiredFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0); @@ -1928,35 +1931,38 @@ public final class JobStatus { proto.end(token); } - void dumpConstraints(PrintWriter pw, int constraints) { - if ((constraints&CONSTRAINT_CHARGING) != 0) { + static void dumpConstraints(PrintWriter pw, int constraints) { + if ((constraints & CONSTRAINT_CHARGING) != 0) { pw.print(" CHARGING"); } - if ((constraints& CONSTRAINT_BATTERY_NOT_LOW) != 0) { + if ((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0) { pw.print(" BATTERY_NOT_LOW"); } - if ((constraints& CONSTRAINT_STORAGE_NOT_LOW) != 0) { + if ((constraints & CONSTRAINT_STORAGE_NOT_LOW) != 0) { pw.print(" STORAGE_NOT_LOW"); } - if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) { + if ((constraints & CONSTRAINT_TIMING_DELAY) != 0) { pw.print(" TIMING_DELAY"); } - if ((constraints&CONSTRAINT_DEADLINE) != 0) { + if ((constraints & CONSTRAINT_DEADLINE) != 0) { pw.print(" DEADLINE"); } - if ((constraints&CONSTRAINT_IDLE) != 0) { + if ((constraints & CONSTRAINT_IDLE) != 0) { pw.print(" IDLE"); } - if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) { + if ((constraints & CONSTRAINT_CONNECTIVITY) != 0) { pw.print(" CONNECTIVITY"); } - if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) { + if ((constraints & CONSTRAINT_FLEXIBLE) != 0) { + pw.print(" FLEXIBILITY"); + } + if ((constraints & CONSTRAINT_CONTENT_TRIGGER) != 0) { pw.print(" CONTENT_TRIGGER"); } - if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) { + if ((constraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0) { pw.print(" DEVICE_NOT_DOZING"); } - if ((constraints&CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) { + if ((constraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) { pw.print(" BACKGROUND_NOT_RESTRICTED"); } if ((constraints & CONSTRAINT_PREFETCH) != 0) { @@ -2242,6 +2248,14 @@ public final class JobStatus { ((requiredConstraints | CONSTRAINT_WITHIN_QUOTA | CONSTRAINT_TARE_WEALTH) & ~satisfiedConstraints)); pw.println(); + if (hasFlexibilityConstraint()) { + pw.print("Num Required Flexible constraints: "); + pw.print(getNumRequiredFlexibleConstraints()); + pw.println(); + pw.print("Num Dropped Flexible constraints: "); + pw.print(getNumDroppedFlexibleConstraints()); + pw.println(); + } pw.println("Constraint history:"); pw.increaseIndent(); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 9d039bd09a2fc..c03647b84f018 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -33,6 +33,7 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NO import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; +import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -390,7 +391,7 @@ public class FlexibilityControllerTest { } @Test - public void testGetLifeCycleBeginningElapsedLocked_prefetch() { + public void testGetLifeCycleBeginningElapsedLocked_Prefetch() { // prefetch with lifecycle when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(700L); JobInfo.Builder jb = createJob(0).setPrefetch(true); @@ -417,7 +418,7 @@ public class FlexibilityControllerTest { } @Test - public void testGetLifeCycleBeginningElapsedLocked_nonPrefetch() { + public void testGetLifeCycleBeginningElapsedLocked_NonPrefetch() { // delay long delay = 100; JobInfo.Builder jb = createJob(0).setMinimumLatency(delay); @@ -432,7 +433,7 @@ public class FlexibilityControllerTest { } @Test - public void testGetLifeCycleEndElapsedLocked_prefetch() { + public void testGetLifeCycleEndElapsedLocked_Prefetch() { // prefetch no estimate JobInfo.Builder jb = createJob(0).setPrefetch(true); JobStatus js = createJobStatus("time", jb); @@ -444,8 +445,9 @@ public class FlexibilityControllerTest { when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(1000L); assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); } + @Test - public void testGetLifeCycleEndElapsedLocked_nonPrefetch() { + public void testGetLifeCycleEndElapsedLocked_NonPrefetch() { // deadline JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L); JobStatus js = createJobStatus("time", jb); @@ -458,6 +460,28 @@ public class FlexibilityControllerTest { mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L)); } + @Test + public void testGetLifeCycleEndElapsedLocked_Rescheduled() { + JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L); + JobStatus js = createJobStatus("time", jb); + js = new JobStatus( + js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, FROZEN_TIME, FROZEN_TIME); + + assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS, + mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); + + js = new JobStatus( + js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 3, FROZEN_TIME, FROZEN_TIME); + + assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS, + mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); + + js = new JobStatus( + js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 10, FROZEN_TIME, FROZEN_TIME); + assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS, + mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); + } + @Test public void testWontStopJobFromRunning() { JobStatus js = createJobStatus("testWontStopJobFromRunning", createJob(101)); @@ -577,6 +601,15 @@ public class FlexibilityControllerTest { assertFalse(js.hasFlexibilityConstraint()); } + @Test + public void testExceptions_RescheduledOnce() { + JobInfo.Builder jb = createJob(0); + JobStatus js = createJobStatus("time", jb); + js = new JobStatus( + js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, FROZEN_TIME, FROZEN_TIME); + assertFalse(js.hasFlexibilityConstraint()); + } + @Test public void testExceptions_None() { JobInfo.Builder jb = createJob(0);