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

Commit 2d0db1b3 authored by Kweku Adams's avatar Kweku Adams
Browse files

Avoid doubly penalizing rescheduled periodic jobs for flex.

Periodic jobs that were completed successfully and rescheduled sometimes
have adjusted start times to avoid running them back to back. When this
happens, make sure the flex logic takes the adjustment into account and
doesn't delay the constraint drops too significantly.

Bug: 236261941
Test: atest FrameworksMockingServicesTests:FlexibilityControllerTest
Change-Id: I039f86a409754089c7592719e0fd3355d6883cfb
parent 418c3175
Loading
Loading
Loading
Loading
+17 −3
Original line number Diff line number Diff line
@@ -369,8 +369,23 @@ public final class FlexibilityController extends StateController {
    @VisibleForTesting
    @GuardedBy("mLock")
    long getLifeCycleBeginningElapsedLocked(JobStatus js) {
        long earliestRuntime = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
                ? js.enqueueTime : js.getEarliestRunTime();
        if (js.getJob().isPeriodic() && js.getNumPreviousAttempts() == 0) {
            // Rescheduling periodic jobs (after a successful execution) may result in the job's
            // start time being a little after the "true" periodic start time (to avoid jobs
            // running back to back). See JobSchedulerService#getRescheduleJobForPeriodic for more
            // details. Since rescheduled periodic jobs may already be delayed slightly by this
            // policy, don't penalize them further by then enforcing the full set of applied
            // flex constraints at the beginning of the newly determined start time. Let the flex
            // constraint requirement start closer to the true periodic start time.
            final long truePeriodicStartTimeElapsed =
                    js.getLatestRunTimeElapsed() - js.getJob().getFlexMillis();
            // For now, treat the lifecycle beginning as the midpoint between the true periodic
            // start time and the adjusted start time.
            earliestRuntime = (earliestRuntime + truePeriodicStartTimeElapsed) / 2;
        }
        if (js.getJob().isPrefetch()) {
            final long earliestRuntime = Math.max(js.enqueueTime, js.getEarliestRunTime());
            final long estimatedLaunchTime =
                    mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
            long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault(
@@ -381,8 +396,7 @@ public final class FlexibilityController extends StateController {
            }
            return Math.max(prefetchWindowStart, earliestRuntime);
        }
        return js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
                ? js.enqueueTime : js.getEarliestRunTime();
        return earliestRuntime;
    }

    @VisibleForTesting
+61 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.app.job.JobInfo.NETWORK_TYPE_CELLULAR;
import static android.app.job.JobInfo.NETWORK_TYPE_NONE;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -421,6 +422,66 @@ public class FlexibilityControllerTest {
        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
    }

    @Test
    public void testGetLifeCycleBeginningElapsedLocked_Periodic() {
        // Periodic with lifecycle
        JobInfo.Builder jbBasic = createJob(0).setPeriodic(HOUR_IN_MILLIS);
        JobInfo.Builder jbFlex = createJob(0)
                .setPeriodic(HOUR_IN_MILLIS, 20 * MINUTE_IN_MILLIS);
        JobStatus jsBasic =
                createJobStatus("testGetLifeCycleBeginningElapsedLocked_Periodic", jbBasic);
        JobStatus jsFlex =
                createJobStatus("testGetLifeCycleBeginningElapsedLocked_Periodic", jbFlex);

        final long nowElapsed = JobSchedulerService.sElapsedRealtimeClock.millis();
        // Base case, no start adjustment
        assertEquals(nowElapsed,
                mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsBasic));
        assertEquals(nowElapsed + 40 * MINUTE_IN_MILLIS,
                mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsFlex));

        // Rescheduled with start adjustment
        final long adjustmentMs = 4 * MINUTE_IN_MILLIS;
        jsBasic = new JobStatus(jsBasic,
                // "True" start is nowElapsed + HOUR_IN_MILLIS
                nowElapsed + HOUR_IN_MILLIS + adjustmentMs,
                nowElapsed + 2 * HOUR_IN_MILLIS,
                0 /* numFailures */, 0 /* numSystemStops */,
                JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
                0, 0);
        jsFlex = new JobStatus(jsFlex,
                // "True" start is nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS
                nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + adjustmentMs,
                nowElapsed + 2 * HOUR_IN_MILLIS,
                0 /* numFailures */, 0 /* numSystemStops */,
                JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
                0, 0);

        assertEquals(nowElapsed + HOUR_IN_MILLIS + adjustmentMs / 2,
                mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsBasic));
        assertEquals(nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + adjustmentMs / 2,
                mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsFlex));

        // Rescheduled for failure
        jsBasic = new JobStatus(jsBasic,
                nowElapsed + 30 * MINUTE_IN_MILLIS,
                NO_LATEST_RUNTIME,
                1 /* numFailures */, 1 /* numSystemStops */,
                JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
                0, 0);
        jsFlex = new JobStatus(jsFlex,
                nowElapsed + 30 * MINUTE_IN_MILLIS,
                NO_LATEST_RUNTIME,
                1 /* numFailures */, 1 /* numSystemStops */,
                JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
                0, 0);

        assertEquals(nowElapsed + 30 * MINUTE_IN_MILLIS,
                mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsBasic));
        assertEquals(nowElapsed + 30 * MINUTE_IN_MILLIS,
                mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsFlex));
    }

    @Test
    public void testGetLifeCycleBeginningElapsedLocked_Prefetch() {
        // prefetch with lifecycle