Loading apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +228 −124 Original line number Diff line number Diff line Loading @@ -273,23 +273,22 @@ class JobConcurrencyManager { */ JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT]; boolean[] mRecycledSlotChanged = new boolean[MAX_JOB_CONTEXTS_COUNT]; private final ArraySet<ContextAssignment> mRecycledChanged = new ArraySet<>(); private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>(); private final ArraySet<ContextAssignment> mRecycledPreferredUidOnly = new ArraySet<>(); private final ArraySet<ContextAssignment> mRecycledStoppable = new ArraySet<>(); int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT]; String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT]; int[] mRecycledPreemptReasonCodeForContext = new int[MAX_JOB_CONTEXTS_COUNT]; String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT]; private final Pools.Pool<ContextAssignment> mContextAssignmentPool = new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT); /** * Set of JobServiceContexts that we use to run jobs. * Set of JobServiceContexts that are actively running jobs. */ final List<JobServiceContext> mActiveServices = new ArrayList<>(); /** Set of JobServiceContexts that aren't currently running any jobs. */ final ArraySet<JobServiceContext> mIdleContexts = new ArraySet<>(); private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>(); private final WorkCountTracker mWorkCountTracker = new WorkCountTracker(); Loading Loading @@ -378,7 +377,7 @@ class JobConcurrencyManager { final IBatteryStats batteryStats = IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)); for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { mActiveServices.add( mIdleContexts.add( new JobServiceContext(mService, this, batteryStats, mService.mJobPackageTracker, mContext.getMainLooper())); } Loading Loading @@ -582,12 +581,10 @@ class JobConcurrencyManager { // To avoid GC churn, we recycle the arrays. JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap; boolean[] slotChanged = mRecycledSlotChanged; int[] preferredUidForContext = mRecycledPreferredUidForContext; int[] workTypeForContext = mRecycledWorkTypeForContext; String[] preemptReasonForContext = mRecycledPreemptReasonForContext; int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext; String[] shouldStopJobReason = mRecycledShouldStopJobReason; final ArraySet<ContextAssignment> changed = mRecycledChanged; final ArraySet<ContextAssignment> idle = mRecycledIdle; final ArraySet<ContextAssignment> preferredUidOnly = mRecycledPreferredUidOnly; final ArraySet<ContextAssignment> stoppable = mRecycledStoppable; updateCounterConfigLocked(); // Reset everything since we'll re-evaluate the current state. Loading @@ -598,20 +595,47 @@ class JobConcurrencyManager { // shouldStopRunningJobLocked(). updateNonRunningPrioritiesLocked(pendingJobQueue, true); for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { final JobServiceContext js = activeServices.get(i); final JobStatus status = js.getRunningJobLocked(); final int numRunningJobs = activeServices.size(); for (int i = 0; i < numRunningJobs; ++i) { final JobServiceContext jsc = activeServices.get(i); final JobStatus js = jsc.getRunningJobLocked(); ContextAssignment assignment = mContextAssignmentPool.acquire(); if (assignment == null) { assignment = new ContextAssignment(); } assignment.context = jsc; if (js != null) { mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType()); assignment.workType = jsc.getRunningJobWorkType(); } if ((contextIdToJobMap[i] = status) != null) { mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType()); workTypeForContext[i] = js.getRunningJobWorkType(); assignment.preferredUid = jsc.getPreferredUid(); if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) { stoppable.add(assignment); } else { preferredUidOnly.add(assignment); } } for (int i = numRunningJobs; i < MAX_JOB_CONTEXTS_COUNT; ++i) { final JobServiceContext jsc; final int numIdleContexts = mIdleContexts.size(); if (numIdleContexts > 0) { jsc = mIdleContexts.removeAt(numIdleContexts - 1); } else { Slog.wtf(TAG, "Had fewer than " + MAX_JOB_CONTEXTS_COUNT + " in existence"); jsc = createNewJobServiceContext(); } slotChanged[i] = false; preferredUidForContext[i] = js.getPreferredUid(); preemptReasonForContext[i] = null; preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED; shouldStopJobReason[i] = shouldStopRunningJobLocked(js); ContextAssignment assignment = mContextAssignmentPool.acquire(); if (assignment == null) { assignment = new ContextAssignment(); } assignment.context = jsc; idle.add(assignment); } if (DEBUG) { Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial")); Loading @@ -626,83 +650,97 @@ class JobConcurrencyManager { continue; } // Find an available slot for nextPending. The context should be available OR // it should have the lowest bias among all running jobs // (sharing the same Uid as nextPending) int minBiasForPreemption = Integer.MAX_VALUE; int selectedContextId = -1; int allWorkTypes = getJobWorkTypes(nextPending); int workType = mWorkCountTracker.canJobStart(allWorkTypes); boolean startingJob = false; int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; String preemptReason = null; // Find an available slot for nextPending. The context should be one of the following: // 1. Unused // 2. Its job should have used up its minimum execution guarantee so it // 3. Its job should have the lowest bias among all running jobs (sharing the same UID // as nextPending) ContextAssignment selectedContext = null; final int allWorkTypes = getJobWorkTypes(nextPending); 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++) { JobStatus job = contextIdToJobMap[j]; int preferredUid = preferredUidForContext[j]; if (job == null) { final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) || (preferredUid == JobServiceContext.NO_PREFERRED_UID); boolean startingJob = false; if (idle.size() > 0) { final int idx = idle.size() - 1; final ContextAssignment assignment = idle.valueAt(idx); final boolean preferredUidOkay = (assignment.preferredUid == nextPending.getUid()) || (assignment.preferredUid == JobServiceContext.NO_PREFERRED_UID); int workType = mWorkCountTracker.canJobStart(allWorkTypes); 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; selectedContext = assignment; startingJob = true; break; idle.removeAt(idx); assignment.newJob = nextPending; assignment.newWorkType = workType; } // No job on this context, but nextPending can't run here because // the context has a preferred Uid or we have reached the limit on // concurrent jobs. continue; } if (job.getUid() != nextPending.getUid()) { if (selectedContext == null) { for (int s = stoppable.size() - 1; s >= 0; --s) { ContextAssignment assignment = stoppable.valueAt(s); JobStatus runningJob = assignment.context.getRunningJobLocked(); // 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 (job.lastEvaluatedBias < JobInfo.BIAS_TOP_APP && reason != null && mWorkCountTracker.canJobStart(allWorkTypes, activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) { if (runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP && assignment.shouldStopJobReason != null) { int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes, assignment.context.getRunningJobWorkType()); if (replaceWorkType != 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 // preempted job finally stops. preemptReason = reason; preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE; assignment.preemptReason = assignment.shouldStopJobReason; assignment.preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE; selectedContext = assignment; stoppable.removeAt(s); assignment.newJob = nextPending; assignment.newWorkType = replaceWorkType; // Don't preserve the UID since we're stopping the job because // something is pending (eg. EJs). assignment.context.clearPreferredUid(); break; } } } } if (selectedContext == null) { int lowestBiasSeen = Integer.MAX_VALUE; for (int p = preferredUidOnly.size() - 1; p >= 0; --p) { final ContextAssignment assignment = preferredUidOnly.valueAt(p); final JobStatus runningJob = assignment.context.getRunningJobLocked(); if (runningJob.getUid() != nextPending.getUid()) { continue; } final int jobBias = mService.evaluateJobBiasLocked(job); final int jobBias = mService.evaluateJobBiasLocked(runningJob); if (jobBias >= nextPending.lastEvaluatedBias) { continue; } if (minBiasForPreemption > jobBias) { if (selectedContext == null || lowestBiasSeen > jobBias) { // Step down the preemption threshold - wind up replacing // the lowest-bias running job minBiasForPreemption = jobBias; selectedContextId = j; preemptReason = "higher bias job found"; preemptReasonCode = JobParameters.STOP_REASON_PREEMPT; lowestBiasSeen = jobBias; selectedContext = assignment; assignment.preemptReason = "higher bias job found"; assignment.preemptReasonCode = JobParameters.STOP_REASON_PREEMPT; // In this case, we're just going to preempt a low bias job, we're not // actually starting a job, so don't set startingJob. // actually starting a job, so don't set startingJob to true. } } if (selectedContext != null) { selectedContext.newJob = nextPending; preferredUidOnly.remove(selectedContext); } } final PackageStats packageStats = getPkgStatsLocked( nextPending.getSourceUserId(), nextPending.getSourcePackageName()); if (selectedContextId != -1) { contextIdToJobMap[selectedContextId] = nextPending; slotChanged[selectedContextId] = true; preemptReasonCodeForContext[selectedContextId] = preemptReasonCode; preemptReasonForContext[selectedContextId] = preemptReason; if (selectedContext != null) { changed.add(selectedContext); 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); mWorkCountTracker.stageJob(selectedContext.newWorkType, allWorkTypes); mActivePkgStats.add( nextPending.getSourceUserId(), nextPending.getSourcePackageName(), packageStats); Loading @@ -714,37 +752,52 @@ class JobConcurrencyManager { Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString()); } for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { boolean preservePreferredUid = false; if (slotChanged[i]) { JobStatus js = activeServices.get(i).getRunningJobLocked(); for (int c = changed.size() - 1; c >= 0; --c) { final ContextAssignment assignment = changed.valueAt(c); final JobStatus js = assignment.context.getRunningJobLocked(); if (js != null) { if (DEBUG) { Slog.d(TAG, "preempting job: " + activeServices.get(i).getRunningJobLocked()); Slog.d(TAG, "preempting job: " + js); } // preferredUid will be set to uid of currently running job. activeServices.get(i).cancelExecutingJobLocked( preemptReasonCodeForContext[i], JobParameters.INTERNAL_STOP_REASON_PREEMPT, preemptReasonForContext[i]); // Only preserve the UID if we're preempting for the same UID. If we're stopping // the job because something is pending (eg. EJs), then we shouldn't preserve // the UID. preservePreferredUid = preemptReasonCodeForContext[i] == JobParameters.STOP_REASON_PREEMPT; assignment.context.cancelExecutingJobLocked( assignment.preemptReasonCode, JobParameters.INTERNAL_STOP_REASON_PREEMPT, assignment.preemptReason); } else { final JobStatus pendingJob = contextIdToJobMap[i]; final JobStatus pendingJob = assignment.newJob; if (DEBUG) { Slog.d(TAG, "About to run job on context " + i + ", job: " + pendingJob); + assignment.context.getId() + ", job: " + pendingJob); } startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]); startJobLocked(assignment.context, pendingJob, assignment.newWorkType); } assignment.clear(); mContextAssignmentPool.release(assignment); } for (int s = stoppable.size() - 1; s >= 0; --s) { final ContextAssignment assignment = stoppable.valueAt(s); assignment.context.clearPreferredUid(); assignment.clear(); mContextAssignmentPool.release(assignment); } if (!preservePreferredUid) { activeServices.get(i).clearPreferredUid(); for (int p = preferredUidOnly.size() - 1; p >= 0; --p) { final ContextAssignment assignment = preferredUidOnly.valueAt(p); assignment.context.clearPreferredUid(); assignment.clear(); mContextAssignmentPool.release(assignment); } for (int i = idle.size() - 1; i >= 0; --i) { final ContextAssignment assignment = idle.valueAt(i); mIdleContexts.add(assignment.context); assignment.context.clearPreferredUid(); assignment.clear(); mContextAssignmentPool.release(assignment); } changed.clear(); idle.clear(); stoppable.clear(); preferredUidOnly.clear(); mWorkCountTracker.resetStagingCount(); mActivePkgStats.forEach(mPackageStatsStagingCountClearer); noteConcurrency(); Loading Loading @@ -787,7 +840,7 @@ class JobConcurrencyManager { @GuardedBy("mLock") private void stopLongRunningJobsLocked(@NonNull String debugReason) { for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; ++i) { for (int i = 0; i < mActiveServices.size(); ++i) { final JobServiceContext jsc = mActiveServices.get(i); final JobStatus jobStatus = jsc.getRunningJobLocked(); Loading Loading @@ -931,6 +984,8 @@ class JobConcurrencyManager { } } else { mRunningJobs.add(jobStatus); mActiveServices.add(worker); mIdleContexts.remove(worker); mWorkCountTracker.onJobStarted(workType); packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob()); mActivePkgStats.add( Loading @@ -950,6 +1005,8 @@ class JobConcurrencyManager { @WorkType final int workType) { mWorkCountTracker.onJobFinished(workType); mRunningJobs.remove(jobStatus); mActiveServices.remove(worker); mIdleContexts.add(worker); final PackageStats packageStats = mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); if (packageStats == null) { Loading Loading @@ -1093,7 +1150,7 @@ class JobConcurrencyManager { /** * Returns {@code null} if the job can continue running and a non-null String if the job should * be stopped. The non-null String details the reason for stopping the job. A job will generally * be stopped if there similar job types waiting to be run and stopping this job would allow * be stopped if there are similar job types waiting to be run and stopping this job would allow * another job to run, or if system state suggests the job should stop. */ @Nullable Loading Loading @@ -1202,6 +1259,14 @@ class JobConcurrencyManager { return foundSome; } @NonNull private JobServiceContext createNewJobServiceContext() { return new JobServiceContext(mService, this, IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)), mService.mJobPackageTracker, mContext.getMainLooper()); } @GuardedBy("mLock") private String printPendingQueueLocked() { StringBuilder s = new StringBuilder("Pending queue: "); Loading Loading @@ -1327,10 +1392,13 @@ class JobConcurrencyManager { } @GuardedBy("mLock") void dumpActiveJobsLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate, void dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed, long nowUptime) { pw.println("Active jobs:"); pw.increaseIndent(); if (mActiveServices.size() == 0) { pw.println("N/A"); } for (int i = 0; i < mActiveServices.size(); i++) { JobServiceContext jsc = mActiveServices.get(i); final JobStatus job = jsc.getRunningJobLocked(); Loading @@ -1339,7 +1407,8 @@ class JobConcurrencyManager { continue; } pw.print("Slot #"); pw.print(i); pw.print(": "); pw.print("Slot #"); pw.print(i); pw.print("(ID="); pw.print(jsc.getId()); pw.print("): "); jsc.dumpLocked(pw, nowElapsed); if (job != null) { Loading @@ -1361,6 +1430,19 @@ class JobConcurrencyManager { } } pw.decreaseIndent(); pw.println(); pw.print("Idle contexts ("); pw.print(mIdleContexts.size()); pw.println("):"); pw.increaseIndent(); for (int i = 0; i < mIdleContexts.size(); i++) { JobServiceContext jsc = mIdleContexts.valueAt(i); pw.print("ID="); pw.print(jsc.getId()); pw.print(": "); jsc.dumpLocked(pw, nowElapsed); } pw.decreaseIndent(); } public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) { Loading Loading @@ -1989,4 +2071,26 @@ class JobConcurrencyManager { pw.println("}"); } } private static final class ContextAssignment { public JobServiceContext context; public int preferredUid = JobServiceContext.NO_PREFERRED_UID; public int workType = WORK_TYPE_NONE; public String preemptReason; public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; public String shouldStopJobReason; public JobStatus newJob; public int newWorkType = WORK_TYPE_NONE; void clear() { context = null; preferredUid = JobServiceContext.NO_PREFERRED_UID; workType = WORK_TYPE_NONE; preemptReason = null; preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; shouldStopJobReason = null; newJob = null; newWorkType = WORK_TYPE_NONE; } } } apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +1 −1 Original line number Diff line number Diff line Loading @@ -3770,7 +3770,7 @@ public class JobSchedulerService extends com.android.server.SystemService pw.decreaseIndent(); pw.println(); mConcurrencyManager.dumpActiveJobsLocked(pw, predicate, nowElapsed, nowUptime); mConcurrencyManager.dumpContextInfoLocked(pw, predicate, nowElapsed, nowUptime); pw.println(); boolean recentPrinted = false; Loading apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +4 −0 Original line number Diff line number Diff line Loading @@ -433,6 +433,10 @@ public final class JobServiceContext implements ServiceConnection { mPreferredUid = NO_PREFERRED_UID; } int getId() { return hashCode(); } long getExecutionStartTimeElapsed() { return mExecutionStartTimeElapsed; } Loading Loading
apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +228 −124 Original line number Diff line number Diff line Loading @@ -273,23 +273,22 @@ class JobConcurrencyManager { */ JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT]; boolean[] mRecycledSlotChanged = new boolean[MAX_JOB_CONTEXTS_COUNT]; private final ArraySet<ContextAssignment> mRecycledChanged = new ArraySet<>(); private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>(); private final ArraySet<ContextAssignment> mRecycledPreferredUidOnly = new ArraySet<>(); private final ArraySet<ContextAssignment> mRecycledStoppable = new ArraySet<>(); int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT]; String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT]; int[] mRecycledPreemptReasonCodeForContext = new int[MAX_JOB_CONTEXTS_COUNT]; String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT]; private final Pools.Pool<ContextAssignment> mContextAssignmentPool = new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT); /** * Set of JobServiceContexts that we use to run jobs. * Set of JobServiceContexts that are actively running jobs. */ final List<JobServiceContext> mActiveServices = new ArrayList<>(); /** Set of JobServiceContexts that aren't currently running any jobs. */ final ArraySet<JobServiceContext> mIdleContexts = new ArraySet<>(); private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>(); private final WorkCountTracker mWorkCountTracker = new WorkCountTracker(); Loading Loading @@ -378,7 +377,7 @@ class JobConcurrencyManager { final IBatteryStats batteryStats = IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)); for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { mActiveServices.add( mIdleContexts.add( new JobServiceContext(mService, this, batteryStats, mService.mJobPackageTracker, mContext.getMainLooper())); } Loading Loading @@ -582,12 +581,10 @@ class JobConcurrencyManager { // To avoid GC churn, we recycle the arrays. JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap; boolean[] slotChanged = mRecycledSlotChanged; int[] preferredUidForContext = mRecycledPreferredUidForContext; int[] workTypeForContext = mRecycledWorkTypeForContext; String[] preemptReasonForContext = mRecycledPreemptReasonForContext; int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext; String[] shouldStopJobReason = mRecycledShouldStopJobReason; final ArraySet<ContextAssignment> changed = mRecycledChanged; final ArraySet<ContextAssignment> idle = mRecycledIdle; final ArraySet<ContextAssignment> preferredUidOnly = mRecycledPreferredUidOnly; final ArraySet<ContextAssignment> stoppable = mRecycledStoppable; updateCounterConfigLocked(); // Reset everything since we'll re-evaluate the current state. Loading @@ -598,20 +595,47 @@ class JobConcurrencyManager { // shouldStopRunningJobLocked(). updateNonRunningPrioritiesLocked(pendingJobQueue, true); for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { final JobServiceContext js = activeServices.get(i); final JobStatus status = js.getRunningJobLocked(); final int numRunningJobs = activeServices.size(); for (int i = 0; i < numRunningJobs; ++i) { final JobServiceContext jsc = activeServices.get(i); final JobStatus js = jsc.getRunningJobLocked(); ContextAssignment assignment = mContextAssignmentPool.acquire(); if (assignment == null) { assignment = new ContextAssignment(); } assignment.context = jsc; if (js != null) { mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType()); assignment.workType = jsc.getRunningJobWorkType(); } if ((contextIdToJobMap[i] = status) != null) { mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType()); workTypeForContext[i] = js.getRunningJobWorkType(); assignment.preferredUid = jsc.getPreferredUid(); if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) { stoppable.add(assignment); } else { preferredUidOnly.add(assignment); } } for (int i = numRunningJobs; i < MAX_JOB_CONTEXTS_COUNT; ++i) { final JobServiceContext jsc; final int numIdleContexts = mIdleContexts.size(); if (numIdleContexts > 0) { jsc = mIdleContexts.removeAt(numIdleContexts - 1); } else { Slog.wtf(TAG, "Had fewer than " + MAX_JOB_CONTEXTS_COUNT + " in existence"); jsc = createNewJobServiceContext(); } slotChanged[i] = false; preferredUidForContext[i] = js.getPreferredUid(); preemptReasonForContext[i] = null; preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED; shouldStopJobReason[i] = shouldStopRunningJobLocked(js); ContextAssignment assignment = mContextAssignmentPool.acquire(); if (assignment == null) { assignment = new ContextAssignment(); } assignment.context = jsc; idle.add(assignment); } if (DEBUG) { Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial")); Loading @@ -626,83 +650,97 @@ class JobConcurrencyManager { continue; } // Find an available slot for nextPending. The context should be available OR // it should have the lowest bias among all running jobs // (sharing the same Uid as nextPending) int minBiasForPreemption = Integer.MAX_VALUE; int selectedContextId = -1; int allWorkTypes = getJobWorkTypes(nextPending); int workType = mWorkCountTracker.canJobStart(allWorkTypes); boolean startingJob = false; int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; String preemptReason = null; // Find an available slot for nextPending. The context should be one of the following: // 1. Unused // 2. Its job should have used up its minimum execution guarantee so it // 3. Its job should have the lowest bias among all running jobs (sharing the same UID // as nextPending) ContextAssignment selectedContext = null; final int allWorkTypes = getJobWorkTypes(nextPending); 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++) { JobStatus job = contextIdToJobMap[j]; int preferredUid = preferredUidForContext[j]; if (job == null) { final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) || (preferredUid == JobServiceContext.NO_PREFERRED_UID); boolean startingJob = false; if (idle.size() > 0) { final int idx = idle.size() - 1; final ContextAssignment assignment = idle.valueAt(idx); final boolean preferredUidOkay = (assignment.preferredUid == nextPending.getUid()) || (assignment.preferredUid == JobServiceContext.NO_PREFERRED_UID); int workType = mWorkCountTracker.canJobStart(allWorkTypes); 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; selectedContext = assignment; startingJob = true; break; idle.removeAt(idx); assignment.newJob = nextPending; assignment.newWorkType = workType; } // No job on this context, but nextPending can't run here because // the context has a preferred Uid or we have reached the limit on // concurrent jobs. continue; } if (job.getUid() != nextPending.getUid()) { if (selectedContext == null) { for (int s = stoppable.size() - 1; s >= 0; --s) { ContextAssignment assignment = stoppable.valueAt(s); JobStatus runningJob = assignment.context.getRunningJobLocked(); // 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 (job.lastEvaluatedBias < JobInfo.BIAS_TOP_APP && reason != null && mWorkCountTracker.canJobStart(allWorkTypes, activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) { if (runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP && assignment.shouldStopJobReason != null) { int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes, assignment.context.getRunningJobWorkType()); if (replaceWorkType != 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 // preempted job finally stops. preemptReason = reason; preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE; assignment.preemptReason = assignment.shouldStopJobReason; assignment.preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE; selectedContext = assignment; stoppable.removeAt(s); assignment.newJob = nextPending; assignment.newWorkType = replaceWorkType; // Don't preserve the UID since we're stopping the job because // something is pending (eg. EJs). assignment.context.clearPreferredUid(); break; } } } } if (selectedContext == null) { int lowestBiasSeen = Integer.MAX_VALUE; for (int p = preferredUidOnly.size() - 1; p >= 0; --p) { final ContextAssignment assignment = preferredUidOnly.valueAt(p); final JobStatus runningJob = assignment.context.getRunningJobLocked(); if (runningJob.getUid() != nextPending.getUid()) { continue; } final int jobBias = mService.evaluateJobBiasLocked(job); final int jobBias = mService.evaluateJobBiasLocked(runningJob); if (jobBias >= nextPending.lastEvaluatedBias) { continue; } if (minBiasForPreemption > jobBias) { if (selectedContext == null || lowestBiasSeen > jobBias) { // Step down the preemption threshold - wind up replacing // the lowest-bias running job minBiasForPreemption = jobBias; selectedContextId = j; preemptReason = "higher bias job found"; preemptReasonCode = JobParameters.STOP_REASON_PREEMPT; lowestBiasSeen = jobBias; selectedContext = assignment; assignment.preemptReason = "higher bias job found"; assignment.preemptReasonCode = JobParameters.STOP_REASON_PREEMPT; // In this case, we're just going to preempt a low bias job, we're not // actually starting a job, so don't set startingJob. // actually starting a job, so don't set startingJob to true. } } if (selectedContext != null) { selectedContext.newJob = nextPending; preferredUidOnly.remove(selectedContext); } } final PackageStats packageStats = getPkgStatsLocked( nextPending.getSourceUserId(), nextPending.getSourcePackageName()); if (selectedContextId != -1) { contextIdToJobMap[selectedContextId] = nextPending; slotChanged[selectedContextId] = true; preemptReasonCodeForContext[selectedContextId] = preemptReasonCode; preemptReasonForContext[selectedContextId] = preemptReason; if (selectedContext != null) { changed.add(selectedContext); 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); mWorkCountTracker.stageJob(selectedContext.newWorkType, allWorkTypes); mActivePkgStats.add( nextPending.getSourceUserId(), nextPending.getSourcePackageName(), packageStats); Loading @@ -714,37 +752,52 @@ class JobConcurrencyManager { Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString()); } for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { boolean preservePreferredUid = false; if (slotChanged[i]) { JobStatus js = activeServices.get(i).getRunningJobLocked(); for (int c = changed.size() - 1; c >= 0; --c) { final ContextAssignment assignment = changed.valueAt(c); final JobStatus js = assignment.context.getRunningJobLocked(); if (js != null) { if (DEBUG) { Slog.d(TAG, "preempting job: " + activeServices.get(i).getRunningJobLocked()); Slog.d(TAG, "preempting job: " + js); } // preferredUid will be set to uid of currently running job. activeServices.get(i).cancelExecutingJobLocked( preemptReasonCodeForContext[i], JobParameters.INTERNAL_STOP_REASON_PREEMPT, preemptReasonForContext[i]); // Only preserve the UID if we're preempting for the same UID. If we're stopping // the job because something is pending (eg. EJs), then we shouldn't preserve // the UID. preservePreferredUid = preemptReasonCodeForContext[i] == JobParameters.STOP_REASON_PREEMPT; assignment.context.cancelExecutingJobLocked( assignment.preemptReasonCode, JobParameters.INTERNAL_STOP_REASON_PREEMPT, assignment.preemptReason); } else { final JobStatus pendingJob = contextIdToJobMap[i]; final JobStatus pendingJob = assignment.newJob; if (DEBUG) { Slog.d(TAG, "About to run job on context " + i + ", job: " + pendingJob); + assignment.context.getId() + ", job: " + pendingJob); } startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]); startJobLocked(assignment.context, pendingJob, assignment.newWorkType); } assignment.clear(); mContextAssignmentPool.release(assignment); } for (int s = stoppable.size() - 1; s >= 0; --s) { final ContextAssignment assignment = stoppable.valueAt(s); assignment.context.clearPreferredUid(); assignment.clear(); mContextAssignmentPool.release(assignment); } if (!preservePreferredUid) { activeServices.get(i).clearPreferredUid(); for (int p = preferredUidOnly.size() - 1; p >= 0; --p) { final ContextAssignment assignment = preferredUidOnly.valueAt(p); assignment.context.clearPreferredUid(); assignment.clear(); mContextAssignmentPool.release(assignment); } for (int i = idle.size() - 1; i >= 0; --i) { final ContextAssignment assignment = idle.valueAt(i); mIdleContexts.add(assignment.context); assignment.context.clearPreferredUid(); assignment.clear(); mContextAssignmentPool.release(assignment); } changed.clear(); idle.clear(); stoppable.clear(); preferredUidOnly.clear(); mWorkCountTracker.resetStagingCount(); mActivePkgStats.forEach(mPackageStatsStagingCountClearer); noteConcurrency(); Loading Loading @@ -787,7 +840,7 @@ class JobConcurrencyManager { @GuardedBy("mLock") private void stopLongRunningJobsLocked(@NonNull String debugReason) { for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; ++i) { for (int i = 0; i < mActiveServices.size(); ++i) { final JobServiceContext jsc = mActiveServices.get(i); final JobStatus jobStatus = jsc.getRunningJobLocked(); Loading Loading @@ -931,6 +984,8 @@ class JobConcurrencyManager { } } else { mRunningJobs.add(jobStatus); mActiveServices.add(worker); mIdleContexts.remove(worker); mWorkCountTracker.onJobStarted(workType); packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob()); mActivePkgStats.add( Loading @@ -950,6 +1005,8 @@ class JobConcurrencyManager { @WorkType final int workType) { mWorkCountTracker.onJobFinished(workType); mRunningJobs.remove(jobStatus); mActiveServices.remove(worker); mIdleContexts.add(worker); final PackageStats packageStats = mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); if (packageStats == null) { Loading Loading @@ -1093,7 +1150,7 @@ class JobConcurrencyManager { /** * Returns {@code null} if the job can continue running and a non-null String if the job should * be stopped. The non-null String details the reason for stopping the job. A job will generally * be stopped if there similar job types waiting to be run and stopping this job would allow * be stopped if there are similar job types waiting to be run and stopping this job would allow * another job to run, or if system state suggests the job should stop. */ @Nullable Loading Loading @@ -1202,6 +1259,14 @@ class JobConcurrencyManager { return foundSome; } @NonNull private JobServiceContext createNewJobServiceContext() { return new JobServiceContext(mService, this, IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)), mService.mJobPackageTracker, mContext.getMainLooper()); } @GuardedBy("mLock") private String printPendingQueueLocked() { StringBuilder s = new StringBuilder("Pending queue: "); Loading Loading @@ -1327,10 +1392,13 @@ class JobConcurrencyManager { } @GuardedBy("mLock") void dumpActiveJobsLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate, void dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed, long nowUptime) { pw.println("Active jobs:"); pw.increaseIndent(); if (mActiveServices.size() == 0) { pw.println("N/A"); } for (int i = 0; i < mActiveServices.size(); i++) { JobServiceContext jsc = mActiveServices.get(i); final JobStatus job = jsc.getRunningJobLocked(); Loading @@ -1339,7 +1407,8 @@ class JobConcurrencyManager { continue; } pw.print("Slot #"); pw.print(i); pw.print(": "); pw.print("Slot #"); pw.print(i); pw.print("(ID="); pw.print(jsc.getId()); pw.print("): "); jsc.dumpLocked(pw, nowElapsed); if (job != null) { Loading @@ -1361,6 +1430,19 @@ class JobConcurrencyManager { } } pw.decreaseIndent(); pw.println(); pw.print("Idle contexts ("); pw.print(mIdleContexts.size()); pw.println("):"); pw.increaseIndent(); for (int i = 0; i < mIdleContexts.size(); i++) { JobServiceContext jsc = mIdleContexts.valueAt(i); pw.print("ID="); pw.print(jsc.getId()); pw.print(": "); jsc.dumpLocked(pw, nowElapsed); } pw.decreaseIndent(); } public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) { Loading Loading @@ -1989,4 +2071,26 @@ class JobConcurrencyManager { pw.println("}"); } } private static final class ContextAssignment { public JobServiceContext context; public int preferredUid = JobServiceContext.NO_PREFERRED_UID; public int workType = WORK_TYPE_NONE; public String preemptReason; public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; public String shouldStopJobReason; public JobStatus newJob; public int newWorkType = WORK_TYPE_NONE; void clear() { context = null; preferredUid = JobServiceContext.NO_PREFERRED_UID; workType = WORK_TYPE_NONE; preemptReason = null; preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; shouldStopJobReason = null; newJob = null; newWorkType = WORK_TYPE_NONE; } } }
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +1 −1 Original line number Diff line number Diff line Loading @@ -3770,7 +3770,7 @@ public class JobSchedulerService extends com.android.server.SystemService pw.decreaseIndent(); pw.println(); mConcurrencyManager.dumpActiveJobsLocked(pw, predicate, nowElapsed, nowUptime); mConcurrencyManager.dumpContextInfoLocked(pw, predicate, nowElapsed, nowUptime); pw.println(); boolean recentPrinted = false; Loading
apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +4 −0 Original line number Diff line number Diff line Loading @@ -433,6 +433,10 @@ public final class JobServiceContext implements ServiceConnection { mPreferredUid = NO_PREFERRED_UID; } int getId() { return hashCode(); } long getExecutionStartTimeElapsed() { return mExecutionStartTimeElapsed; } Loading