Merge "Give user-initiated jobs immediacy privilege for TOP app."

This commit is contained in:
TreeHugger Robot
2022-12-14 17:45:43 +00:00
committed by Android (Google) Code Review
3 changed files with 81 additions and 33 deletions

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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
@@ -499,6 +498,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);