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

Commit 4a76f0d2 authored by Xin Guan's avatar Xin Guan Committed by Android (Google) Code Review
Browse files

Merge "Revert "Fix job count out of quota."" into main

parents 2e6f5b2d d05e6f92
Loading
Loading
Loading
Loading
+0 −11
Original line number Diff line number Diff line
@@ -1737,17 +1737,6 @@ class JobConcurrencyManager {
                    continue;
                }

                if (!nextPending.isReady()) {
                    // This could happen when the job count reached its quota, the constrains
                    // for the job has been updated but hasn't been removed from the pending
                    // queue yet.
                    if (DEBUG) {
                        Slog.w(TAG, "Pending+not ready job: " + nextPending);
                    }
                    pendingJobQueue.remove(nextPending);
                    continue;
                }

                if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
                    Slog.w(TAG, "Already running similar job to: " + nextPending);
                }
+25 −66
Original line number Diff line number Diff line
@@ -512,7 +512,7 @@ public final class QuotaController extends StateController {

    /** An app has reached its quota. The message should contain a {@link UserPackage} object. */
    @VisibleForTesting
    static final int MSG_REACHED_TIME_QUOTA = 0;
    static final int MSG_REACHED_QUOTA = 0;
    /** Drop any old timing sessions. */
    private static final int MSG_CLEAN_UP_SESSIONS = 1;
    /** Check if a package is now within its quota. */
@@ -524,7 +524,7 @@ public final class QuotaController extends StateController {
     * object.
     */
    @VisibleForTesting
    static final int MSG_REACHED_EJ_TIME_QUOTA = 4;
    static final int MSG_REACHED_EJ_QUOTA = 4;
    /**
     * Process a new {@link UsageEvents.Event}. The event will be the message's object and the
     * userId will the first arg.
@@ -533,11 +533,6 @@ public final class QuotaController extends StateController {
    /** A UID's free quota grace period has ended. */
    @VisibleForTesting
    static final int MSG_END_GRACE_PERIOD = 6;
    /**
     * An app has reached its job count quota. The message should contain a {@link UserPackage}
     * object.
     */
    static final int MSG_REACHED_COUNT_QUOTA = 7;

    public QuotaController(@NonNull JobSchedulerService service,
            @NonNull BackgroundJobsController backgroundJobsController,
@@ -879,37 +874,17 @@ public final class QuotaController extends StateController {
    }

    @VisibleForTesting
    @GuardedBy("mLock")
    boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
        final int standbyBucket = jobStatus.getEffectiveStandbyBucket();
        // A job is within quota if one of the following is true:
        //   1. it was started while the app was in the TOP state
        //   2. the app is currently in the foreground
        //   3. the app overall is within its quota
        if (jobStatus.shouldTreatAsUserInitiatedJob()
        return jobStatus.shouldTreatAsUserInitiatedJob()
                || isTopStartedJobLocked(jobStatus)
                || isUidInForeground(jobStatus.getSourceUid())) {
            return true;
        }

        if (standbyBucket == NEVER_INDEX) return false;

        if (isQuotaFreeLocked(standbyBucket)) return true;

        final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(),
                jobStatus.getSourcePackageName(), standbyBucket);
        if (!(getRemainingExecutionTimeLocked(stats) > 0)) {
            // Out of execution time quota.
            return false;
        }

        if (mService.isCurrentlyRunningLocked(jobStatus)) {
            // if job is running, considered as in quota so it can keep running.
            return true;
        }

        // Check if the app is within job count quota.
        return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats);
                || isUidInForeground(jobStatus.getSourceUid())
                || isWithinQuotaLocked(
                jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
    }

    @GuardedBy("mLock")
@@ -934,11 +909,12 @@ public final class QuotaController extends StateController {
        ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
        // TODO: use a higher minimum remaining time for jobs with MINIMUM priority
        return getRemainingExecutionTimeLocked(stats) > 0
                && isUnderJobCountQuotaLocked(stats)
                && isUnderSessionCountQuotaLocked(stats);
                && isUnderJobCountQuotaLocked(stats, standbyBucket)
                && isUnderSessionCountQuotaLocked(stats, standbyBucket);
    }

    private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) {
    private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
            final int standbyBucket) {
        final long now = sElapsedRealtimeClock.millis();
        final boolean isUnderAllowedTimeQuota =
                (stats.jobRateLimitExpirationTimeElapsed <= now
@@ -947,7 +923,8 @@ public final class QuotaController extends StateController {
                && stats.bgJobCountInWindow < stats.jobCountLimit;
    }

    private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) {
    private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
            final int standbyBucket) {
        final long now = sElapsedRealtimeClock.millis();
        final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
                || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
@@ -1472,7 +1449,6 @@ public final class QuotaController extends StateController {
                stats.jobCountInRateLimitingWindow = 0;
            }
            stats.jobCountInRateLimitingWindow += count;
            stats.bgJobCountInWindow += count;
        }
    }

@@ -1707,11 +1683,10 @@ public final class QuotaController extends StateController {
                    changedJobs.add(js);
                }
            } else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX
                    && realStandbyBucket == js.getEffectiveStandbyBucket()
                    && !mService.isCurrentlyRunningLocked(js)) {
                    && realStandbyBucket == js.getEffectiveStandbyBucket()) {
                // An app in the ACTIVE bucket may be out of quota while the job could be in quota
                // for some reason. Therefore, avoid setting the real value here and check each job
                // individually. Running job need to determine its own quota status as well.
                // individually.
                if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) {
                    changedJobs.add(js);
                }
@@ -1830,8 +1805,9 @@ public final class QuotaController extends StateController {
        }

        ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
        final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats);
        final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats);
        final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
        final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
                standbyBucket);
        final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);

        final boolean inRegularQuota =
@@ -2150,11 +2126,6 @@ public final class QuotaController extends StateController {
                mBgJobCount++;
                if (mRegularJobTimer) {
                    incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1);
                    final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId,
                            mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false);
                    if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
                        mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget();
                    }
                }
                if (mRunningBgJobs.size() == 1) {
                    // Started tracking the first job.
@@ -2286,6 +2257,7 @@ public final class QuotaController extends StateController {
                    // repeatedly plugged in and unplugged, or an app changes foreground state
                    // very frequently, the job count for a package may be artificially high.
                    mBgJobCount = mRunningBgJobs.size();

                    if (mRegularJobTimer) {
                        incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount);
                        // Starting the timer means that all cached execution stats are now
@@ -2312,8 +2284,7 @@ public final class QuotaController extends StateController {
                    return;
                }
                Message msg = mHandler.obtainMessage(
                        mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA,
                        mPkg);
                        mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
                final long timeRemainingMs = mRegularJobTimer
                        ? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
                        : getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
@@ -2330,7 +2301,7 @@ public final class QuotaController extends StateController {

        private void cancelCutoff() {
            mHandler.removeMessages(
                    mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg);
                    mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
        }

        public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
@@ -2586,7 +2557,7 @@ public final class QuotaController extends StateController {
                    break;
                default:
                    if (DEBUG) {
                        Slog.d(TAG, "Dropping usage event " + event.getEventType());
                        Slog.d(TAG, "Dropping event " + event.getEventType());
                    }
                    break;
            }
@@ -2695,7 +2666,7 @@ public final class QuotaController extends StateController {
        public void handleMessage(Message msg) {
            synchronized (mLock) {
                switch (msg.what) {
                    case MSG_REACHED_TIME_QUOTA: {
                    case MSG_REACHED_QUOTA: {
                        UserPackage pkg = (UserPackage) msg.obj;
                        if (DEBUG) {
                            Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
@@ -2714,7 +2685,7 @@ public final class QuotaController extends StateController {
                            // This could potentially happen if an old session phases out while a
                            // job is currently running.
                            // Reschedule message
                            Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg);
                            Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
                            timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
                                    pkg.packageName);
                            if (DEBUG) {
@@ -2724,7 +2695,7 @@ public final class QuotaController extends StateController {
                        }
                        break;
                    }
                    case MSG_REACHED_EJ_TIME_QUOTA: {
                    case MSG_REACHED_EJ_QUOTA: {
                        UserPackage pkg = (UserPackage) msg.obj;
                        if (DEBUG) {
                            Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
@@ -2742,7 +2713,7 @@ public final class QuotaController extends StateController {
                            // This could potentially happen if an old session phases out while a
                            // job is currently running.
                            // Reschedule message
                            Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg);
                            Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
                            timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
                                    pkg.userId, pkg.packageName);
                            if (DEBUG) {
@@ -2752,18 +2723,6 @@ public final class QuotaController extends StateController {
                        }
                        break;
                    }
                    case MSG_REACHED_COUNT_QUOTA: {
                        UserPackage pkg = (UserPackage) msg.obj;
                        if (DEBUG) {
                            Slog.d(TAG, pkg + " has reached its count quota.");
                        }

                        mStateChangedListener.onControllerStateChanged(
                                maybeUpdateConstraintForPkgLocked(
                                        sElapsedRealtimeClock.millis(),
                                        pkg.userId, pkg.packageName));
                        break;
                    }
                    case MSG_CLEAN_UP_SESSIONS:
                        if (DEBUG) {
                            Slog.d(TAG, "Cleaning up timing sessions.");
+4 −71
Original line number Diff line number Diff line
@@ -1978,7 +1978,7 @@ public class QuotaControllerTest {
    }

    @Test
    public void testIsWithinQuotaLocked_UnderDuration_OverJobCountRateLimitWindow() {
    public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
        setDischarging();
        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2021,7 +2021,7 @@ public class QuotaControllerTest {
    }

    @Test
    public void testIsWithinQuotaLocked_OverDuration_OverJobCountRateLimitWindow() {
    public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
        setDischarging();
        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2166,73 +2166,6 @@ public class QuotaControllerTest {
        }
    }

    @Test
    public void testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow() {
        setDischarging();

        JobStatus jobRunning = createJobStatus(
                "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 1);
        JobStatus jobPending = createJobStatus(
                "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 2);
        setStandbyBucket(WORKING_INDEX, jobRunning, jobPending);

        setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);

        long now = JobSchedulerService.sElapsedRealtimeClock.millis();
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 9), false);

        final ExecutionStats stats;
        synchronized (mQuotaController.mLock) {
            stats = mQuotaController.getExecutionStatsLocked(
                    SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
            assertTrue(mQuotaController
                    .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
            assertEquals(10, stats.jobCountLimit);
            assertEquals(9, stats.bgJobCountInWindow);
        }

        when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true);
        when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false);

        InOrder inOrder = inOrder(mJobSchedulerService);
        trackJobs(jobRunning, jobPending);
        // UID in the background.
        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
        // Start the job.
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobRunning);
        }

        advanceElapsedClock(MINUTE_IN_MILLIS);
        // Wait for some extra time to allow for job processing.
        ArraySet<JobStatus> expected = new ArraySet<>();
        expected.add(jobPending);
        inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
                .onControllerStateChanged(eq(expected));

        synchronized (mQuotaController.mLock) {
            assertTrue(mQuotaController.isWithinQuotaLocked(jobRunning));
            assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
            assertTrue(jobRunning.isReady());
            assertFalse(mQuotaController.isWithinQuotaLocked(jobPending));
            assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
            assertFalse(jobPending.isReady());
            assertEquals(10, stats.bgJobCountInWindow);
        }

        advanceElapsedClock(MINUTE_IN_MILLIS);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStopTrackingJobLocked(jobRunning, null);
        }

        synchronized (mQuotaController.mLock) {
            assertFalse(mQuotaController
                    .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
            assertEquals(10, stats.bgJobCountInWindow);
        }
    }

    @Test
    public void testIsWithinQuotaLocked_TimingSession() {
        setDischarging();
@@ -4718,7 +4651,7 @@ public class QuotaControllerTest {
        // Handler is told to check when the quota will be consumed, not when the initial
        // remaining time is over.
        verify(handler, atLeast(1)).sendMessageDelayed(
                argThat(msg -> msg.what == QuotaController.MSG_REACHED_TIME_QUOTA),
                argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA),
                eq(10 * SECOND_IN_MILLIS));
        verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));

@@ -6685,7 +6618,7 @@ public class QuotaControllerTest {
        // Handler is told to check when the quota will be consumed, not when the initial
        // remaining time is over.
        verify(handler, atLeast(1)).sendMessageDelayed(
                argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_TIME_QUOTA),
                argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA),
                eq(10 * SECOND_IN_MILLIS));
        verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
    }