Loading apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java +52 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.util.Pools; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.server.job.controllers.JobStatus; import java.util.ArrayList; Loading Loading @@ -66,6 +67,21 @@ class PendingJobQueue { private int mSize = 0; /** * Whether to batch iteration so that we pull several of an app's jobs from the queue at the * same time (resulting in some out of order pulls) instead of pulling purely based on the * sort order. Batching it this way will mean we try to run several jobs of the same app at the * same, resulting in fewer process restarts, and can allow the iteration runtime to amortize * to O(A*J) instead of O(A*J*log(A)), where A = # apps and J = average # jobs per app. */ private boolean mOptimizeIteration = true; /** * Number of jobs that have been pulled from the queue in succession. Used when * {@link #mOptimizeIteration} is true to know when to switch to the next AppJobQueue. */ private int mPullCount = 0; private boolean mNeedToResetIterators = false; void add(@NonNull JobStatus job) { Loading Loading @@ -139,16 +155,44 @@ class PendingJobQueue { mOrderedQueues.offer(ajq); } mNeedToResetIterators = false; // Reset the pull count when the front of the queue changes. mPullCount = 0; } else if (mOrderedQueues.size() == 0) { // Something significant changed, so the priority queue was cleared. Lazily regenerate // the queue. for (int i = mCurrentQueues.size() - 1; i >= 0; --i) { final AppJobQueue ajq = mCurrentQueues.valueAt(i); mOrderedQueues.offer(ajq); } // Reset the pull count when the front of the queue changes. mPullCount = 0; } final int numQueues = mOrderedQueues.size(); if (numQueues == 0) { return null; } final AppJobQueue earliestQueue = mOrderedQueues.poll(); // Increase the pull limit at a slightly faster rate than log(A) increases (until A>=33). // The pull limit increase is intended to balance fairness (one app can't starve out others) // with efficiency (reducing process restarts). // 1-4 apps --> pullLimit = 1, 5-8 apps --> pullLimit = 2, 9+ apps --> pullLimit = 3 final int pullLimit = mOptimizeIteration ? Math.min(3, ((numQueues - 1) >>> 2) + 1) : 1; final AppJobQueue earliestQueue = mOrderedQueues.peek(); if (earliestQueue != null) { JobStatus job = earliestQueue.next(); final JobStatus job = earliestQueue.next(); // Change the front of the queue if we've pulled pullLimit jobs from the current head // or the current head has no more jobs to provide. if (++mPullCount >= pullLimit || earliestQueue.peekNextTimestamp() == AppJobQueue.NO_NEXT_TIMESTAMP) { mOrderedQueues.poll(); if (earliestQueue.peekNextTimestamp() != AppJobQueue.NO_NEXT_TIMESTAMP) { // No need to put back in the queue if it has no more jobs to give. mOrderedQueues.offer(earliestQueue); } // Reset the pull count when the front of the queue changes. mPullCount = 0; } return job; } return null; Loading Loading @@ -186,6 +230,11 @@ class PendingJobQueue { mNeedToResetIterators = true; } @VisibleForTesting void setOptimizeIteration(boolean optimize) { mOptimizeIteration = optimize; } int size() { return mSize; } Loading services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java +20 −4 Original line number Diff line number Diff line Loading @@ -211,13 +211,26 @@ public class PendingJobQueueTest { jobQueue.add(eC11); checkPendingJobInvariants(jobQueue); final JobStatus[] expectedOrder = new JobStatus[]{ eA7, rA1, eB6, rB2, eC3, rD4, eE5, eF9, rH8, rF8, eC11, rC10, rG12, rG13, eE14}; int idx = 0; JobStatus job; final JobStatus[] expectedPureOrder = new JobStatus[]{ eC3, rD4, eE5, eB6, rB2, eA7, rA1, rH8, eF9, rF8, eC11, rC10, rG12, rG13, eE14}; int idx = 0; jobQueue.setOptimizeIteration(false); jobQueue.resetIterator(); while ((job = jobQueue.next()) != null) { assertEquals("List wasn't correctly sorted @ index " + idx, expectedOrder[idx].getJobId(), job.getJobId()); expectedPureOrder[idx].getJobId(), job.getJobId()); idx++; } final JobStatus[] expectedOptimizedOrder = new JobStatus[]{ eC3, eC11, rD4, eE5, eE14, eB6, rB2, eA7, rA1, rH8, eF9, rF8, rC10, rG12, rG13}; idx = 0; jobQueue.setOptimizeIteration(true); jobQueue.resetIterator(); while ((job = jobQueue.next()) != null) { assertEquals("Optimized list wasn't correctly sorted @ index " + idx, expectedOptimizedOrder[idx].getJobId(), job.getJobId()); idx++; } } Loading Loading @@ -379,7 +392,9 @@ public class PendingJobQueueTest { JobStatus job; jobQueue.resetIterator(); int count = 0; while ((job = jobQueue.next()) != null) { count++; final int uid = job.getSourceUid(); // Invariant #1: All jobs are sorted by override state Loading Loading @@ -436,6 +451,7 @@ public class PendingJobQueueTest { fail("UID " + uid + " had an EJ ordered after a regular job"); } } assertEquals("Iterator didn't go through all jobs", jobQueue.size(), count); } private static String testJobToString(JobStatus job) { Loading Loading
apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java +52 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.util.Pools; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.server.job.controllers.JobStatus; import java.util.ArrayList; Loading Loading @@ -66,6 +67,21 @@ class PendingJobQueue { private int mSize = 0; /** * Whether to batch iteration so that we pull several of an app's jobs from the queue at the * same time (resulting in some out of order pulls) instead of pulling purely based on the * sort order. Batching it this way will mean we try to run several jobs of the same app at the * same, resulting in fewer process restarts, and can allow the iteration runtime to amortize * to O(A*J) instead of O(A*J*log(A)), where A = # apps and J = average # jobs per app. */ private boolean mOptimizeIteration = true; /** * Number of jobs that have been pulled from the queue in succession. Used when * {@link #mOptimizeIteration} is true to know when to switch to the next AppJobQueue. */ private int mPullCount = 0; private boolean mNeedToResetIterators = false; void add(@NonNull JobStatus job) { Loading Loading @@ -139,16 +155,44 @@ class PendingJobQueue { mOrderedQueues.offer(ajq); } mNeedToResetIterators = false; // Reset the pull count when the front of the queue changes. mPullCount = 0; } else if (mOrderedQueues.size() == 0) { // Something significant changed, so the priority queue was cleared. Lazily regenerate // the queue. for (int i = mCurrentQueues.size() - 1; i >= 0; --i) { final AppJobQueue ajq = mCurrentQueues.valueAt(i); mOrderedQueues.offer(ajq); } // Reset the pull count when the front of the queue changes. mPullCount = 0; } final int numQueues = mOrderedQueues.size(); if (numQueues == 0) { return null; } final AppJobQueue earliestQueue = mOrderedQueues.poll(); // Increase the pull limit at a slightly faster rate than log(A) increases (until A>=33). // The pull limit increase is intended to balance fairness (one app can't starve out others) // with efficiency (reducing process restarts). // 1-4 apps --> pullLimit = 1, 5-8 apps --> pullLimit = 2, 9+ apps --> pullLimit = 3 final int pullLimit = mOptimizeIteration ? Math.min(3, ((numQueues - 1) >>> 2) + 1) : 1; final AppJobQueue earliestQueue = mOrderedQueues.peek(); if (earliestQueue != null) { JobStatus job = earliestQueue.next(); final JobStatus job = earliestQueue.next(); // Change the front of the queue if we've pulled pullLimit jobs from the current head // or the current head has no more jobs to provide. if (++mPullCount >= pullLimit || earliestQueue.peekNextTimestamp() == AppJobQueue.NO_NEXT_TIMESTAMP) { mOrderedQueues.poll(); if (earliestQueue.peekNextTimestamp() != AppJobQueue.NO_NEXT_TIMESTAMP) { // No need to put back in the queue if it has no more jobs to give. mOrderedQueues.offer(earliestQueue); } // Reset the pull count when the front of the queue changes. mPullCount = 0; } return job; } return null; Loading Loading @@ -186,6 +230,11 @@ class PendingJobQueue { mNeedToResetIterators = true; } @VisibleForTesting void setOptimizeIteration(boolean optimize) { mOptimizeIteration = optimize; } int size() { return mSize; } Loading
services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java +20 −4 Original line number Diff line number Diff line Loading @@ -211,13 +211,26 @@ public class PendingJobQueueTest { jobQueue.add(eC11); checkPendingJobInvariants(jobQueue); final JobStatus[] expectedOrder = new JobStatus[]{ eA7, rA1, eB6, rB2, eC3, rD4, eE5, eF9, rH8, rF8, eC11, rC10, rG12, rG13, eE14}; int idx = 0; JobStatus job; final JobStatus[] expectedPureOrder = new JobStatus[]{ eC3, rD4, eE5, eB6, rB2, eA7, rA1, rH8, eF9, rF8, eC11, rC10, rG12, rG13, eE14}; int idx = 0; jobQueue.setOptimizeIteration(false); jobQueue.resetIterator(); while ((job = jobQueue.next()) != null) { assertEquals("List wasn't correctly sorted @ index " + idx, expectedOrder[idx].getJobId(), job.getJobId()); expectedPureOrder[idx].getJobId(), job.getJobId()); idx++; } final JobStatus[] expectedOptimizedOrder = new JobStatus[]{ eC3, eC11, rD4, eE5, eE14, eB6, rB2, eA7, rA1, rH8, eF9, rF8, rC10, rG12, rG13}; idx = 0; jobQueue.setOptimizeIteration(true); jobQueue.resetIterator(); while ((job = jobQueue.next()) != null) { assertEquals("Optimized list wasn't correctly sorted @ index " + idx, expectedOptimizedOrder[idx].getJobId(), job.getJobId()); idx++; } } Loading Loading @@ -379,7 +392,9 @@ public class PendingJobQueueTest { JobStatus job; jobQueue.resetIterator(); int count = 0; while ((job = jobQueue.next()) != null) { count++; final int uid = job.getSourceUid(); // Invariant #1: All jobs are sorted by override state Loading Loading @@ -436,6 +451,7 @@ public class PendingJobQueueTest { fail("UID " + uid + " had an EJ ordered after a regular job"); } } assertEquals("Iterator didn't go through all jobs", jobQueue.size(), count); } private static String testJobToString(JobStatus job) { Loading