Loading apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +195 −8 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.job; import static com.android.server.job.JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.annotation.IntDef; Loading @@ -39,7 +40,9 @@ import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Pools; import android.util.Slog; import android.util.SparseArrayMap; import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TimeUtils; Loading @@ -60,6 +63,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; /** * This class decides, given the various configuration and the system status, which jobs can start Loading @@ -73,6 +77,12 @@ class JobConcurrencyManager { private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS = CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; private static final String KEY_PKG_CONCURRENCY_LIMIT_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej"; private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3; private static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR = CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular"; private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = MAX_JOB_CONTEXTS_COUNT / 2; /** * Set of possible execution types that a job can have. The actual type(s) of a job are based Loading Loading @@ -165,8 +175,6 @@ class JobConcurrencyManager { private long mLastScreenOnRealtime; private long mLastScreenOffRealtime; private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON = new WorkConfigLimitsPerMemoryTrimLevel( new WorkTypeConfig("screen_on_normal", 11, Loading Loading @@ -274,11 +282,28 @@ class JobConcurrencyManager { private final WorkCountTracker mWorkCountTracker = new WorkCountTracker(); private final Pools.Pool<PackageStats> mPkgStatsPool = new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT); private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>(); private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal; /** Wait for this long after screen off before adjusting the job concurrency. */ private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS; /** * The maximum number of expedited jobs a single userId-package can have running simultaneously. * TOP apps are not limited. */ private long mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ; /** * The maximum number of regular jobs a single userId-package can have running simultaneously. * TOP apps are not limited. */ private long mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR; /** Current memory trim level. */ private int mLastMemoryTrimLevel; Loading @@ -286,6 +311,9 @@ class JobConcurrencyManager { private long mNextSystemStateRefreshTime; private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000; private final Consumer<PackageStats> mPackageStatsStagingCountClearer = PackageStats::resetStagedCount; private final StatLogger mStatLogger = new StatLogger(new String[]{ "assignJobsToContexts", "refreshSystemState", Loading Loading @@ -330,6 +358,21 @@ class JobConcurrencyManager { onInteractiveStateChanged(mPowerManager.isInteractive()); } @GuardedBy("mLock") void onAppRemovedLocked(String pkgName, int uid) { final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName); if (packageStats != null) { if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) { // Don't delete the object just yet. We'll remove it in onJobCompleted() when the // jobs officially stop running. Slog.w(TAG, pkgName + "(" + uid + ") marked as removed before jobs stopped running"); } else { mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName); } } } void onUserRemoved(int userId) { mGracePeriodObserver.onUserRemoved(userId); } Loading Loading @@ -557,6 +600,7 @@ class JobConcurrencyManager { boolean startingJob = false; int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; String preemptReason = null; final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending); // TODO(141645789): rewrite this to look at empty contexts first so we don't // unnecessarily preempt for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) { Loading @@ -566,7 +610,7 @@ class JobConcurrencyManager { final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) || (preferredUid == JobServiceContext.NO_PREFERRED_UID); if (preferredUidOkay && workType != WORK_TYPE_NONE) { if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) { // This slot is free, and we haven't yet hit the limit on // concurrent jobs... we can just throw the job in to here. selectedContextId = j; Loading @@ -579,9 +623,11 @@ class JobConcurrencyManager { continue; } if (job.getUid() != nextPending.getUid()) { // Maybe stop the job if it has had its day in the sun. // Maybe stop the job if it has had its day in the sun. Don't let a different // app preempt jobs started for TOP apps though. final String reason = shouldStopJobReason[j]; if (reason != null && mWorkCountTracker.canJobStart(allWorkTypes, if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP && reason != null && mWorkCountTracker.canJobStart(allWorkTypes, activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) { // Right now, the way the code is set up, we don't need to explicitly // assign the new job to this context since we'll reassign when the Loading @@ -608,23 +654,27 @@ class JobConcurrencyManager { // actually starting a job, so don't set startingJob. } } final PackageStats packageStats = getPkgStatsLocked( nextPending.getSourceUserId(), nextPending.getSourcePackageName()); if (selectedContextId != -1) { contextIdToJobMap[selectedContextId] = nextPending; slotChanged[selectedContextId] = true; preemptReasonCodeForContext[selectedContextId] = preemptReasonCode; preemptReasonForContext[selectedContextId] = preemptReason; packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob()); } if (startingJob) { // Increase the counters when we're going to start a job. workTypeForContext[selectedContextId] = workType; mWorkCountTracker.stageJob(workType, allWorkTypes); mActivePkgStats.add( nextPending.getSourceUserId(), nextPending.getSourcePackageName(), packageStats); } } if (DEBUG) { Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); } if (DEBUG) { Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString()); } Loading Loading @@ -660,6 +710,7 @@ class JobConcurrencyManager { } } mWorkCountTracker.resetStagingCount(); mActivePkgStats.forEach(mPackageStatsStagingCountClearer); noteConcurrency(); } Loading Loading @@ -701,6 +752,48 @@ class JobConcurrencyManager { } } @GuardedBy("mLock") @NonNull private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) { PackageStats packageStats = mActivePkgStats.get(userId, packageName); if (packageStats == null) { packageStats = mPkgStatsPool.acquire(); if (packageStats == null) { packageStats = new PackageStats(); } packageStats.setPackage(userId, packageName); } return packageStats; } @GuardedBy("mLock") private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) { if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { // Don't restrict top apps' concurrency. The work type limits will make sure // background jobs have slots to run if the system has resources. return false; } // Use < instead of <= as that gives us a little wiggle room in case a new job comes // along very shortly. if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) { // Don't artificially limit a single package if we don't even have enough jobs to use // the maximum number of slots. We'll preempt the job later if we need the slot. return false; } final PackageStats packageStats = mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); if (packageStats == null) { // No currently running jobs. return false; } if (jobStatus.shouldTreatAsExpeditedJob()) { return packageStats.numRunningEj + packageStats.numStagedEj < mPkgConcurrencyLimitEj; } else { return packageStats.numRunningRegular + packageStats.numStagedRegular < mPkgConcurrencyLimitRegular; } } @GuardedBy("mLock") private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType) { Loading @@ -708,12 +801,18 @@ class JobConcurrencyManager { for (int ic = 0; ic < controllers.size(); ic++) { controllers.get(ic).prepareForExecutionLocked(jobStatus); } final PackageStats packageStats = getPkgStatsLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob()); if (!worker.executeRunnableJob(jobStatus, workType)) { Slog.e(TAG, "Error executing " + jobStatus); mWorkCountTracker.onStagedJobFailed(workType); } else { mRunningJobs.add(jobStatus); mWorkCountTracker.onJobStarted(workType); packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob()); mActivePkgStats.add( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats); } final List<JobStatus> pendingJobs = mService.mPendingJobs; if (pendingJobs.remove(jobStatus)) { Loading @@ -726,6 +825,18 @@ class JobConcurrencyManager { @WorkType final int workType) { mWorkCountTracker.onJobFinished(workType); mRunningJobs.remove(jobStatus); final PackageStats packageStats = mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); if (packageStats == null) { Slog.wtf(TAG, "Running job didn't have an active PackageStats object"); } else { packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob); if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) { mActivePkgStats.delete(packageStats.userId, packageStats.packageName); mPkgStatsPool.release(packageStats); } } final List<JobStatus> pendingJobs = mService.mPendingJobs; if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) { updateCounterConfigLocked(); Loading @@ -746,7 +857,7 @@ class JobConcurrencyManager { } if (worker.getPreferredUid() != nextPending.getUid()) { if (backupJob == null) { if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) { int allWorkTypes = getJobWorkTypes(nextPending); int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); if (workAsType != WORK_TYPE_NONE) { Loading @@ -758,6 +869,13 @@ class JobConcurrencyManager { continue; } // Only bypass the concurrent limit if we had preempted the job due to a higher // priority job. if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority && isPkgConcurrencyLimitedLocked(nextPending)) { continue; } if (highestPriorityJob == null || highestPriorityJob.lastEvaluatedPriority < nextPending.lastEvaluatedPriority) { Loading Loading @@ -815,6 +933,10 @@ class JobConcurrencyManager { continue; } if (isPkgConcurrencyLimitedLocked(nextPending)) { continue; } final int allWorkTypes = getJobWorkTypes(nextPending); final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); if (workAsType == WORK_TYPE_NONE) { Loading Loading @@ -979,8 +1101,16 @@ class JobConcurrencyManager { CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties); CONFIG_LIMITS_SCREEN_OFF.low.update(properties); CONFIG_LIMITS_SCREEN_OFF.critical.update(properties); // Package concurrency limits must in the range [1, MAX_JOB_CONTEXTS_COUNT]. mPkgConcurrencyLimitEj = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ))); mPkgConcurrencyLimitRegular = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, properties.getInt( KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR))); } @GuardedBy("mLock") public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) { pw.println("Concurrency:"); Loading @@ -989,6 +1119,8 @@ class JobConcurrencyManager { pw.println("Configuration:"); pw.increaseIndent(); pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println(); pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println(); pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println(); pw.println(); CONFIG_LIMITS_SCREEN_ON.normal.dump(pw); pw.println(); Loading Loading @@ -1033,6 +1165,12 @@ class JobConcurrencyManager { pw.println(mLastMemoryTrimLevel); pw.println(); pw.println("Active Package stats:"); pw.increaseIndent(); mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw)); pw.decreaseIndent(); pw.println(); pw.print("User Grace Period: "); pw.println(mGracePeriodObserver.mGracePeriodExpiration); pw.println(); Loading Loading @@ -1620,4 +1758,53 @@ class JobConcurrencyManager { return sb.toString(); } } private static class PackageStats { public int userId; public String packageName; public int numRunningEj; public int numRunningRegular; public int numStagedEj; public int numStagedRegular; private void setPackage(int userId, @NonNull String packageName) { this.userId = userId; this.packageName = packageName; numRunningEj = numRunningRegular = 0; resetStagedCount(); } private void resetStagedCount() { numStagedEj = numStagedRegular = 0; } private void adjustRunningCount(boolean add, boolean forEj) { if (forEj) { numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1)); } else { numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1)); } } private void adjustStagedCount(boolean add, boolean forEj) { if (forEj) { numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1)); } else { numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1)); } } @GuardedBy("mLock") private void dumpLocked(IndentingPrintWriter pw) { pw.print("PackageStats{"); pw.print(userId); pw.print("-"); pw.print(packageName); pw.print("#runEJ", numRunningEj); pw.print("#runReg", numRunningRegular); pw.print("#stagedEJ", numStagedEj); pw.print("#stagedReg", numStagedRegular); pw.println("}"); } } } apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +1 −0 Original line number Diff line number Diff line Loading @@ -816,6 +816,7 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); } mDebuggableApps.remove(pkgName); mConcurrencyManager.onAppRemovedLocked(pkgName, pkgUid); } } else if (Intent.ACTION_USER_ADDED.equals(action)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); Loading Loading
apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +195 −8 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.job; import static com.android.server.job.JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.annotation.IntDef; Loading @@ -39,7 +40,9 @@ import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Pools; import android.util.Slog; import android.util.SparseArrayMap; import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TimeUtils; Loading @@ -60,6 +63,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; /** * This class decides, given the various configuration and the system status, which jobs can start Loading @@ -73,6 +77,12 @@ class JobConcurrencyManager { private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS = CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; private static final String KEY_PKG_CONCURRENCY_LIMIT_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej"; private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3; private static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR = CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular"; private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = MAX_JOB_CONTEXTS_COUNT / 2; /** * Set of possible execution types that a job can have. The actual type(s) of a job are based Loading Loading @@ -165,8 +175,6 @@ class JobConcurrencyManager { private long mLastScreenOnRealtime; private long mLastScreenOffRealtime; private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON = new WorkConfigLimitsPerMemoryTrimLevel( new WorkTypeConfig("screen_on_normal", 11, Loading Loading @@ -274,11 +282,28 @@ class JobConcurrencyManager { private final WorkCountTracker mWorkCountTracker = new WorkCountTracker(); private final Pools.Pool<PackageStats> mPkgStatsPool = new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT); private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>(); private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal; /** Wait for this long after screen off before adjusting the job concurrency. */ private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS; /** * The maximum number of expedited jobs a single userId-package can have running simultaneously. * TOP apps are not limited. */ private long mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ; /** * The maximum number of regular jobs a single userId-package can have running simultaneously. * TOP apps are not limited. */ private long mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR; /** Current memory trim level. */ private int mLastMemoryTrimLevel; Loading @@ -286,6 +311,9 @@ class JobConcurrencyManager { private long mNextSystemStateRefreshTime; private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000; private final Consumer<PackageStats> mPackageStatsStagingCountClearer = PackageStats::resetStagedCount; private final StatLogger mStatLogger = new StatLogger(new String[]{ "assignJobsToContexts", "refreshSystemState", Loading Loading @@ -330,6 +358,21 @@ class JobConcurrencyManager { onInteractiveStateChanged(mPowerManager.isInteractive()); } @GuardedBy("mLock") void onAppRemovedLocked(String pkgName, int uid) { final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName); if (packageStats != null) { if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) { // Don't delete the object just yet. We'll remove it in onJobCompleted() when the // jobs officially stop running. Slog.w(TAG, pkgName + "(" + uid + ") marked as removed before jobs stopped running"); } else { mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName); } } } void onUserRemoved(int userId) { mGracePeriodObserver.onUserRemoved(userId); } Loading Loading @@ -557,6 +600,7 @@ class JobConcurrencyManager { boolean startingJob = false; int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; String preemptReason = null; final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending); // TODO(141645789): rewrite this to look at empty contexts first so we don't // unnecessarily preempt for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) { Loading @@ -566,7 +610,7 @@ class JobConcurrencyManager { final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) || (preferredUid == JobServiceContext.NO_PREFERRED_UID); if (preferredUidOkay && workType != WORK_TYPE_NONE) { if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) { // This slot is free, and we haven't yet hit the limit on // concurrent jobs... we can just throw the job in to here. selectedContextId = j; Loading @@ -579,9 +623,11 @@ class JobConcurrencyManager { continue; } if (job.getUid() != nextPending.getUid()) { // Maybe stop the job if it has had its day in the sun. // Maybe stop the job if it has had its day in the sun. Don't let a different // app preempt jobs started for TOP apps though. final String reason = shouldStopJobReason[j]; if (reason != null && mWorkCountTracker.canJobStart(allWorkTypes, if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP && reason != null && mWorkCountTracker.canJobStart(allWorkTypes, activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) { // Right now, the way the code is set up, we don't need to explicitly // assign the new job to this context since we'll reassign when the Loading @@ -608,23 +654,27 @@ class JobConcurrencyManager { // actually starting a job, so don't set startingJob. } } final PackageStats packageStats = getPkgStatsLocked( nextPending.getSourceUserId(), nextPending.getSourcePackageName()); if (selectedContextId != -1) { contextIdToJobMap[selectedContextId] = nextPending; slotChanged[selectedContextId] = true; preemptReasonCodeForContext[selectedContextId] = preemptReasonCode; preemptReasonForContext[selectedContextId] = preemptReason; packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob()); } if (startingJob) { // Increase the counters when we're going to start a job. workTypeForContext[selectedContextId] = workType; mWorkCountTracker.stageJob(workType, allWorkTypes); mActivePkgStats.add( nextPending.getSourceUserId(), nextPending.getSourcePackageName(), packageStats); } } if (DEBUG) { Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); } if (DEBUG) { Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString()); } Loading Loading @@ -660,6 +710,7 @@ class JobConcurrencyManager { } } mWorkCountTracker.resetStagingCount(); mActivePkgStats.forEach(mPackageStatsStagingCountClearer); noteConcurrency(); } Loading Loading @@ -701,6 +752,48 @@ class JobConcurrencyManager { } } @GuardedBy("mLock") @NonNull private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) { PackageStats packageStats = mActivePkgStats.get(userId, packageName); if (packageStats == null) { packageStats = mPkgStatsPool.acquire(); if (packageStats == null) { packageStats = new PackageStats(); } packageStats.setPackage(userId, packageName); } return packageStats; } @GuardedBy("mLock") private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) { if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { // Don't restrict top apps' concurrency. The work type limits will make sure // background jobs have slots to run if the system has resources. return false; } // Use < instead of <= as that gives us a little wiggle room in case a new job comes // along very shortly. if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) { // Don't artificially limit a single package if we don't even have enough jobs to use // the maximum number of slots. We'll preempt the job later if we need the slot. return false; } final PackageStats packageStats = mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); if (packageStats == null) { // No currently running jobs. return false; } if (jobStatus.shouldTreatAsExpeditedJob()) { return packageStats.numRunningEj + packageStats.numStagedEj < mPkgConcurrencyLimitEj; } else { return packageStats.numRunningRegular + packageStats.numStagedRegular < mPkgConcurrencyLimitRegular; } } @GuardedBy("mLock") private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType) { Loading @@ -708,12 +801,18 @@ class JobConcurrencyManager { for (int ic = 0; ic < controllers.size(); ic++) { controllers.get(ic).prepareForExecutionLocked(jobStatus); } final PackageStats packageStats = getPkgStatsLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob()); if (!worker.executeRunnableJob(jobStatus, workType)) { Slog.e(TAG, "Error executing " + jobStatus); mWorkCountTracker.onStagedJobFailed(workType); } else { mRunningJobs.add(jobStatus); mWorkCountTracker.onJobStarted(workType); packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob()); mActivePkgStats.add( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats); } final List<JobStatus> pendingJobs = mService.mPendingJobs; if (pendingJobs.remove(jobStatus)) { Loading @@ -726,6 +825,18 @@ class JobConcurrencyManager { @WorkType final int workType) { mWorkCountTracker.onJobFinished(workType); mRunningJobs.remove(jobStatus); final PackageStats packageStats = mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); if (packageStats == null) { Slog.wtf(TAG, "Running job didn't have an active PackageStats object"); } else { packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob); if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) { mActivePkgStats.delete(packageStats.userId, packageStats.packageName); mPkgStatsPool.release(packageStats); } } final List<JobStatus> pendingJobs = mService.mPendingJobs; if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) { updateCounterConfigLocked(); Loading @@ -746,7 +857,7 @@ class JobConcurrencyManager { } if (worker.getPreferredUid() != nextPending.getUid()) { if (backupJob == null) { if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) { int allWorkTypes = getJobWorkTypes(nextPending); int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); if (workAsType != WORK_TYPE_NONE) { Loading @@ -758,6 +869,13 @@ class JobConcurrencyManager { continue; } // Only bypass the concurrent limit if we had preempted the job due to a higher // priority job. if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority && isPkgConcurrencyLimitedLocked(nextPending)) { continue; } if (highestPriorityJob == null || highestPriorityJob.lastEvaluatedPriority < nextPending.lastEvaluatedPriority) { Loading Loading @@ -815,6 +933,10 @@ class JobConcurrencyManager { continue; } if (isPkgConcurrencyLimitedLocked(nextPending)) { continue; } final int allWorkTypes = getJobWorkTypes(nextPending); final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); if (workAsType == WORK_TYPE_NONE) { Loading Loading @@ -979,8 +1101,16 @@ class JobConcurrencyManager { CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties); CONFIG_LIMITS_SCREEN_OFF.low.update(properties); CONFIG_LIMITS_SCREEN_OFF.critical.update(properties); // Package concurrency limits must in the range [1, MAX_JOB_CONTEXTS_COUNT]. mPkgConcurrencyLimitEj = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ))); mPkgConcurrencyLimitRegular = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, properties.getInt( KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR))); } @GuardedBy("mLock") public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) { pw.println("Concurrency:"); Loading @@ -989,6 +1119,8 @@ class JobConcurrencyManager { pw.println("Configuration:"); pw.increaseIndent(); pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println(); pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println(); pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println(); pw.println(); CONFIG_LIMITS_SCREEN_ON.normal.dump(pw); pw.println(); Loading Loading @@ -1033,6 +1165,12 @@ class JobConcurrencyManager { pw.println(mLastMemoryTrimLevel); pw.println(); pw.println("Active Package stats:"); pw.increaseIndent(); mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw)); pw.decreaseIndent(); pw.println(); pw.print("User Grace Period: "); pw.println(mGracePeriodObserver.mGracePeriodExpiration); pw.println(); Loading Loading @@ -1620,4 +1758,53 @@ class JobConcurrencyManager { return sb.toString(); } } private static class PackageStats { public int userId; public String packageName; public int numRunningEj; public int numRunningRegular; public int numStagedEj; public int numStagedRegular; private void setPackage(int userId, @NonNull String packageName) { this.userId = userId; this.packageName = packageName; numRunningEj = numRunningRegular = 0; resetStagedCount(); } private void resetStagedCount() { numStagedEj = numStagedRegular = 0; } private void adjustRunningCount(boolean add, boolean forEj) { if (forEj) { numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1)); } else { numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1)); } } private void adjustStagedCount(boolean add, boolean forEj) { if (forEj) { numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1)); } else { numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1)); } } @GuardedBy("mLock") private void dumpLocked(IndentingPrintWriter pw) { pw.print("PackageStats{"); pw.print(userId); pw.print("-"); pw.print(packageName); pw.print("#runEJ", numRunningEj); pw.print("#runReg", numRunningRegular); pw.print("#stagedEJ", numStagedEj); pw.print("#stagedReg", numStagedRegular); pw.println("}"); } } }
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +1 −0 Original line number Diff line number Diff line Loading @@ -816,6 +816,7 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); } mDebuggableApps.remove(pkgName); mConcurrencyManager.onAppRemovedLocked(pkgName, pkgUid); } } else if (Intent.ACTION_USER_ADDED.equals(action)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); Loading