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

Commit f697a14b authored by Kweku Adams's avatar Kweku Adams
Browse files

Throttle how frequently delay alarms are set.

If there are many timing delays set within a few milliseconds of each
other, TimeController can end up spamming and overwhelming AlarmManager.
Since a job doesn't need to have its delay constraint updated
immediately and we therefore don't need to have exact timing of every
single delay alarm, we should be alright limiting how often a delay
alarm can be set to go off.

Bug: 167681468
Test: atest com.android.server.job.controllers.TimeControllerTest
Test: atest CtsJobSchedulerTestCases
Change-Id: I5d2eba00d1c47ddf81bb440dd00dae0239c91eb0
parent 056630f1
Loading
Loading
Loading
Loading
+14 −3
Original line number Diff line number Diff line
@@ -50,6 +50,9 @@ public final class TimeController extends StateController {
    private static final boolean DEBUG = JobSchedulerService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

    @VisibleForTesting
    static final long DELAY_COALESCE_TIME_MS = 30_000L;

    /** Deadline alarm tag for logging purposes */
    private final String DEADLINE_TAG = "*job.deadline*";
    /** Delay alarm tag for logging purposes */
@@ -57,6 +60,7 @@ public final class TimeController extends StateController {

    private long mNextJobExpiredElapsedMillis;
    private long mNextDelayExpiredElapsedMillis;
    private volatile long mLastFiredDelayExpiredElapsedMillis;

    private final boolean mChainedAttributionEnabled;

@@ -273,7 +277,6 @@ public final class TimeController extends StateController {
    @VisibleForTesting
    void checkExpiredDelaysAndResetAlarm() {
        synchronized (mLock) {
            final long nowElapsedMillis = sElapsedRealtimeClock.millis();
            long nextDelayTime = Long.MAX_VALUE;
            int nextDelayUid = 0;
            String nextDelayPackageName = null;
@@ -284,7 +287,7 @@ public final class TimeController extends StateController {
                if (!job.hasTimingDelayConstraint()) {
                    continue;
                }
                if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
                if (evaluateTimingDelayConstraint(job, sElapsedRealtimeClock.millis())) {
                    if (canStopTrackingJobLocked(job)) {
                        it.remove();
                    }
@@ -356,7 +359,11 @@ public final class TimeController extends StateController {
     * This alarm <b>will not</b> wake up the phone.
     */
    private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
        alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
        // To avoid spamming AlarmManager in the case where many delay times are a few milliseconds
        // apart, make sure the alarm is set no earlier than DELAY_COALESCE_TIME_MS since the last
        // time a delay alarm went off and that the alarm is not scheduled for the past.
        alarmTimeElapsedMillis = maybeAdjustAlarmTime(Math.max(alarmTimeElapsedMillis,
                mLastFiredDelayExpiredElapsedMillis + DELAY_COALESCE_TIME_MS));
        if (mNextDelayExpiredElapsedMillis == alarmTimeElapsedMillis) {
            return;
        }
@@ -416,6 +423,7 @@ public final class TimeController extends StateController {
            if (DEBUG) {
                Slog.d(TAG, "Delay-expired alarm fired");
            }
            mLastFiredDelayExpiredElapsedMillis = sElapsedRealtimeClock.millis();
            checkExpiredDelaysAndResetAlarm();
        }
    };
@@ -429,6 +437,9 @@ public final class TimeController extends StateController {
        pw.print("Next delay alarm in ");
        TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
        pw.println();
        pw.print("Last delay alarm fired @ ");
        TimeUtils.formatDuration(nowElapsed, mLastFiredDelayExpiredElapsedMillis, pw);
        pw.println();
        pw.print("Next deadline alarm in ");
        TimeUtils.formatDuration(mNextJobExpiredElapsedMillis, nowElapsed, pw);
        pw.println();
+45 −0
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoSession;
@@ -631,6 +632,50 @@ public class TimeControllerTest {
                .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
    }

    @Test
    public void testDelayAlarmSchedulingCoalescedIntervals() {
        doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());

        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();

        JobStatus jobLatest = createJobStatus("testDelayAlarmSchedulingCoalescedIntervals",
                createJob().setMinimumLatency(HOUR_IN_MILLIS));
        JobStatus jobMiddle = createJobStatus("testDelayAlarmSchedulingCoalescedIntervals",
                createJob().setMinimumLatency(TimeController.DELAY_COALESCE_TIME_MS / 2));
        JobStatus jobEarliest = createJobStatus("testDelayAlarmSchedulingCoalescedIntervals",
                createJob().setMinimumLatency(TimeController.DELAY_COALESCE_TIME_MS / 10));

        ArgumentCaptor<AlarmManager.OnAlarmListener> listenerCaptor =
                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
        InOrder inOrder = inOrder(mAlarmManager);

        mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
        mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
        mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
        inOrder.verify(mAlarmManager, times(1))
                .set(anyInt(), eq(now + TimeController.DELAY_COALESCE_TIME_MS / 10), anyLong(),
                        anyLong(), eq(TAG_DELAY),
                        listenerCaptor.capture(), any(), any());
        final AlarmManager.OnAlarmListener delayListener = listenerCaptor.getValue();

        advanceElapsedClock(TimeController.DELAY_COALESCE_TIME_MS / 10);
        delayListener.onAlarm();
        // The next delay alarm time should be TimeController.DELAY_COALESCE_TIME_MS after the last
        // time the delay alarm fired.
        inOrder.verify(mAlarmManager, times(1))
                .set(anyInt(), eq(now + TimeController.DELAY_COALESCE_TIME_MS / 10
                                + TimeController.DELAY_COALESCE_TIME_MS), anyLong(),
                        anyLong(), eq(TAG_DELAY), any(), any(), any());

        advanceElapsedClock(TimeController.DELAY_COALESCE_TIME_MS);
        delayListener.onAlarm();
        // The last job is significantly after the coalesce time, so the 3rd scheduling shouldn't be
        // affected by the first two jobs' alarms.
        inOrder.verify(mAlarmManager, times(1))
                .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(),
                        anyLong(), eq(TAG_DELAY), any(), any(), any());
    }

    @Test
    public void testEvaluateStateLocked_Delay() {
        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();