Merge "Use new sorting mechanism." into tm-dev
This commit is contained in:
commit
c6f4154081
@ -68,7 +68,6 @@ import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
@ -578,7 +577,7 @@ class JobConcurrencyManager {
|
||||
Slog.d(TAG, printPendingQueueLocked());
|
||||
}
|
||||
|
||||
final List<JobStatus> pendingJobs = mService.mPendingJobs;
|
||||
final PendingJobQueue pendingJobQueue = mService.mPendingJobQueue;
|
||||
final List<JobServiceContext> activeServices = mActiveServices;
|
||||
|
||||
// To avoid GC churn, we recycle the arrays.
|
||||
@ -597,7 +596,7 @@ class JobConcurrencyManager {
|
||||
// Update the priorities of jobs that aren't running, and also count the pending work types.
|
||||
// Do this before the following loop to hopefully reduce the cost of
|
||||
// shouldStopRunningJobLocked().
|
||||
updateNonRunningPrioritiesLocked(pendingJobs, true);
|
||||
updateNonRunningPrioritiesLocked(pendingJobQueue, true);
|
||||
|
||||
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
|
||||
final JobServiceContext js = activeServices.get(i);
|
||||
@ -620,9 +619,9 @@ class JobConcurrencyManager {
|
||||
|
||||
mWorkCountTracker.onCountDone();
|
||||
|
||||
for (int i = 0; i < pendingJobs.size(); i++) {
|
||||
final JobStatus nextPending = pendingJobs.get(i);
|
||||
|
||||
JobStatus nextPending;
|
||||
pendingJobQueue.resetIterator();
|
||||
while ((nextPending = pendingJobQueue.next()) != null) {
|
||||
if (mRunningJobs.contains(nextPending)) {
|
||||
continue;
|
||||
}
|
||||
@ -841,10 +840,11 @@ class JobConcurrencyManager {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updateNonRunningPrioritiesLocked(@NonNull final List<JobStatus> pendingJobs,
|
||||
private void updateNonRunningPrioritiesLocked(@NonNull final PendingJobQueue jobQueue,
|
||||
boolean updateCounter) {
|
||||
for (int i = 0; i < pendingJobs.size(); i++) {
|
||||
final JobStatus pending = pendingJobs.get(i);
|
||||
JobStatus pending;
|
||||
jobQueue.resetIterator();
|
||||
while ((pending = jobQueue.next()) != null) {
|
||||
|
||||
// If job is already running, go to next job.
|
||||
if (mRunningJobs.contains(pending)) {
|
||||
@ -882,7 +882,8 @@ class JobConcurrencyManager {
|
||||
}
|
||||
// Use < instead of <= as that gives us a little wiggle room in case a new job comes
|
||||
// along very shortly.
|
||||
if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) {
|
||||
if (mService.mPendingJobQueue.size() + mRunningJobs.size()
|
||||
< mWorkTypeConfig.getMaxTotal()) {
|
||||
// Don't artificially limit a single package if we don't even have enough jobs to use
|
||||
// the maximum number of slots. We'll preempt the job later if we need the slot.
|
||||
return false;
|
||||
@ -936,8 +937,7 @@ class JobConcurrencyManager {
|
||||
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
|
||||
packageStats);
|
||||
}
|
||||
final List<JobStatus> pendingJobs = mService.mPendingJobs;
|
||||
if (pendingJobs.remove(jobStatus)) {
|
||||
if (mService.mPendingJobQueue.remove(jobStatus)) {
|
||||
mService.mJobPackageTracker.noteNonpending(jobStatus);
|
||||
}
|
||||
} finally {
|
||||
@ -962,11 +962,11 @@ class JobConcurrencyManager {
|
||||
}
|
||||
}
|
||||
|
||||
final List<JobStatus> pendingJobs = mService.mPendingJobs;
|
||||
final PendingJobQueue pendingJobQueue = mService.mPendingJobQueue;
|
||||
if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
|
||||
updateCounterConfigLocked();
|
||||
// Preemption case needs special care.
|
||||
updateNonRunningPrioritiesLocked(pendingJobs, false);
|
||||
updateNonRunningPrioritiesLocked(pendingJobQueue, false);
|
||||
|
||||
JobStatus highestBiasJob = null;
|
||||
int highBiasWorkType = workType;
|
||||
@ -974,9 +974,10 @@ class JobConcurrencyManager {
|
||||
JobStatus backupJob = null;
|
||||
int backupWorkType = WORK_TYPE_NONE;
|
||||
int backupAllWorkTypes = WORK_TYPE_NONE;
|
||||
for (int i = 0; i < pendingJobs.size(); i++) {
|
||||
final JobStatus nextPending = pendingJobs.get(i);
|
||||
|
||||
JobStatus nextPending;
|
||||
pendingJobQueue.resetIterator();
|
||||
while ((nextPending = pendingJobQueue.next()) != null) {
|
||||
if (mRunningJobs.contains(nextPending)) {
|
||||
continue;
|
||||
}
|
||||
@ -1041,16 +1042,18 @@ class JobConcurrencyManager {
|
||||
startJobLocked(worker, backupJob, backupWorkType);
|
||||
}
|
||||
}
|
||||
} else if (pendingJobs.size() > 0) {
|
||||
} else if (pendingJobQueue.size() > 0) {
|
||||
updateCounterConfigLocked();
|
||||
updateNonRunningPrioritiesLocked(pendingJobs, false);
|
||||
updateNonRunningPrioritiesLocked(pendingJobQueue, false);
|
||||
|
||||
// This slot is now free and we have pending jobs. Start the highest bias job we find.
|
||||
JobStatus highestBiasJob = null;
|
||||
int highBiasWorkType = workType;
|
||||
int highBiasAllWorkTypes = workType;
|
||||
for (int i = 0; i < pendingJobs.size(); i++) {
|
||||
final JobStatus nextPending = pendingJobs.get(i);
|
||||
|
||||
JobStatus nextPending;
|
||||
pendingJobQueue.resetIterator();
|
||||
while ((nextPending = pendingJobQueue.next()) != null) {
|
||||
|
||||
if (mRunningJobs.contains(nextPending)) {
|
||||
continue;
|
||||
@ -1127,8 +1130,8 @@ class JobConcurrencyManager {
|
||||
return "too many jobs running";
|
||||
}
|
||||
|
||||
final List<JobStatus> pendingJobs = mService.mPendingJobs;
|
||||
final int numPending = pendingJobs.size();
|
||||
final PendingJobQueue pendingJobQueue = mService.mPendingJobQueue;
|
||||
final int numPending = pendingJobQueue.size();
|
||||
if (numPending == 0) {
|
||||
// All quiet. We can let this job run to completion.
|
||||
return null;
|
||||
@ -1163,8 +1166,9 @@ class JobConcurrencyManager {
|
||||
|
||||
// Harder check. We need to see if a different work type can replace this job.
|
||||
int remainingWorkTypes = ALL_WORK_TYPES;
|
||||
for (int i = 0; i < numPending; ++i) {
|
||||
final JobStatus pending = pendingJobs.get(i);
|
||||
JobStatus pending;
|
||||
pendingJobQueue.resetIterator();
|
||||
while ((pending = pendingJobQueue.next()) != null) {
|
||||
final int workTypes = getJobWorkTypes(pending);
|
||||
if ((workTypes & remainingWorkTypes) > 0
|
||||
&& mWorkCountTracker.canJobStart(workTypes, workType) != WORK_TYPE_NONE) {
|
||||
@ -1201,9 +1205,10 @@ class JobConcurrencyManager {
|
||||
@GuardedBy("mLock")
|
||||
private String printPendingQueueLocked() {
|
||||
StringBuilder s = new StringBuilder("Pending queue: ");
|
||||
Iterator<JobStatus> it = mService.mPendingJobs.iterator();
|
||||
while (it.hasNext()) {
|
||||
JobStatus js = it.next();
|
||||
PendingJobQueue pendingJobQueue = mService.mPendingJobQueue;
|
||||
JobStatus js;
|
||||
pendingJobQueue.resetIterator();
|
||||
while ((js = pendingJobQueue.next()) != null) {
|
||||
s.append("(")
|
||||
.append(js.getJob().getId())
|
||||
.append(", ")
|
||||
|
@ -21,7 +21,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import android.annotation.ElapsedRealtimeLong;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
@ -81,7 +80,6 @@ import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.SparseIntArray;
|
||||
import android.util.SparseLongArray;
|
||||
import android.util.SparseSetArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
@ -288,7 +286,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
|
||||
* when ready to execute them.
|
||||
*/
|
||||
final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
|
||||
final PendingJobQueue mPendingJobQueue = new PendingJobQueue();
|
||||
|
||||
int[] mStartedUsers = EmptyArray.INT;
|
||||
|
||||
@ -828,189 +826,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
final Constants mConstants;
|
||||
final ConstantsObserver mConstantsObserver;
|
||||
|
||||
@VisibleForTesting
|
||||
class PendingJobComparator implements Comparator<JobStatus> {
|
||||
private static final int EJ_PRIORITY_MODIFIER = 10;
|
||||
|
||||
/** Cache of the earliest non-PRIORITY_MAX enqueue time found per UID. */
|
||||
private final SparseLongArray mEarliestNonMaxEnqueueTimeCache = new SparseLongArray();
|
||||
/**
|
||||
* Cache of the last enqueue time of each priority for each UID. The SparseArray is keyed
|
||||
* by UID and the SparseLongArray is keyed by the priority.
|
||||
*/
|
||||
private final SparseArray<SparseLongArray> mLastPriorityEnqueueTimeCache =
|
||||
new SparseArray<>();
|
||||
/**
|
||||
* The earliest enqueue time each UID's priority's jobs should use. The SparseArray is keyed
|
||||
* by UID and the SparseLongArray is keyed by the value returned from
|
||||
* {@link #getPriorityIndex(int, boolean)}.
|
||||
*/
|
||||
private final SparseArray<SparseLongArray> mEarliestAllowedEnqueueTimes =
|
||||
new SparseArray<>();
|
||||
|
||||
private int getPriorityIndex(int priority, boolean isEJ) {
|
||||
// We need to separate HIGH priority EJs from HIGH priority regular jobs.
|
||||
if (isEJ) {
|
||||
return priority * EJ_PRIORITY_MODIFIER;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh sorting determinants based on the current state of {@link #mPendingJobs}.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
@VisibleForTesting
|
||||
void refreshLocked() {
|
||||
mEarliestNonMaxEnqueueTimeCache.clear();
|
||||
for (int i = 0; i < mPendingJobs.size(); ++i) {
|
||||
final JobStatus job = mPendingJobs.get(i);
|
||||
final int uid = job.getSourceUid();
|
||||
if (job.getEffectivePriority() < JobInfo.PRIORITY_MAX) {
|
||||
final long earliestEnqueueTime =
|
||||
mEarliestNonMaxEnqueueTimeCache.get(uid, Long.MAX_VALUE);
|
||||
mEarliestNonMaxEnqueueTimeCache.put(uid,
|
||||
Math.min(earliestEnqueueTime, job.enqueueTime));
|
||||
}
|
||||
|
||||
final int pIdx =
|
||||
getPriorityIndex(job.getEffectivePriority(), job.isRequestedExpeditedJob());
|
||||
SparseLongArray lastPriorityEnqueueTime = mLastPriorityEnqueueTimeCache.get(uid);
|
||||
if (lastPriorityEnqueueTime == null) {
|
||||
lastPriorityEnqueueTime = new SparseLongArray();
|
||||
mLastPriorityEnqueueTimeCache.put(uid, lastPriorityEnqueueTime);
|
||||
}
|
||||
lastPriorityEnqueueTime.put(pIdx,
|
||||
Math.max(job.enqueueTime, lastPriorityEnqueueTime.get(pIdx, 0)));
|
||||
}
|
||||
|
||||
// Move lower priority jobs behind higher priority jobs (instead of moving higher
|
||||
// priority jobs ahead of lower priority jobs), except for EJs.
|
||||
for (int i = 0; i < mLastPriorityEnqueueTimeCache.size(); ++i) {
|
||||
final int uid = mLastPriorityEnqueueTimeCache.keyAt(i);
|
||||
SparseLongArray lastEnqueueTimes = mLastPriorityEnqueueTimeCache.valueAt(i);
|
||||
SparseLongArray earliestAllowedEnqueueTimes = new SparseLongArray();
|
||||
mEarliestAllowedEnqueueTimes.put(uid, earliestAllowedEnqueueTimes);
|
||||
long earliestAllowedEnqueueTime = mEarliestNonMaxEnqueueTimeCache.get(uid,
|
||||
lastEnqueueTimes.get(getPriorityIndex(JobInfo.PRIORITY_MAX, true), -1));
|
||||
earliestAllowedEnqueueTimes.put(getPriorityIndex(JobInfo.PRIORITY_MAX, true),
|
||||
earliestAllowedEnqueueTime);
|
||||
earliestAllowedEnqueueTime = 1
|
||||
+ Math.max(earliestAllowedEnqueueTime,
|
||||
lastEnqueueTimes.get(getPriorityIndex(JobInfo.PRIORITY_HIGH, true), -1));
|
||||
earliestAllowedEnqueueTimes.put(getPriorityIndex(JobInfo.PRIORITY_HIGH, true),
|
||||
earliestAllowedEnqueueTime);
|
||||
earliestAllowedEnqueueTime++;
|
||||
for (int p = JobInfo.PRIORITY_HIGH; p >= JobInfo.PRIORITY_MIN; --p) {
|
||||
final int pIdx = getPriorityIndex(p, false);
|
||||
earliestAllowedEnqueueTimes.put(pIdx, earliestAllowedEnqueueTime);
|
||||
final long lastEnqueueTime = lastEnqueueTimes.get(pIdx, -1);
|
||||
if (lastEnqueueTime != -1) {
|
||||
// Add additional millisecond for the next priority to ensure sorting is
|
||||
// stable/accurate when comparing to other apps.
|
||||
earliestAllowedEnqueueTime = 1
|
||||
+ Math.max(earliestAllowedEnqueueTime, lastEnqueueTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear intermediate state that we don't need to reduce steady state memory usage.
|
||||
mLastPriorityEnqueueTimeCache.clear();
|
||||
}
|
||||
|
||||
@ElapsedRealtimeLong
|
||||
private long getEffectiveEnqueueTime(@NonNull JobStatus job) {
|
||||
// Move lower priority jobs behind higher priority jobs (instead of moving higher
|
||||
// priority jobs ahead of lower priority jobs), except for MAX EJs.
|
||||
final int uid = job.getSourceUid();
|
||||
if (job.isRequestedExpeditedJob()
|
||||
&& job.getEffectivePriority() == JobInfo.PRIORITY_MAX) {
|
||||
return Math.min(job.enqueueTime,
|
||||
mEarliestNonMaxEnqueueTimeCache.get(uid, Long.MAX_VALUE));
|
||||
}
|
||||
final int priorityIdx =
|
||||
getPriorityIndex(job.getEffectivePriority(), job.isRequestedExpeditedJob());
|
||||
final SparseLongArray earliestAllowedEnqueueTimes =
|
||||
mEarliestAllowedEnqueueTimes.get(uid);
|
||||
if (earliestAllowedEnqueueTimes == null) {
|
||||
// We're probably trying to insert directly without refreshing the internal arrays.
|
||||
// Since we haven't seen this UID before, we can just use the job's enqueue time.
|
||||
return job.enqueueTime;
|
||||
}
|
||||
return Math.max(job.enqueueTime, earliestAllowedEnqueueTimes.get(priorityIdx));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(JobStatus o1, JobStatus o2) {
|
||||
if (o1 == o2) {
|
||||
return 0;
|
||||
}
|
||||
// Jobs with an override state set (via adb) should be put first as tests/developers
|
||||
// expect the jobs to run immediately.
|
||||
if (o1.overrideState != o2.overrideState) {
|
||||
// Higher override state (OVERRIDE_FULL) should be before lower state
|
||||
// (OVERRIDE_SOFT)
|
||||
return o2.overrideState - o1.overrideState;
|
||||
}
|
||||
final boolean o1EJ = o1.isRequestedExpeditedJob();
|
||||
final boolean o2EJ = o2.isRequestedExpeditedJob();
|
||||
if (o1.getSourceUid() == o2.getSourceUid()) {
|
||||
if (o1EJ != o2EJ) {
|
||||
// Attempt to run requested expedited jobs ahead of regular jobs, regardless of
|
||||
// expedited job quota.
|
||||
return o1EJ ? -1 : 1;
|
||||
}
|
||||
if (o1.getEffectivePriority() != o2.getEffectivePriority()) {
|
||||
// Use the priority set by an app for intra-app job ordering. Higher
|
||||
// priority should be before lower priority.
|
||||
return o2.getEffectivePriority() - o1.getEffectivePriority();
|
||||
}
|
||||
} else {
|
||||
// TODO: see if we can simplify this using explicit topological sorting
|
||||
// Since we order jobs within a UID by the job's priority, in order to satisfy the
|
||||
// transitivity constraint of the comparator, we must ensure consistent/appropriate
|
||||
// ordering between apps as well. That is, if a job is ordered before or behind
|
||||
// another job because of its priority, that ordering must translate to the
|
||||
// relative ordering against other jobs.
|
||||
// The effective ordering implementation here is to use HIGH priority EJs as a
|
||||
// pivot point. MAX priority EJs are moved *ahead* of HIGH priority EJs. All
|
||||
// regular jobs are moved *behind* HIGH priority EJs. The intention for moving jobs
|
||||
// "behind" the EJs instead of moving all high priority jobs before lower priority
|
||||
// jobs is to reduce any potential abuse (or just unfortunate execution) cases where
|
||||
// there are early low priority jobs that don't get to run because so many of the
|
||||
// app's high priority jobs are pushed before low priority job. This may still
|
||||
// happen because of the job ordering mechanism, but moving jobs back prevents
|
||||
// one app's jobs from always being at the front (due to the early scheduled low
|
||||
// priority job and our base case of sorting by enqueue time).
|
||||
|
||||
final long o1EffectiveEnqueueTime = getEffectiveEnqueueTime(o1);
|
||||
final long o2EffectiveEnqueueTime = getEffectiveEnqueueTime(o2);
|
||||
|
||||
if (o1EffectiveEnqueueTime < o2EffectiveEnqueueTime) {
|
||||
return -1;
|
||||
} else if (o1EffectiveEnqueueTime > o2EffectiveEnqueueTime) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (o1.enqueueTime < o2.enqueueTime) {
|
||||
return -1;
|
||||
}
|
||||
return o1.enqueueTime > o2.enqueueTime ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
final PendingJobComparator mPendingJobComparator = new PendingJobComparator();
|
||||
|
||||
static <T> void addOrderedItem(ArrayList<T> array, T newItem, Comparator<T> comparator) {
|
||||
int where = Collections.binarySearch(array, newItem, comparator);
|
||||
if (where < 0) {
|
||||
where = ~where;
|
||||
}
|
||||
array.add(where, newItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
|
||||
* still clean up. On reinstall the package will have a new uid.
|
||||
@ -1434,7 +1249,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
// This is a new job, we can just immediately put it on the pending
|
||||
// list and try to run it.
|
||||
mJobPackageTracker.notePending(jobStatus);
|
||||
addOrderedItem(mPendingJobs, jobStatus, mPendingJobComparator);
|
||||
mPendingJobQueue.add(jobStatus);
|
||||
maybeRunPendingJobsLocked();
|
||||
} else {
|
||||
evaluateControllerStatesLocked(jobStatus);
|
||||
@ -1563,7 +1378,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
cancelled.unprepareLocked();
|
||||
stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
|
||||
// Remove from pending queue.
|
||||
if (mPendingJobs.remove(cancelled)) {
|
||||
if (mPendingJobQueue.remove(cancelled)) {
|
||||
mJobPackageTracker.noteNonpending(cancelled);
|
||||
}
|
||||
// Cancel if running.
|
||||
@ -1658,8 +1473,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
|
||||
void reportActiveLocked() {
|
||||
// active is true if pending queue contains jobs OR some job is running.
|
||||
boolean active = mPendingJobs.size() > 0;
|
||||
if (mPendingJobs.size() <= 0) {
|
||||
boolean active = mPendingJobQueue.size() > 0;
|
||||
if (!active) {
|
||||
final ArraySet<JobStatus> runningJobs = mConcurrencyManager.getRunningJobsLocked();
|
||||
for (int i = runningJobs.size() - 1; i >= 0; --i) {
|
||||
final JobStatus job = runningJobs.valueAt(i);
|
||||
@ -1952,10 +1767,13 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
mJobPackageTracker.noteNonpending(job);
|
||||
}
|
||||
|
||||
void noteJobsNonpending(List<JobStatus> jobs) {
|
||||
for (int i = jobs.size() - 1; i >= 0; i--) {
|
||||
noteJobNonPending(jobs.get(i));
|
||||
private void clearPendingJobQueue() {
|
||||
JobStatus job;
|
||||
mPendingJobQueue.resetIterator();
|
||||
while ((job = mPendingJobQueue.next()) != null) {
|
||||
noteJobNonPending(job);
|
||||
}
|
||||
mPendingJobQueue.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2236,7 +2054,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
if (js != null) {
|
||||
if (isReadyToBeExecutedLocked(js)) {
|
||||
mJobPackageTracker.notePending(js);
|
||||
addOrderedItem(mPendingJobs, js, mPendingJobComparator);
|
||||
mPendingJobQueue.add(js);
|
||||
}
|
||||
mChangedJobList.remove(js);
|
||||
} else {
|
||||
@ -2382,14 +2200,13 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "queuing all ready jobs for execution:");
|
||||
}
|
||||
noteJobsNonpending(mPendingJobs);
|
||||
mPendingJobs.clear();
|
||||
clearPendingJobQueue();
|
||||
stopNonReadyActiveJobsLocked();
|
||||
mJobs.forEachJob(mReadyQueueFunctor);
|
||||
mReadyQueueFunctor.postProcessLocked();
|
||||
|
||||
if (DEBUG) {
|
||||
final int queuedJobs = mPendingJobs.size();
|
||||
final int queuedJobs = mPendingJobQueue.size();
|
||||
if (queuedJobs == 0) {
|
||||
Slog.d(TAG, "No jobs pending.");
|
||||
} else {
|
||||
@ -2416,11 +2233,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
@GuardedBy("mLock")
|
||||
private void postProcessLocked() {
|
||||
noteJobsPending(newReadyJobs);
|
||||
mPendingJobs.addAll(newReadyJobs);
|
||||
mPendingJobComparator.refreshLocked();
|
||||
if (mPendingJobs.size() > 1) {
|
||||
mPendingJobs.sort(mPendingJobComparator);
|
||||
}
|
||||
mPendingJobQueue.addAll(newReadyJobs);
|
||||
|
||||
newReadyJobs.clear();
|
||||
}
|
||||
@ -2453,7 +2266,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
mHandler.obtainMessage(MSG_STOP_JOB,
|
||||
JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, 0, job)
|
||||
.sendToTarget();
|
||||
} else if (mPendingJobs.remove(job)) {
|
||||
} else if (mPendingJobQueue.remove(job)) {
|
||||
noteJobNonPending(job);
|
||||
}
|
||||
return;
|
||||
@ -2530,7 +2343,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
mConcurrencyManager.stopJobOnServiceContextLocked(job, job.getStopReason(),
|
||||
internalStopReason, debugReason);
|
||||
} else if (mPendingJobs.remove(job)) {
|
||||
} else if (mPendingJobQueue.remove(job)) {
|
||||
noteJobNonPending(job);
|
||||
}
|
||||
evaluateControllerStatesLocked(job);
|
||||
@ -2546,11 +2359,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs.");
|
||||
}
|
||||
noteJobsPending(runnableJobs);
|
||||
mPendingJobs.addAll(runnableJobs);
|
||||
mPendingJobComparator.refreshLocked();
|
||||
if (mPendingJobs.size() > 1) {
|
||||
mPendingJobs.sort(mPendingJobComparator);
|
||||
}
|
||||
mPendingJobQueue.addAll(runnableJobs);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
|
||||
@ -2574,14 +2383,13 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
@GuardedBy("mLock")
|
||||
private void maybeQueueReadyJobsForExecutionLocked() {
|
||||
mHandler.removeMessages(MSG_CHECK_JOB);
|
||||
// This method will evaluate all jobs, so we don't need to keep any messages for a suubset
|
||||
// This method will evaluate all jobs, so we don't need to keep any messages for a subset
|
||||
// of jobs in the queue.
|
||||
mHandler.removeMessages(MSG_CHECK_CHANGED_JOB_LIST);
|
||||
mChangedJobList.clear();
|
||||
if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
|
||||
|
||||
noteJobsNonpending(mPendingJobs);
|
||||
mPendingJobs.clear();
|
||||
clearPendingJobQueue();
|
||||
stopNonReadyActiveJobsLocked();
|
||||
mJobs.forEachJob(mMaybeQueueFunctor);
|
||||
mMaybeQueueFunctor.postProcessLocked();
|
||||
@ -2682,7 +2490,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean jobPending = mPendingJobs.contains(job);
|
||||
final boolean jobPending = mPendingJobQueue.contains(job);
|
||||
final boolean jobActive = rejectActive && mConcurrencyManager.isJobRunningLocked(job);
|
||||
|
||||
if (DEBUG) {
|
||||
@ -2802,7 +2610,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
*/
|
||||
void maybeRunPendingJobsLocked() {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
|
||||
Slog.d(TAG, "pending queue: " + mPendingJobQueue.size() + " jobs.");
|
||||
}
|
||||
mConcurrencyManager.assignJobsToContextsLocked();
|
||||
reportActiveLocked();
|
||||
@ -3634,7 +3442,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
|
||||
boolean printed = false;
|
||||
if (mPendingJobs.contains(js)) {
|
||||
if (mPendingJobQueue.contains(js)) {
|
||||
pw.print("pending");
|
||||
printed = true;
|
||||
}
|
||||
@ -3836,7 +3644,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
pw.print(" !restricted=");
|
||||
pw.print(!isRestricted);
|
||||
pw.print(" !pending=");
|
||||
pw.print(!mPendingJobs.contains(job));
|
||||
pw.print(!mPendingJobQueue.contains(job));
|
||||
pw.print(" !active=");
|
||||
pw.print(!mConcurrencyManager.isJobRunningLocked(job));
|
||||
pw.print(" !backingup=");
|
||||
@ -3929,8 +3737,11 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
boolean pendingPrinted = false;
|
||||
pw.println("Pending queue:");
|
||||
pw.increaseIndent();
|
||||
for (int i = 0; i < mPendingJobs.size(); i++) {
|
||||
JobStatus job = mPendingJobs.get(i);
|
||||
JobStatus job;
|
||||
int pendingIdx = 0;
|
||||
mPendingJobQueue.resetIterator();
|
||||
while ((job = mPendingJobQueue.next()) != null) {
|
||||
pendingIdx++;
|
||||
if (!predicate.test(job)) {
|
||||
continue;
|
||||
}
|
||||
@ -3938,7 +3749,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
pendingPrinted = true;
|
||||
}
|
||||
|
||||
pw.print("Pending #"); pw.print(i); pw.print(": ");
|
||||
pw.print("Pending #"); pw.print(pendingIdx); pw.print(": ");
|
||||
pw.println(job.toShortString());
|
||||
|
||||
pw.increaseIndent();
|
||||
@ -3969,7 +3780,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
// Print most recent first
|
||||
final int idx = (mLastCompletedJobIndex + NUM_COMPLETED_JOB_HISTORY - r)
|
||||
% NUM_COMPLETED_JOB_HISTORY;
|
||||
final JobStatus job = mLastCompletedJobs[idx];
|
||||
job = mLastCompletedJobs[idx];
|
||||
if (job != null) {
|
||||
if (!predicate.test(job)) {
|
||||
continue;
|
||||
@ -4062,7 +3873,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_RESTRICTED,
|
||||
checkIfRestricted(job) != null);
|
||||
proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING,
|
||||
mPendingJobs.contains(job));
|
||||
mPendingJobQueue.contains(job));
|
||||
proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE,
|
||||
mConcurrencyManager.isJobRunningLocked(job));
|
||||
proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_UID_BACKING_UP,
|
||||
@ -4109,7 +3920,9 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
mJobPackageTracker.dumpHistory(proto, JobSchedulerServiceDumpProto.HISTORY,
|
||||
filterAppId);
|
||||
|
||||
for (JobStatus job : mPendingJobs) {
|
||||
JobStatus job;
|
||||
mPendingJobQueue.resetIterator();
|
||||
while ((job = mPendingJobQueue.next()) != null) {
|
||||
final long pjToken = proto.start(JobSchedulerServiceDumpProto.PENDING_JOBS);
|
||||
|
||||
job.writeToShortProto(proto, PendingJob.INFO);
|
||||
|
@ -30,7 +30,6 @@ import static com.android.server.job.JobSchedulerService.RARE_INDEX;
|
||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
@ -56,11 +55,6 @@ import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.SystemClock;
|
||||
import android.platform.test.annotations.LargeTest;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.SparseLongArray;
|
||||
|
||||
import com.android.server.AppStateTracker;
|
||||
import com.android.server.AppStateTrackerImpl;
|
||||
@ -82,16 +76,10 @@ import org.mockito.quality.Strictness;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Random;
|
||||
|
||||
public class JobSchedulerServiceTest {
|
||||
private static final String TAG = JobSchedulerServiceTest.class.getSimpleName();
|
||||
|
||||
private static final int[] sRegJobPriorities = {
|
||||
JobInfo.PRIORITY_HIGH, JobInfo.PRIORITY_DEFAULT,
|
||||
JobInfo.PRIORITY_LOW, JobInfo.PRIORITY_MIN
|
||||
};
|
||||
|
||||
private JobSchedulerService mService;
|
||||
|
||||
private MockitoSession mMockingSession;
|
||||
@ -769,7 +757,7 @@ public class JobSchedulerServiceTest {
|
||||
job.setStandbyBucket(RARE_INDEX);
|
||||
|
||||
// Not enough RARE jobs to run.
|
||||
mService.mPendingJobs.clear();
|
||||
mService.mPendingJobQueue.clear();
|
||||
maybeQueueFunctor.reset();
|
||||
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
|
||||
maybeQueueFunctor.accept(job);
|
||||
@ -778,10 +766,10 @@ public class JobSchedulerServiceTest {
|
||||
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
|
||||
}
|
||||
maybeQueueFunctor.postProcessLocked();
|
||||
assertEquals(0, mService.mPendingJobs.size());
|
||||
assertEquals(0, mService.mPendingJobQueue.size());
|
||||
|
||||
// Enough RARE jobs to run.
|
||||
mService.mPendingJobs.clear();
|
||||
mService.mPendingJobQueue.clear();
|
||||
maybeQueueFunctor.reset();
|
||||
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT; ++i) {
|
||||
maybeQueueFunctor.accept(job);
|
||||
@ -790,10 +778,10 @@ public class JobSchedulerServiceTest {
|
||||
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
|
||||
}
|
||||
maybeQueueFunctor.postProcessLocked();
|
||||
assertEquals(5, mService.mPendingJobs.size());
|
||||
assertEquals(5, mService.mPendingJobQueue.size());
|
||||
|
||||
// Not enough RARE jobs to run, but a non-batched job saves the day.
|
||||
mService.mPendingJobs.clear();
|
||||
mService.mPendingJobQueue.clear();
|
||||
maybeQueueFunctor.reset();
|
||||
JobStatus activeJob = createJobStatus(
|
||||
"testRareJobBatching",
|
||||
@ -807,10 +795,10 @@ public class JobSchedulerServiceTest {
|
||||
}
|
||||
maybeQueueFunctor.accept(activeJob);
|
||||
maybeQueueFunctor.postProcessLocked();
|
||||
assertEquals(3, mService.mPendingJobs.size());
|
||||
assertEquals(3, mService.mPendingJobQueue.size());
|
||||
|
||||
// Not enough RARE jobs to run, but an old RARE job saves the day.
|
||||
mService.mPendingJobs.clear();
|
||||
mService.mPendingJobQueue.clear();
|
||||
maybeQueueFunctor.reset();
|
||||
JobStatus oldRareJob = createJobStatus("testRareJobBatching", createJobInfo());
|
||||
oldRareJob.setStandbyBucket(RARE_INDEX);
|
||||
@ -826,7 +814,7 @@ public class JobSchedulerServiceTest {
|
||||
maybeQueueFunctor.accept(oldRareJob);
|
||||
assertEquals(oldBatchTime, oldRareJob.getFirstForceBatchedTimeElapsed());
|
||||
maybeQueueFunctor.postProcessLocked();
|
||||
assertEquals(3, mService.mPendingJobs.size());
|
||||
assertEquals(3, mService.mPendingJobQueue.size());
|
||||
}
|
||||
|
||||
/** Tests that jobs scheduled by the app itself are counted towards scheduling limits. */
|
||||
@ -914,358 +902,4 @@ public class JobSchedulerServiceTest {
|
||||
0, ""));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingJobSorting() {
|
||||
// First letter in job variable name indicate regular (r) or expedited (e).
|
||||
// Capital letters in job variable name indicate the app/UID.
|
||||
// Numbers in job variable name indicate the enqueue time.
|
||||
// Expected sort order:
|
||||
// eA7 > rA1 > eB6 > rB2 > eC3 > rD4 > eE5 > eF9 > rF8 > eC11 > rC10 > rG12 > rG13 > eE14
|
||||
// Intentions:
|
||||
// * A jobs let us test skipping both regular and expedited jobs of other apps
|
||||
// * B jobs let us test skipping only regular job of another app without going too far
|
||||
// * C jobs test that regular jobs don't skip over other app's jobs and that EJs only
|
||||
// skip up to level of the earliest regular job
|
||||
// * E jobs test that expedited jobs don't skip the line when the app has no regular jobs
|
||||
// * F jobs test correct expedited/regular ordering doesn't push jobs too high in list
|
||||
// * G jobs test correct ordering for regular jobs
|
||||
// * H job tests correct behavior when enqueue times are the same
|
||||
JobStatus rA1 = createJobStatus("testPendingJobSorting", createJobInfo(1), 1);
|
||||
JobStatus rB2 = createJobStatus("testPendingJobSorting", createJobInfo(2), 2);
|
||||
JobStatus eC3 = createJobStatus("testPendingJobSorting",
|
||||
createJobInfo(3).setExpedited(true), 3);
|
||||
JobStatus rD4 = createJobStatus("testPendingJobSorting", createJobInfo(4), 4);
|
||||
JobStatus eE5 = createJobStatus("testPendingJobSorting",
|
||||
createJobInfo(5).setExpedited(true), 5);
|
||||
JobStatus eB6 = createJobStatus("testPendingJobSorting",
|
||||
createJobInfo(6).setExpedited(true), 2);
|
||||
JobStatus eA7 = createJobStatus("testPendingJobSorting",
|
||||
createJobInfo(7).setExpedited(true), 1);
|
||||
JobStatus rH8 = createJobStatus("testPendingJobSorting", createJobInfo(8), 8);
|
||||
JobStatus rF8 = createJobStatus("testPendingJobSorting", createJobInfo(8), 6);
|
||||
JobStatus eF9 = createJobStatus("testPendingJobSorting",
|
||||
createJobInfo(9).setExpedited(true), 6);
|
||||
JobStatus rC10 = createJobStatus("testPendingJobSorting", createJobInfo(10), 3);
|
||||
JobStatus eC11 = createJobStatus("testPendingJobSorting",
|
||||
createJobInfo(11).setExpedited(true), 3);
|
||||
JobStatus rG12 = createJobStatus("testPendingJobSorting", createJobInfo(12), 7);
|
||||
JobStatus rG13 = createJobStatus("testPendingJobSorting", createJobInfo(13), 7);
|
||||
JobStatus eE14 = createJobStatus("testPendingJobSorting",
|
||||
createJobInfo(14).setExpedited(true), 5);
|
||||
|
||||
rA1.enqueueTime = 10;
|
||||
rB2.enqueueTime = 20;
|
||||
eC3.enqueueTime = 30;
|
||||
rD4.enqueueTime = 40;
|
||||
eE5.enqueueTime = 50;
|
||||
eB6.enqueueTime = 60;
|
||||
eA7.enqueueTime = 70;
|
||||
rF8.enqueueTime = 80;
|
||||
rH8.enqueueTime = 80;
|
||||
eF9.enqueueTime = 90;
|
||||
rC10.enqueueTime = 100;
|
||||
eC11.enqueueTime = 110;
|
||||
rG12.enqueueTime = 120;
|
||||
rG13.enqueueTime = 130;
|
||||
eE14.enqueueTime = 140;
|
||||
|
||||
mService.mPendingJobs.clear();
|
||||
// Add in random order so sorting is apparent.
|
||||
mService.mPendingJobs.add(eC3);
|
||||
mService.mPendingJobs.add(eE5);
|
||||
mService.mPendingJobs.add(rA1);
|
||||
mService.mPendingJobs.add(rG13);
|
||||
mService.mPendingJobs.add(rD4);
|
||||
mService.mPendingJobs.add(eA7);
|
||||
mService.mPendingJobs.add(rG12);
|
||||
mService.mPendingJobs.add(rH8);
|
||||
mService.mPendingJobs.add(rF8);
|
||||
mService.mPendingJobs.add(eB6);
|
||||
mService.mPendingJobs.add(eE14);
|
||||
mService.mPendingJobs.add(eF9);
|
||||
mService.mPendingJobs.add(rB2);
|
||||
mService.mPendingJobs.add(rC10);
|
||||
mService.mPendingJobs.add(eC11);
|
||||
|
||||
mService.mPendingJobComparator.refreshLocked();
|
||||
mService.mPendingJobs.sort(mService.mPendingJobComparator);
|
||||
|
||||
final JobStatus[] expectedOrder = new JobStatus[]{
|
||||
eA7, rA1, eB6, rB2, eC3, rD4, eE5, eF9, rH8, rF8, eC11, rC10, rG12, rG13, eE14};
|
||||
for (int i = 0; i < expectedOrder.length; ++i) {
|
||||
assertEquals("List wasn't correctly sorted @ index " + i,
|
||||
expectedOrder[i].getJobId(), mService.mPendingJobs.get(i).getJobId());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPendingJobInvariants() {
|
||||
final SparseBooleanArray regJobSeen = new SparseBooleanArray();
|
||||
// Latest priority enqueue times seen for each priority for each app.
|
||||
final SparseArray<SparseLongArray> latestPriorityRegEnqueueTimesPerUid =
|
||||
new SparseArray<>();
|
||||
final SparseArray<SparseLongArray> latestPriorityEjEnqueueTimesPerUid = new SparseArray<>();
|
||||
final long noEntry = -1;
|
||||
|
||||
for (int i = 0; i < mService.mPendingJobs.size(); ++i) {
|
||||
final JobStatus job = mService.mPendingJobs.get(i);
|
||||
final int uid = job.getSourceUid();
|
||||
|
||||
// Invariant #1: All jobs (for a UID) are sorted by priority order
|
||||
// Invariant #2: Jobs (for a UID) with the same priority are sorted by enqueue time.
|
||||
// Invariant #3: EJs (for a UID) should be before regular jobs
|
||||
|
||||
final int priority = job.getEffectivePriority();
|
||||
final SparseArray<SparseLongArray> latestPriorityEnqueueTimesPerUid =
|
||||
job.isRequestedExpeditedJob()
|
||||
? latestPriorityEjEnqueueTimesPerUid
|
||||
: latestPriorityRegEnqueueTimesPerUid;
|
||||
SparseLongArray latestPriorityEnqueueTimes = latestPriorityEnqueueTimesPerUid.get(uid);
|
||||
if (latestPriorityEnqueueTimes != null) {
|
||||
// Invariant 1
|
||||
for (int p = priority - 1; p >= JobInfo.PRIORITY_MIN; --p) {
|
||||
// If we haven't seen the priority, there shouldn't be an entry in the array.
|
||||
assertEquals("Jobs not properly sorted by priority for uid " + uid,
|
||||
noEntry, latestPriorityEnqueueTimes.get(p, noEntry));
|
||||
}
|
||||
|
||||
// Invariant 2
|
||||
final long lastSeenPriorityEnqueueTime =
|
||||
latestPriorityEnqueueTimes.get(priority, noEntry);
|
||||
if (lastSeenPriorityEnqueueTime != noEntry) {
|
||||
assertTrue("Jobs with same priority not sorted by enqueue time: "
|
||||
+ lastSeenPriorityEnqueueTime + " vs " + job.enqueueTime,
|
||||
lastSeenPriorityEnqueueTime <= job.enqueueTime);
|
||||
}
|
||||
} else {
|
||||
latestPriorityEnqueueTimes = new SparseLongArray();
|
||||
latestPriorityEnqueueTimesPerUid.put(uid, latestPriorityEnqueueTimes);
|
||||
}
|
||||
latestPriorityEnqueueTimes.put(priority, job.enqueueTime);
|
||||
|
||||
// Invariant 3
|
||||
if (!job.isRequestedExpeditedJob()) {
|
||||
regJobSeen.put(uid, true);
|
||||
} else if (regJobSeen.get(uid)) {
|
||||
fail("UID " + uid + " had an EJ ordered after a regular job");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String sortedJobToString(JobStatus job) {
|
||||
return "testJob " + job.getSourceUid() + "/" + job.getJobId()
|
||||
+ "/p" + job.getEffectivePriority()
|
||||
+ "/" + job.isRequestedExpeditedJob() + "@" + job.enqueueTime;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingJobSorting_Random() {
|
||||
Random random = new Random(1); // Always use the same series of pseudo random values.
|
||||
|
||||
mService.mPendingJobs.clear();
|
||||
|
||||
for (int i = 0; i < 5000; ++i) {
|
||||
JobStatus job = createJobStatus("testPendingJobSorting_Random",
|
||||
createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(250));
|
||||
job.enqueueTime = random.nextInt(1_000_000);
|
||||
mService.mPendingJobs.add(job);
|
||||
}
|
||||
|
||||
mService.mPendingJobComparator.refreshLocked();
|
||||
try {
|
||||
mService.mPendingJobs.sort(mService.mPendingJobComparator);
|
||||
} catch (Exception e) {
|
||||
for (JobStatus toDump : mService.mPendingJobs) {
|
||||
Log.i(TAG, sortedJobToString(toDump));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
checkPendingJobInvariants();
|
||||
}
|
||||
|
||||
private int sign(int i) {
|
||||
if (i > 0) {
|
||||
return 1;
|
||||
}
|
||||
if (i < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingJobSortingTransitivity() {
|
||||
// Always use the same series of pseudo random values.
|
||||
for (int seed : new int[]{1337, 7357, 606, 6357, 41106010, 3, 2, 1}) {
|
||||
Random random = new Random(seed);
|
||||
|
||||
mService.mPendingJobs.clear();
|
||||
|
||||
for (int i = 0; i < 300; ++i) {
|
||||
JobStatus job = createJobStatus("testPendingJobSortingTransitivity",
|
||||
createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(50));
|
||||
job.enqueueTime = random.nextInt(1_000_000);
|
||||
job.overrideState = random.nextInt(4);
|
||||
mService.mPendingJobs.add(job);
|
||||
}
|
||||
|
||||
verifyPendingJobComparatorTransitivity();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
public void testPendingJobSortingTransitivity_Concentrated() {
|
||||
// Always use the same series of pseudo random values.
|
||||
for (int seed : new int[]{1337, 6000, 637739, 6357, 1, 7, 13}) {
|
||||
Random random = new Random(seed);
|
||||
|
||||
mService.mPendingJobs.clear();
|
||||
|
||||
for (int i = 0; i < 300; ++i) {
|
||||
JobStatus job = createJobStatus("testPendingJobSortingTransitivity_Concentrated",
|
||||
createJobInfo(i).setExpedited(random.nextFloat() < .03),
|
||||
random.nextInt(20));
|
||||
job.enqueueTime = random.nextInt(250);
|
||||
job.overrideState = random.nextFloat() < .01
|
||||
? JobStatus.OVERRIDE_SORTING : JobStatus.OVERRIDE_NONE;
|
||||
mService.mPendingJobs.add(job);
|
||||
Log.d(TAG, sortedJobToString(job));
|
||||
}
|
||||
|
||||
verifyPendingJobComparatorTransitivity();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingJobSorting_Random_WithPriority() {
|
||||
Random random = new Random(1); // Always use the same series of pseudo random values.
|
||||
|
||||
mService.mPendingJobs.clear();
|
||||
|
||||
for (int i = 0; i < 5000; ++i) {
|
||||
final boolean isEj = random.nextBoolean();
|
||||
final int priority;
|
||||
if (isEj) {
|
||||
priority = random.nextBoolean() ? JobInfo.PRIORITY_MAX : JobInfo.PRIORITY_HIGH;
|
||||
} else {
|
||||
priority = sRegJobPriorities[random.nextInt(sRegJobPriorities.length)];
|
||||
}
|
||||
JobStatus job = createJobStatus("testPendingJobSorting_Random_WithPriority",
|
||||
createJobInfo(i).setExpedited(isEj).setPriority(priority),
|
||||
random.nextInt(250));
|
||||
job.enqueueTime = random.nextInt(1_000_000);
|
||||
mService.mPendingJobs.add(job);
|
||||
}
|
||||
|
||||
mService.mPendingJobComparator.refreshLocked();
|
||||
try {
|
||||
mService.mPendingJobs.sort(mService.mPendingJobComparator);
|
||||
} catch (Exception e) {
|
||||
for (JobStatus toDump : mService.mPendingJobs) {
|
||||
Log.i(TAG, sortedJobToString(toDump));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
checkPendingJobInvariants();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingJobSortingTransitivity_WithPriority() {
|
||||
// Always use the same series of pseudo random values.
|
||||
for (int seed : new int[]{1337, 7357, 606, 6357, 41106010, 3, 2, 1}) {
|
||||
Random random = new Random(seed);
|
||||
|
||||
mService.mPendingJobs.clear();
|
||||
|
||||
for (int i = 0; i < 300; ++i) {
|
||||
final boolean isEj = random.nextBoolean();
|
||||
final int priority;
|
||||
if (isEj) {
|
||||
priority = random.nextBoolean() ? JobInfo.PRIORITY_MAX : JobInfo.PRIORITY_HIGH;
|
||||
} else {
|
||||
priority = sRegJobPriorities[random.nextInt(sRegJobPriorities.length)];
|
||||
}
|
||||
JobStatus job = createJobStatus("testPendingJobSortingTransitivity_WithPriority",
|
||||
createJobInfo(i).setExpedited(isEj).setPriority(priority),
|
||||
random.nextInt(50));
|
||||
job.enqueueTime = random.nextInt(1_000_000);
|
||||
job.overrideState = random.nextInt(4);
|
||||
mService.mPendingJobs.add(job);
|
||||
}
|
||||
|
||||
verifyPendingJobComparatorTransitivity();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
public void testPendingJobSortingTransitivity_Concentrated_WithPriority() {
|
||||
// Always use the same series of pseudo random values.
|
||||
for (int seed : new int[]{1337, 6000, 637739, 6357, 1, 7, 13}) {
|
||||
Random random = new Random(seed);
|
||||
|
||||
mService.mPendingJobs.clear();
|
||||
|
||||
for (int i = 0; i < 300; ++i) {
|
||||
final boolean isEj = random.nextFloat() < .03;
|
||||
final int priority;
|
||||
if (isEj) {
|
||||
priority = random.nextBoolean() ? JobInfo.PRIORITY_MAX : JobInfo.PRIORITY_HIGH;
|
||||
} else {
|
||||
priority = sRegJobPriorities[random.nextInt(sRegJobPriorities.length)];
|
||||
}
|
||||
JobStatus job = createJobStatus(
|
||||
"testPendingJobSortingTransitivity_Concentrated_WithPriority",
|
||||
createJobInfo(i).setExpedited(isEj).setPriority(priority),
|
||||
random.nextInt(20));
|
||||
job.enqueueTime = random.nextInt(250);
|
||||
job.overrideState = random.nextFloat() < .01
|
||||
? JobStatus.OVERRIDE_SORTING : JobStatus.OVERRIDE_NONE;
|
||||
mService.mPendingJobs.add(job);
|
||||
Log.d(TAG, sortedJobToString(job));
|
||||
}
|
||||
|
||||
verifyPendingJobComparatorTransitivity();
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyPendingJobComparatorTransitivity() {
|
||||
mService.mPendingJobComparator.refreshLocked();
|
||||
|
||||
for (int i = 0; i < mService.mPendingJobs.size(); ++i) {
|
||||
final JobStatus job1 = mService.mPendingJobs.get(i);
|
||||
|
||||
for (int j = 0; j < mService.mPendingJobs.size(); ++j) {
|
||||
final JobStatus job2 = mService.mPendingJobs.get(j);
|
||||
final int sign12 = sign(mService.mPendingJobComparator.compare(job1, job2));
|
||||
final int sign21 = sign(mService.mPendingJobComparator.compare(job2, job1));
|
||||
if (sign12 != -sign21) {
|
||||
final String job1String = sortedJobToString(job1);
|
||||
final String job2String = sortedJobToString(job2);
|
||||
fail("compare(" + job1String + ", " + job2String + ") != "
|
||||
+ "-compare(" + job2String + ", " + job1String + ")");
|
||||
}
|
||||
|
||||
for (int k = 0; k < mService.mPendingJobs.size(); ++k) {
|
||||
final JobStatus job3 = mService.mPendingJobs.get(k);
|
||||
final int sign23 = sign(mService.mPendingJobComparator.compare(job2, job3));
|
||||
final int sign13 = sign(mService.mPendingJobComparator.compare(job1, job3));
|
||||
|
||||
// Confirm 1 < 2 < 3 or 1 > 2 > 3 or 1 == 2 == 3
|
||||
if ((sign12 == sign23 && sign12 != sign13)
|
||||
// Confirm that if 1 == 2, then (1 < 3 AND 2 < 3) OR (1 > 3 && 2 > 3)
|
||||
|| (sign12 == 0 && sign13 != sign23)) {
|
||||
final String job1String = sortedJobToString(job1);
|
||||
final String job2String = sortedJobToString(job2);
|
||||
final String job3String = sortedJobToString(job3);
|
||||
fail("Transitivity fail"
|
||||
+ ": compare(" + job1String + ", " + job2String + ")=" + sign12
|
||||
+ ", compare(" + job2String + ", " + job3String + ")=" + sign23
|
||||
+ ", compare(" + job1String + ", " + job3String + ")=" + sign13);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user