From 5ba3a0720287af10ed9f329973802dd24b36c124 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Mon, 28 Mar 2022 21:28:47 +0000 Subject: [PATCH] Sort ContextAssignments to stop longer running jobs first. Bug: 141645789 Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job frameworks/base/services/tests/mockingservicestests/src/com/android/server/job Change-Id: Ia067935fdd204a7c6f9227522626e28d474c1e92 --- .../server/job/JobConcurrencyManager.java | 67 +++++++++++++++---- 1 file changed, 53 insertions(+), 14 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 69189d51614d1..49ce22224aa5b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -68,6 +68,8 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; @@ -267,10 +269,44 @@ class JobConcurrencyManager { ) ); + /** + * Comparator to sort the determination lists, putting the ContextAssignments that we most + * prefer to use at the end of the list. + */ + private static final Comparator sDeterminationComparator = (ca1, ca2) -> { + if (ca1 == ca2) { + return 0; + } + final JobStatus js1 = ca1.context.getRunningJobLocked(); + final JobStatus js2 = ca2.context.getRunningJobLocked(); + // Prefer using an empty context over one with a running job. + if (js1 == null) { + if (js2 == null) { + return 0; + } + return 1; + } else if (js2 == null) { + return -1; + } + // We would prefer to replace bg jobs over TOP jobs. + if (js1.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { + if (js2.lastEvaluatedBias != JobInfo.BIAS_TOP_APP) { + return -1; + } + } else if (js2.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { + return 1; + } + // Prefer replacing the job that has been running the longest. + return Long.compare( + ca2.context.getExecutionStartTimeElapsed(), + ca1.context.getExecutionStartTimeElapsed()); + }; + + // We reuse the lists to avoid GC churn. private final ArraySet mRecycledChanged = new ArraySet<>(); private final ArraySet mRecycledIdle = new ArraySet<>(); - private final ArraySet mRecycledPreferredUidOnly = new ArraySet<>(); - private final ArraySet mRecycledStoppable = new ArraySet<>(); + private final ArrayList mRecycledPreferredUidOnly = new ArrayList<>(); + private final ArrayList mRecycledStoppable = new ArrayList<>(); private final Pools.Pool mContextAssignmentPool = new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT); @@ -576,8 +612,8 @@ class JobConcurrencyManager { // To avoid GC churn, we recycle the arrays. final ArraySet changed = mRecycledChanged; final ArraySet idle = mRecycledIdle; - final ArraySet preferredUidOnly = mRecycledPreferredUidOnly; - final ArraySet stoppable = mRecycledStoppable; + final ArrayList preferredUidOnly = mRecycledPreferredUidOnly; + final ArrayList stoppable = mRecycledStoppable; updateCounterConfigLocked(); // Reset everything since we'll re-evaluate the current state. @@ -612,6 +648,8 @@ class JobConcurrencyManager { preferredUidOnly.add(assignment); } } + preferredUidOnly.sort(sDeterminationComparator); + stoppable.sort(sDeterminationComparator); for (int i = numRunningJobs; i < MAX_JOB_CONTEXTS_COUNT; ++i) { final JobServiceContext jsc; final int numIdleContexts = mIdleContexts.size(); @@ -670,8 +708,8 @@ class JobConcurrencyManager { } if (selectedContext == null) { for (int s = stoppable.size() - 1; s >= 0; --s) { - ContextAssignment assignment = stoppable.valueAt(s); - JobStatus runningJob = assignment.context.getRunningJobLocked(); + final ContextAssignment assignment = stoppable.get(s); + final 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. if (runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP @@ -685,7 +723,7 @@ class JobConcurrencyManager { assignment.preemptReason = assignment.shouldStopJobReason; assignment.preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE; selectedContext = assignment; - stoppable.removeAt(s); + stoppable.remove(s); assignment.newJob = nextPending; assignment.newWorkType = replaceWorkType; break; @@ -696,7 +734,7 @@ class JobConcurrencyManager { if (selectedContext == null) { int lowestBiasSeen = Integer.MAX_VALUE; for (int p = preferredUidOnly.size() - 1; p >= 0; --p) { - final ContextAssignment assignment = preferredUidOnly.valueAt(p); + final ContextAssignment assignment = preferredUidOnly.get(p); final JobStatus runningJob = assignment.context.getRunningJobLocked(); if (runningJob.getUid() != nextPending.getUid()) { continue; @@ -767,7 +805,7 @@ class JobConcurrencyManager { mContextAssignmentPool.release(assignment); } for (int s = stoppable.size() - 1; s >= 0; --s) { - final ContextAssignment assignment = stoppable.valueAt(s); + final ContextAssignment assignment = stoppable.get(s); // The preferred UID is set when we cancel with PREEMPT reason, but don't preserve the // UID for any stoppable contexts since we want to open the context up to any/all apps. assignment.context.clearPreferredUid(); @@ -775,7 +813,7 @@ class JobConcurrencyManager { mContextAssignmentPool.release(assignment); } for (int p = preferredUidOnly.size() - 1; p >= 0; --p) { - final ContextAssignment assignment = preferredUidOnly.valueAt(p); + final ContextAssignment assignment = preferredUidOnly.get(p); assignment.clear(); mContextAssignmentPool.release(assignment); } @@ -1275,12 +1313,12 @@ class JobConcurrencyManager { return s.toString(); } - private static String printAssignments(String header, ArraySet... list) { + private static String printAssignments(String header, Collection... list) { final StringBuilder s = new StringBuilder(header + ": "); for (int l = 0; l < list.length; ++l) { - ArraySet assignments = list[l]; - for (int c = 0; c < assignments.size(); ++c) { - final ContextAssignment assignment = assignments.valueAt(c); + final Collection assignments = list[l]; + int c = 0; + for (final ContextAssignment assignment : assignments) { final JobStatus job = assignment.newJob == null ? assignment.context.getRunningJobLocked() : assignment.newJob; @@ -1294,6 +1332,7 @@ class JobConcurrencyManager { s.append(job.getJobId()).append("/").append(job.getUid()); } s.append(")"); + c++; } } return s.toString();