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

Commit 31b53cfe authored by Zhen Zhang's avatar Zhen Zhang Committed by Kweku Adams
Browse files

Add concurrency restriction at the USER level

Add a new layer of concurrency limitation at the USER level. Jobs that
are from the background users will be restricted at a maximum
concurrency.

Bug: 175643251
Test: atest JobSchedulerServiceTest
Test: atest WorkCountTrackerTest
Test: atest WorkTypeConfigTest
Test: atest JobCurrencyManagerTest
Test: atest GracePeriodObserverTest
Test: atest JobConcurrencyManagerTest
Test: atest CtsJobSchedulerTestCases
Test: atest FrameworksServicesTests:PrioritySchedulingTest

Change-Id: I82932355d30ac48dd40adb420524483908f21a3e
(cherry picked from commit e30d7fc0)
parent 7b3c0ef3
Loading
Loading
Loading
Loading
+190 −14
Original line number Diff line number Diff line
@@ -16,33 +16,43 @@

package com.android.server.job;

import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.UserSwitchObserver;
import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.util.StatLogger;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.StateController;
import com.android.server.pm.UserManagerInternal;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -66,12 +76,14 @@ class JobConcurrencyManager {
    static final int WORK_TYPE_NONE = 0;
    static final int WORK_TYPE_TOP = 1 << 0;
    static final int WORK_TYPE_BG = 1 << 1;
    private static final int NUM_WORK_TYPES = 2;
    static final int WORK_TYPE_BGUSER = 1 << 2;
    private static final int NUM_WORK_TYPES = 3;

    @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
            WORK_TYPE_NONE,
            WORK_TYPE_TOP,
            WORK_TYPE_BG
            WORK_TYPE_BG,
            WORK_TYPE_BGUSER
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface WorkType {
@@ -98,22 +110,26 @@ class JobConcurrencyManager {
                            // defaultMin
                            List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 2)),
                            // defaultMax
                            List.of(Pair.create(WORK_TYPE_BG, 6))),
                            List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4))
                    ),
                    new WorkTypeConfig("screen_on_moderate", 8,
                            // defaultMin
                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
                            // defaultMax
                            List.of(Pair.create(WORK_TYPE_BG, 4))),
                            List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2))
                    ),
                    new WorkTypeConfig("screen_on_low", 5,
                            // defaultMin
                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
                            // defaultMax
                            List.of(Pair.create(WORK_TYPE_BG, 1))),
                            List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
                    ),
                    new WorkTypeConfig("screen_on_critical", 5,
                            // defaultMin
                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
                            // defaultMax
                            List.of(Pair.create(WORK_TYPE_BG, 1)))
                            List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
                    )
            );
    private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF =
            new WorkConfigLimitsPerMemoryTrimLevel(
@@ -121,22 +137,26 @@ class JobConcurrencyManager {
                            // defaultMin
                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
                            // defaultMax
                            List.of(Pair.create(WORK_TYPE_BG, 6))),
                            List.of(Pair.create(WORK_TYPE_BG, 6), Pair.create(WORK_TYPE_BGUSER, 4))
                    ),
                    new WorkTypeConfig("screen_off_moderate", 10,
                            // defaultMin
                            List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
                            // defaultMax
                            List.of(Pair.create(WORK_TYPE_BG, 4))),
                            List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2))
                    ),
                    new WorkTypeConfig("screen_off_low", 5,
                            // defaultMin
                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
                            // defaultMax
                            List.of(Pair.create(WORK_TYPE_BG, 1))),
                            List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
                    ),
                    new WorkTypeConfig("screen_off_critical", 5,
                            // defaultMin
                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
                            // defaultMax
                            List.of(Pair.create(WORK_TYPE_BG, 1)))
                            List.of(Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 1))
                    )
            );

    /**
@@ -171,6 +191,10 @@ class JobConcurrencyManager {
            "assignJobsToContexts",
            "refreshSystemState",
    });
    @VisibleForTesting
    GracePeriodObserver mGracePeriodObserver;
    @VisibleForTesting
    boolean mShouldRestrictBgUser;

    interface Stats {
        int ASSIGN_JOBS_TO_CONTEXTS = 0;
@@ -182,9 +206,13 @@ class JobConcurrencyManager {
    JobConcurrencyManager(JobSchedulerService service) {
        mService = service;
        mLock = mService.mLock;
        mContext = service.getContext();
        mContext = service.getTestableContext();

        mHandler = JobSchedulerBackgroundThread.getHandler();

        mGracePeriodObserver = new GracePeriodObserver(mContext);
        mShouldRestrictBgUser = mContext.getResources().getBoolean(
                R.bool.config_jobSchedulerRestrictBackgroundUser);
    }

    public void onSystemReady() {
@@ -193,10 +221,18 @@ class JobConcurrencyManager {
        final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        mContext.registerReceiver(mReceiver, filter);
        try {
            ActivityManager.getService().registerUserSwitchObserver(mGracePeriodObserver, TAG);
        } catch (RemoteException e) {
        }

        onInteractiveStateChanged(mPowerManager.isInteractive());
    }

    void onUserRemoved(int userId) {
        mGracePeriodObserver.onUserRemoved(userId);
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
@@ -224,7 +260,7 @@ class JobConcurrencyManager {
                Slog.d(TAG, "Interactive: " + interactive);
            }

            final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
            final long nowRealtime = sElapsedRealtimeClock.millis();
            if (interactive) {
                mLastScreenOnRealtime = nowRealtime;
                mEffectiveInteractiveState = true;
@@ -261,7 +297,7 @@ class JobConcurrencyManager {
            if (mLastScreenOnRealtime > mLastScreenOffRealtime) {
                return;
            }
            final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
            final long now = sElapsedRealtimeClock.millis();
            if ((mLastScreenOffRealtime + mScreenOffAdjustmentDelayMs) > now) {
                return;
            }
@@ -723,6 +759,10 @@ class JobConcurrencyManager {
            pw.print(mLastMemoryTrimLevel);
            pw.println();

            pw.print("User Grace Period: ");
            pw.print(mGracePeriodObserver.mGracePeriodExpiration);
            pw.println();

            mStatLogger.dump(pw);
        } finally {
            pw.decreaseIndent();
@@ -748,14 +788,44 @@ class JobConcurrencyManager {
        proto.end(token);
    }

    /**
     * Decides whether a job is from the current foreground user or the equivalent.
     */
    @VisibleForTesting
    boolean shouldRunAsFgUserJob(JobStatus job) {
        if (!mShouldRestrictBgUser) return true;
        int userId = job.getSourceUserId();
        UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
        UserInfo userInfo = um.getUserInfo(userId);

        // If the user has a parent user (e.g. a work profile of another user), the user should be
        // treated equivalent as its parent user.
        if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
                && userInfo.profileGroupId != userId) {
            userId = userInfo.profileGroupId;
            userInfo = um.getUserInfo(userId);
        }

        int currentUser = LocalServices.getService(ActivityManagerInternal.class)
                .getCurrentUserId();
        // A user is treated as foreground user if any of the followings is true:
        // 1. The user is current user
        // 2. The user is primary user
        // 3. The user's grace period has not expired
        return currentUser == userId || userInfo.isPrimary()
                || mGracePeriodObserver.isWithinGracePeriodForUser(userId);
    }

    int getJobWorkTypes(@NonNull JobStatus js) {
        int classification = 0;
        // TODO(171305774): create dedicated work type for EJ and FGS
        if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP
                || js.shouldTreatAsExpeditedJob()) {
            classification |= WORK_TYPE_TOP;
        } else {
        } else if (shouldRunAsFgUserJob(js)) {
            classification |= WORK_TYPE_BG;
        } else {
            classification |= WORK_TYPE_BGUSER;
        }
        return classification;
    }
@@ -766,8 +836,12 @@ class JobConcurrencyManager {
                CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
        private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_";
        private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_";
        private static final String KEY_PREFIX_MAX_BGUSER =
                CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_";
        private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_";
        private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_";
        private static final String KEY_PREFIX_MIN_BGUSER =
                CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_";
        private final String mConfigIdentifier;

        private int mMaxTotal;
@@ -815,6 +889,10 @@ class JobConcurrencyManager {
                    properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier,
                            mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal))));
            mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg);
            final int maxBgUser = Math.max(1, Math.min(mMaxTotal,
                    properties.getInt(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
                            mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER, mMaxTotal))));
            mMaxAllowedSlots.put(WORK_TYPE_BGUSER, maxBgUser);

            int remaining = mMaxTotal;
            mMinReservedSlots.clear();
@@ -829,6 +907,12 @@ class JobConcurrencyManager {
                    properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier,
                            mDefaultMinReservedSlots.get(WORK_TYPE_BG))));
            mMinReservedSlots.put(WORK_TYPE_BG, minBg);
            remaining -= minBg;
            // Ensure bg user is in the range [0, min(maxBgUser, remaining)]
            final int minBgUser = Math.max(0, Math.min(Math.min(maxBgUser, remaining),
                    properties.getInt(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
                            mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER, 0))));
            mMinReservedSlots.put(WORK_TYPE_BGUSER, minBgUser);
        }

        int getMaxTotal() {
@@ -853,6 +937,10 @@ class JobConcurrencyManager {
                    .println();
            pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG))
                    .println();
            pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
                    mMinReservedSlots.get(WORK_TYPE_BGUSER)).println();
            pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
                    mMaxAllowedSlots.get(WORK_TYPE_BGUSER)).println();
        }
    }

@@ -872,6 +960,58 @@ class JobConcurrencyManager {
        }
    }

    /**
     * This class keeps the track of when a user's grace period expires.
     */
    @VisibleForTesting
    static class GracePeriodObserver extends UserSwitchObserver {
        // Key is UserId and Value is the time when grace period expires
        @VisibleForTesting
        final SparseLongArray mGracePeriodExpiration = new SparseLongArray();
        private int mCurrentUserId;
        @VisibleForTesting
        int mGracePeriod;
        private final UserManagerInternal mUserManagerInternal;
        final Object mLock = new Object();


        GracePeriodObserver(Context context) {
            mCurrentUserId = LocalServices.getService(ActivityManagerInternal.class)
                    .getCurrentUserId();
            mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
            mGracePeriod = Math.max(0, context.getResources().getInteger(
                    R.integer.config_jobSchedulerUserGracePeriod));
        }

        @Override
        public void onUserSwitchComplete(int newUserId) {
            final long expiration = sElapsedRealtimeClock.millis() + mGracePeriod;
            synchronized (mLock) {
                if (mCurrentUserId != UserHandle.USER_NULL
                        && mUserManagerInternal.exists(mCurrentUserId)) {
                    mGracePeriodExpiration.append(mCurrentUserId, expiration);
                }
                mGracePeriodExpiration.delete(newUserId);
                mCurrentUserId = newUserId;
            }
        }

        void onUserRemoved(int userId) {
            synchronized (mLock) {
                mGracePeriodExpiration.delete(userId);
            }
        }

        @VisibleForTesting
        public boolean isWithinGracePeriodForUser(int userId) {
            synchronized (mLock) {
                return userId == mCurrentUserId
                        || sElapsedRealtimeClock.millis()
                        < mGracePeriodExpiration.get(userId, Long.MAX_VALUE);
            }
        }
    }

    /**
     * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs
     * are running/pending, how many more job can start.
@@ -900,12 +1040,16 @@ class JobConcurrencyManager {
            mConfigNumReservedSlots.put(WORK_TYPE_TOP,
                    workTypeConfig.getMinReserved(WORK_TYPE_TOP));
            mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG));
            mConfigNumReservedSlots.put(WORK_TYPE_BGUSER,
                    workTypeConfig.getMinReserved(WORK_TYPE_BGUSER));
            mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP));
            mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG));
            mConfigAbsoluteMaxSlots.put(WORK_TYPE_BGUSER, workTypeConfig.getMax(WORK_TYPE_BGUSER));

            mNumUnspecialized = mConfigMaxTotal;
            mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP);
            mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG);
            mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BGUSER);
            mNumUnspecializedRemaining = mConfigMaxTotal;
            for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
                mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
@@ -937,6 +1081,9 @@ class JobConcurrencyManager {
            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
                mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1);
            }
            if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
                mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + 1);
            }
        }

        void stageJob(@WorkType int workType) {
@@ -1029,6 +1176,11 @@ class JobConcurrencyManager {
            int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
            mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg);
            mNumUnspecialized -= resBg;
            final int numBgUser = mNumRunningJobs.get(WORK_TYPE_BGUSER)
                    + mNumPendingJobs.get(WORK_TYPE_BGUSER);
            int resBgUser = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BGUSER), numBgUser);
            mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser);
            mNumUnspecialized -= resBgUser;

            mNumUnspecializedRemaining = mNumUnspecialized;
            // Account for already running jobs after we've assigned the minimum number of slots.
@@ -1048,6 +1200,14 @@ class JobConcurrencyManager {
                resBg += unspecializedAssigned;
                mNumUnspecializedRemaining -= extraRunning;
            }
            extraRunning = (mNumRunningJobs.get(WORK_TYPE_BGUSER) - resBgUser);
            if (extraRunning > 0) {
                unspecializedAssigned = Math.max(0,
                        Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER) - resBgUser,
                                extraRunning));
                resBgUser += unspecializedAssigned;
                mNumUnspecializedRemaining -= extraRunning;
            }

            // Assign remaining unspecialized based on ranking.
            unspecializedAssigned = Math.max(0,
@@ -1060,6 +1220,12 @@ class JobConcurrencyManager {
                            Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg));
            mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
            mNumUnspecializedRemaining -= unspecializedAssigned;
            unspecializedAssigned = Math.max(0,
                    Math.min(mNumUnspecializedRemaining,
                            Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER), numBgUser)
                                    - resBgUser));
            mNumActuallyReservedSlots.put(WORK_TYPE_BGUSER, resBgUser + unspecializedAssigned);
            mNumUnspecializedRemaining -= unspecializedAssigned;
        }

        int canJobStart(int workTypes) {
@@ -1081,6 +1247,16 @@ class JobConcurrencyManager {
                    return WORK_TYPE_BG;
                }
            }
            if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
                final int maxAllowed = Math.min(
                        mConfigAbsoluteMaxSlots.get(WORK_TYPE_BGUSER),
                        mNumActuallyReservedSlots.get(WORK_TYPE_BGUSER)
                                + mNumUnspecializedRemaining);
                if (mNumRunningJobs.get(WORK_TYPE_BGUSER) + mNumStartingJobs.get(WORK_TYPE_BGUSER)
                        < maxAllowed) {
                    return WORK_TYPE_BGUSER;
                }
            }
            return WORK_TYPE_NONE;
        }

+1 −0
Original line number Diff line number Diff line
@@ -743,6 +743,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                        mControllers.get(c).onUserRemovedLocked(userId);
                    }
                }
                mConcurrencyManager.onUserRemoved(userId);
            } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
                // Has this package scheduled any jobs, such that we will take action
                // if it were to be force-stopped?
+5 −0
Original line number Diff line number Diff line
@@ -3477,6 +3477,11 @@
    <!-- The alarm window (in milliseconds) that JobScheduler uses to enter the idle state -->
    <integer name="config_jobSchedulerIdleWindowSlop">300000</integer>

    <!-- If true, jobs from background user will be restricted -->
    <bool name="config_jobSchedulerRestrictBackgroundUser">false</bool>
    <!-- The length of grace period after user becomes background user -->
    <integer name="config_jobSchedulerUserGracePeriod">60000</integer>

    <!-- If true, all guest users created on the device will be ephemeral. -->
    <bool name="config_guestUserEphemeral">false</bool>

+2 −0
Original line number Diff line number Diff line
@@ -2680,6 +2680,8 @@

  <java-symbol type="integer" name="config_jobSchedulerInactivityIdleThreshold" />
  <java-symbol type="integer" name="config_jobSchedulerIdleWindowSlop" />
  <java-symbol type="bool" name="config_jobSchedulerRestrictBackgroundUser" />
  <java-symbol type="integer" name="config_jobSchedulerUserGracePeriod" />

  <java-symbol type="style" name="Animation.ImmersiveModeConfirmation" />

+188 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading