diff options
3 files changed, 81 insertions, 33 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 30986dde6b91..c90445e8877e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -761,8 +761,8 @@ class JobConcurrencyManager { if (js != null) { mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType()); assignment.workType = jsc.getRunningJobWorkType(); - if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { - info.numRunningTopEj++; + if (js.startedWithImmediacyPrivilege) { + info.numRunningImmediacyPrivileged++; } } @@ -829,11 +829,9 @@ class JobConcurrencyManager { continue; } - final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob() - && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP; + final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending); if (DEBUG && isSimilarJobRunningLocked(nextPending)) { - Slog.w(TAG, "Already running similar " + (isTopEj ? "TOP-EJ" : "job") - + " to: " + nextPending); + Slog.w(TAG, "Already running similar job to: " + nextPending); } // Factoring minChangedWaitingTimeMs into the min waiting time effectively limits @@ -876,23 +874,25 @@ class JobConcurrencyManager { final JobStatus runningJob = assignment.context.getRunningJobLocked(); // Maybe stop the job if it has had its day in the sun. Only allow replacing // for one of the following conditions: - // 1. We're putting in the current TOP app's EJ + // 1. We're putting in a job that has the privilege of running immediately // 2. There aren't too many jobs running AND the current job started when the // app was in the background // 3. There aren't too many jobs running AND the current job started when the // app was on TOP, but the app has since left TOP // 4. There aren't too many jobs running AND the current job started when the - // app was on TOP, the app is still TOP, but there are too many TOP+EJs + // app was on TOP, the app is still TOP, but there are too many + // immediacy-privileged jobs // running (because we don't want them to starve out other apps and the // current job has already run for the minimum guaranteed time). // 5. This new job could be waiting for too long for a slot to open up - boolean canReplace = isTopEj; // Case 1 + boolean canReplace = hasImmediacyPrivilege; // Case 1 if (!canReplace && !isInOverage) { final int currentJobBias = mService.evaluateJobBiasLocked(runningJob); canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3 // Case 4 - || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal(); + || info.numRunningImmediacyPrivileged + > (mWorkTypeConfig.getMaxTotal() / 2); } if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5 if (nextPending.shouldTreatAsExpeditedJob()) { @@ -919,7 +919,7 @@ class JobConcurrencyManager { } } } - if (selectedContext == null && (!isInOverage || isTopEj)) { + if (selectedContext == null && (!isInOverage || hasImmediacyPrivilege)) { int lowestBiasSeen = Integer.MAX_VALUE; long newMinPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE; for (int p = preferredUidOnly.size() - 1; p >= 0; --p) { @@ -962,12 +962,13 @@ class JobConcurrencyManager { info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs; } } - // Make sure to run EJs for the TOP app immediately. - if (isTopEj) { + // Make sure to run jobs with special privilege immediately. + if (hasImmediacyPrivilege) { if (selectedContext != null && selectedContext.context.getRunningJobLocked() != null) { - // We're "replacing" a currently running job, but we want TOP EJs to start - // immediately, so we'll start the EJ on a fresh available context and + // We're "replacing" a currently running job, but we want immediacy-privileged + // jobs to start immediately, so we'll start the privileged jobs on a fresh + // available context and // stop this currently running job to replace in two steps. changed.add(selectedContext); projectedRunningCount--; @@ -1029,6 +1030,7 @@ class JobConcurrencyManager { projectedRunningCount--; } if (selectedContext.newJob != null) { + selectedContext.newJob.startedWithImmediacyPrivilege = hasImmediacyPrivilege; projectedRunningCount++; minChangedWaitingTimeMs = Math.min(minChangedWaitingTimeMs, mService.getMinJobExecutionGuaranteeMs(selectedContext.newJob)); @@ -1103,6 +1105,18 @@ class JobConcurrencyManager { mActivePkgStats.forEach(mPackageStatsStagingCountClearer); } + @VisibleForTesting + @GuardedBy("mLock") + boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job) { + // EJs & user-initiated jobs for the TOP app should run immediately. + // However, even for user-initiated jobs, if the app has not recently been in TOP or BAL + // state, we don't give the immediacy privilege so that we can try and maintain + // reasonably concurrency behavior. + return job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP + // TODO(): include BAL state for user-initiated jobs + && (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiated()); + } + @GuardedBy("mLock") void onUidBiasChangedLocked(int prevBias, int newBias) { if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) { @@ -1361,7 +1375,7 @@ class JobConcurrencyManager { mActiveServices.remove(worker); if (mIdleContexts.size() < MAX_RETAINED_OBJECTS) { // Don't need to save all new contexts, but keep some extra around in case we need - // extras for another TOP+EJ overage. + // extras for another immediacy privileged overage. mIdleContexts.add(worker); } else { mNumDroppedContexts++; @@ -1403,7 +1417,8 @@ class JobConcurrencyManager { } if (respectConcurrencyLimit) { worker.clearPreferredUid(); - // We're over the limit (because the TOP app scheduled a lot of EJs), but we should + // We're over the limit (because there were a lot of immediacy-privileged jobs + // scheduled), but we should // be able to stop the other jobs soon so don't start running anything new until we // get back below the limit. noteConcurrency(); @@ -1627,17 +1642,17 @@ class JobConcurrencyManager { } } else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) { return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue"; - } else if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { - // Try not to let TOP + EJ starve out other apps. - int topEjCount = 0; + } else if (js.startedWithImmediacyPrivilege) { + // Try not to let jobs with immediacy privilege starve out other apps. + int immediacyPrivilegeCount = 0; for (int r = mRunningJobs.size() - 1; r >= 0; --r) { JobStatus j = mRunningJobs.valueAt(r); - if (j.startedAsExpeditedJob && j.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { - topEjCount++; + if (j.startedWithImmediacyPrivilege) { + immediacyPrivilegeCount++; } } - if (topEjCount > .5 * mWorkTypeConfig.getMaxTotal()) { - return "prevent top EJ dominance"; + if (immediacyPrivilegeCount > mWorkTypeConfig.getMaxTotal() / 2) { + return "prevent immediacy privilege dominance"; } } // No other pending EJs. Return null so we don't let regular jobs preempt an EJ. @@ -2566,11 +2581,11 @@ class JobConcurrencyManager { @VisibleForTesting static final class AssignmentInfo { public long minPreferredUidOnlyWaitingTimeMs; - public int numRunningTopEj; + public int numRunningImmediacyPrivileged; void clear() { minPreferredUidOnlyWaitingTimeMs = 0; - numRunningTopEj = 0; + numRunningImmediacyPrivileged = 0; } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 419127e6c6a9..2e67a81f89ea 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -388,6 +388,8 @@ public final class JobStatus { */ public boolean startedAsExpeditedJob = false; + public boolean startedWithImmediacyPrivilege = false; + // If non-null, this is work that has been enqueued for the job. public ArrayList<JobWorkItem> pendingWork; diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java index a9dc4af07bab..480a4f358bc0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java @@ -217,7 +217,7 @@ public final class JobConcurrencyManagerTest { assertEquals(0, preferredUidOnly.size()); assertEquals(0, stoppable.size()); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); - assertEquals(0, assignmentInfo.numRunningTopEj); + assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged); } @Test @@ -239,7 +239,7 @@ public final class JobConcurrencyManagerTest { assertEquals(0, preferredUidOnly.size()); assertEquals(0, stoppable.size()); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); - assertEquals(0, assignmentInfo.numRunningTopEj); + assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged); } @Test @@ -265,15 +265,14 @@ public final class JobConcurrencyManagerTest { assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); assertEquals(0, stoppable.size()); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); - assertEquals(0, assignmentInfo.numRunningTopEj); + assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged); } @Test - public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() { + public void testPrepareForAssignmentDetermination_onlyStartedWithImmediacyPrivilege() { for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) { JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i); - job.startedAsExpeditedJob = true; - job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP; + job.startedWithImmediacyPrivilege = true; mJobConcurrencyManager.addRunningJobForTesting(job); } @@ -294,7 +293,7 @@ public final class JobConcurrencyManagerTest { assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size()); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, - assignmentInfo.numRunningTopEj); + assignmentInfo.numRunningImmediacyPrivileged); } @Test @@ -500,6 +499,38 @@ public final class JobConcurrencyManagerTest { } @Test + public void testHasImmediacyPrivilege() { + JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0); + spyOn(job); + assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job)); + + doReturn(false).when(job).shouldTreatAsExpeditedJob(); + doReturn(false).when(job).shouldTreatAsUserInitiated(); + job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP; + assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job)); + + doReturn(true).when(job).shouldTreatAsExpeditedJob(); + doReturn(false).when(job).shouldTreatAsUserInitiated(); + job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT; + assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job)); + + doReturn(false).when(job).shouldTreatAsExpeditedJob(); + doReturn(true).when(job).shouldTreatAsUserInitiated(); + job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT; + assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job)); + + doReturn(false).when(job).shouldTreatAsExpeditedJob(); + doReturn(true).when(job).shouldTreatAsUserInitiated(); + job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP; + assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job)); + + doReturn(true).when(job).shouldTreatAsExpeditedJob(); + doReturn(false).when(job).shouldTreatAsUserInitiated(); + job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP; + assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job)); + } + + @Test public void testIsPkgConcurrencyLimited_top() { final JobStatus topJob = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0); topJob.lastEvaluatedBias = JobInfo.BIAS_TOP_APP; |