Merge "Add additional lanes if current jobs will run too long."
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.job;
|
||||
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
|
||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
|
||||
@@ -99,6 +101,18 @@ class JobConcurrencyManager {
|
||||
static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR =
|
||||
CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular";
|
||||
private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = STANDARD_CONCURRENCY_LIMIT / 2;
|
||||
@VisibleForTesting
|
||||
static final String KEY_ENABLE_MAX_WAIT_TIME_BYPASS =
|
||||
CONFIG_KEY_PREFIX_CONCURRENCY + "enable_max_wait_time_bypass";
|
||||
private static final boolean DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS = true;
|
||||
private static final String KEY_MAX_WAIT_EJ_MS =
|
||||
CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_ej_ms";
|
||||
@VisibleForTesting
|
||||
static final long DEFAULT_MAX_WAIT_EJ_MS = 5 * MINUTE_IN_MILLIS;
|
||||
private static final String KEY_MAX_WAIT_REGULAR_MS =
|
||||
CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_regular_ms";
|
||||
@VisibleForTesting
|
||||
static final long DEFAULT_MAX_WAIT_REGULAR_MS = 30 * MINUTE_IN_MILLIS;
|
||||
|
||||
/**
|
||||
* Set of possible execution types that a job can have. The actual type(s) of a job are based
|
||||
@@ -353,6 +367,20 @@ class JobConcurrencyManager {
|
||||
*/
|
||||
private int mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR;
|
||||
|
||||
private boolean mMaxWaitTimeBypassEnabled = DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS;
|
||||
|
||||
/**
|
||||
* The maximum time an expedited job would have to be potentially waiting for an available
|
||||
* slot before we would consider creating a new slot for it.
|
||||
*/
|
||||
private long mMaxWaitEjMs = DEFAULT_MAX_WAIT_EJ_MS;
|
||||
|
||||
/**
|
||||
* The maximum time a regular job would have to be potentially waiting for an available
|
||||
* slot before we would consider creating a new slot for it.
|
||||
*/
|
||||
private long mMaxWaitRegularMs = DEFAULT_MAX_WAIT_REGULAR_MS;
|
||||
|
||||
/** Current memory trim level. */
|
||||
private int mLastMemoryTrimLevel;
|
||||
|
||||
@@ -665,7 +693,7 @@ class JobConcurrencyManager {
|
||||
return;
|
||||
}
|
||||
|
||||
prepareForAssignmentDeterminationLocked(
|
||||
final long minPreferredUidOnlyWaitingTimeMs = prepareForAssignmentDeterminationLocked(
|
||||
mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -674,7 +702,8 @@ class JobConcurrencyManager {
|
||||
}
|
||||
|
||||
determineAssignmentsLocked(
|
||||
mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
|
||||
mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
|
||||
minPreferredUidOnlyWaitingTimeMs);
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, printAssignments("running jobs final",
|
||||
@@ -691,9 +720,10 @@ class JobConcurrencyManager {
|
||||
noteConcurrency();
|
||||
}
|
||||
|
||||
/** @return the minimum remaining execution time for preferred UID only JobServiceContexts. */
|
||||
@VisibleForTesting
|
||||
@GuardedBy("mLock")
|
||||
void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
|
||||
long prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
|
||||
final List<ContextAssignment> preferredUidOnly,
|
||||
final List<ContextAssignment> stoppable) {
|
||||
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
|
||||
@@ -709,6 +739,8 @@ class JobConcurrencyManager {
|
||||
updateNonRunningPrioritiesLocked(pendingJobQueue, true);
|
||||
|
||||
final int numRunningJobs = activeServices.size();
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
long minPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
|
||||
for (int i = 0; i < numRunningJobs; ++i) {
|
||||
final JobServiceContext jsc = activeServices.get(i);
|
||||
final JobStatus js = jsc.getRunningJobLocked();
|
||||
@@ -729,6 +761,9 @@ class JobConcurrencyManager {
|
||||
if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) {
|
||||
stoppable.add(assignment);
|
||||
} else {
|
||||
assignment.timeUntilStoppableMs = jsc.getRemainingGuaranteedTimeMs(nowElapsed);
|
||||
minPreferredUidOnlyWaitingTimeMs =
|
||||
Math.min(minPreferredUidOnlyWaitingTimeMs, assignment.timeUntilStoppableMs);
|
||||
preferredUidOnly.add(assignment);
|
||||
}
|
||||
}
|
||||
@@ -754,6 +789,10 @@ class JobConcurrencyManager {
|
||||
}
|
||||
|
||||
mWorkCountTracker.onCountDone();
|
||||
// Return 0 if there were no preferred UID only contexts to indicate no waiting time due
|
||||
// to such jobs.
|
||||
return minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
|
||||
? 0 : minPreferredUidOnlyWaitingTimeMs;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -761,12 +800,14 @@ class JobConcurrencyManager {
|
||||
void determineAssignmentsLocked(final ArraySet<ContextAssignment> changed,
|
||||
final ArraySet<ContextAssignment> idle,
|
||||
final List<ContextAssignment> preferredUidOnly,
|
||||
final List<ContextAssignment> stoppable) {
|
||||
final List<ContextAssignment> stoppable,
|
||||
long minPreferredUidOnlyWaitingTimeMs) {
|
||||
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
|
||||
final List<JobServiceContext> activeServices = mActiveServices;
|
||||
pendingJobQueue.resetIterator();
|
||||
JobStatus nextPending;
|
||||
int projectedRunningCount = activeServices.size();
|
||||
long minChangedWaitingTimeMs = Long.MAX_VALUE;
|
||||
while ((nextPending = pendingJobQueue.next()) != null) {
|
||||
if (mRunningJobs.contains(nextPending)) {
|
||||
// Should never happen.
|
||||
@@ -785,6 +826,14 @@ class JobConcurrencyManager {
|
||||
+ " to: " + nextPending);
|
||||
}
|
||||
|
||||
// Factoring minChangedWaitingTimeMs into the min waiting time effectively limits
|
||||
// the number of additional contexts that are created due to long waiting times.
|
||||
// By factoring it in, we imply that the new slot will be available for other
|
||||
// pending jobs that could be designated as waiting too long, and those other jobs
|
||||
// would only have to wait for the new slots to become available.
|
||||
final long minWaitingTimeMs =
|
||||
Math.min(minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);
|
||||
|
||||
// Find an available slot for nextPending. The context should be one of the following:
|
||||
// 1. Unused
|
||||
// 2. Its job should have used up its minimum execution guarantee so it
|
||||
@@ -833,6 +882,7 @@ class JobConcurrencyManager {
|
||||
// app was on TOP, the app is still TOP, but there are too many TOP+EJs
|
||||
// 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
|
||||
if (!canReplace && !isInOverage) {
|
||||
final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
|
||||
@@ -840,6 +890,13 @@ class JobConcurrencyManager {
|
||||
|| currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
|
||||
|| topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4
|
||||
}
|
||||
if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
|
||||
if (nextPending.shouldTreatAsExpeditedJob()) {
|
||||
canReplace = minWaitingTimeMs >= mMaxWaitEjMs;
|
||||
} else {
|
||||
canReplace = minWaitingTimeMs >= mMaxWaitRegularMs;
|
||||
}
|
||||
}
|
||||
if (canReplace) {
|
||||
int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes,
|
||||
assignment.context.getRunningJobWorkType());
|
||||
@@ -860,6 +917,7 @@ class JobConcurrencyManager {
|
||||
}
|
||||
if (selectedContext == null && (!isInOverage || isTopEj)) {
|
||||
int lowestBiasSeen = Integer.MAX_VALUE;
|
||||
long newMinPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
|
||||
for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
|
||||
final ContextAssignment assignment = preferredUidOnly.get(p);
|
||||
final JobStatus runningJob = assignment.context.getRunningJobLocked();
|
||||
@@ -872,6 +930,13 @@ class JobConcurrencyManager {
|
||||
}
|
||||
|
||||
if (selectedContext == null || lowestBiasSeen > jobBias) {
|
||||
if (selectedContext != null) {
|
||||
// We're no longer using the previous context, so factor it into the
|
||||
// calculation.
|
||||
newMinPreferredUidOnlyWaitingTimeMs = Math.min(
|
||||
newMinPreferredUidOnlyWaitingTimeMs,
|
||||
selectedContext.timeUntilStoppableMs);
|
||||
}
|
||||
// Step down the preemption threshold - wind up replacing
|
||||
// the lowest-bias running job
|
||||
lowestBiasSeen = jobBias;
|
||||
@@ -880,11 +945,17 @@ class JobConcurrencyManager {
|
||||
assignment.preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
|
||||
// In this case, we're just going to preempt a low bias job, we're not
|
||||
// actually starting a job, so don't set startingJob to true.
|
||||
} else {
|
||||
// We're not going to use this context, so factor it into the calculation.
|
||||
newMinPreferredUidOnlyWaitingTimeMs = Math.min(
|
||||
newMinPreferredUidOnlyWaitingTimeMs,
|
||||
assignment.timeUntilStoppableMs);
|
||||
}
|
||||
}
|
||||
if (selectedContext != null) {
|
||||
selectedContext.newJob = nextPending;
|
||||
preferredUidOnly.remove(selectedContext);
|
||||
minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
|
||||
}
|
||||
}
|
||||
// Make sure to run EJs for the TOP app immediately.
|
||||
@@ -901,6 +972,9 @@ class JobConcurrencyManager {
|
||||
selectedContext = null;
|
||||
}
|
||||
if (selectedContext == null) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Allowing additional context because EJ would wait too long");
|
||||
}
|
||||
selectedContext = mContextAssignmentPool.acquire();
|
||||
if (selectedContext == null) {
|
||||
selectedContext = new ContextAssignment();
|
||||
@@ -913,6 +987,35 @@ class JobConcurrencyManager {
|
||||
selectedContext.newWorkType =
|
||||
(workType != WORK_TYPE_NONE) ? workType : WORK_TYPE_TOP;
|
||||
}
|
||||
} else if (selectedContext == null && mMaxWaitTimeBypassEnabled) {
|
||||
final boolean wouldBeWaitingTooLong = nextPending.shouldTreatAsExpeditedJob()
|
||||
? minWaitingTimeMs >= mMaxWaitEjMs
|
||||
: minWaitingTimeMs >= mMaxWaitRegularMs;
|
||||
if (wouldBeWaitingTooLong) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Allowing additional context because job would wait too long");
|
||||
}
|
||||
selectedContext = mContextAssignmentPool.acquire();
|
||||
if (selectedContext == null) {
|
||||
selectedContext = new ContextAssignment();
|
||||
}
|
||||
selectedContext.context = mIdleContexts.size() > 0
|
||||
? mIdleContexts.removeAt(mIdleContexts.size() - 1)
|
||||
: createNewJobServiceContext();
|
||||
selectedContext.newJob = nextPending;
|
||||
final int workType = mWorkCountTracker.canJobStart(allWorkTypes);
|
||||
if (workType != WORK_TYPE_NONE) {
|
||||
selectedContext.newWorkType = workType;
|
||||
} else {
|
||||
// Use the strongest work type possible for this job.
|
||||
for (int type = 1; type <= ALL_WORK_TYPES; type = type << 1) {
|
||||
if ((type & allWorkTypes) != 0) {
|
||||
selectedContext.newWorkType = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final PackageStats packageStats = getPkgStatsLocked(
|
||||
nextPending.getSourceUserId(), nextPending.getSourcePackageName());
|
||||
@@ -923,6 +1026,8 @@ class JobConcurrencyManager {
|
||||
}
|
||||
if (selectedContext.newJob != null) {
|
||||
projectedRunningCount++;
|
||||
minChangedWaitingTimeMs = Math.min(minChangedWaitingTimeMs,
|
||||
mService.getMinJobExecutionGuaranteeMs(selectedContext.newJob));
|
||||
}
|
||||
packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
|
||||
}
|
||||
@@ -1251,13 +1356,37 @@ class JobConcurrencyManager {
|
||||
}
|
||||
|
||||
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
|
||||
if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT || pendingJobQueue.size() == 0) {
|
||||
if (pendingJobQueue.size() == 0) {
|
||||
worker.clearPreferredUid();
|
||||
// We're over the limit (because the TOP app scheduled a lot of EJs). Don't start
|
||||
// running anything new until we get back below the limit.
|
||||
noteConcurrency();
|
||||
return;
|
||||
}
|
||||
if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT) {
|
||||
final boolean respectConcurrencyLimit;
|
||||
if (!mMaxWaitTimeBypassEnabled) {
|
||||
respectConcurrencyLimit = true;
|
||||
} else {
|
||||
long minWaitingTimeMs = Long.MAX_VALUE;
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
for (int i = mActiveServices.size() - 1; i >= 0; --i) {
|
||||
minWaitingTimeMs = Math.min(minWaitingTimeMs,
|
||||
mActiveServices.get(i).getRemainingGuaranteedTimeMs(nowElapsed));
|
||||
}
|
||||
final boolean wouldBeWaitingTooLong =
|
||||
mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0
|
||||
? minWaitingTimeMs >= mMaxWaitEjMs
|
||||
: minWaitingTimeMs >= mMaxWaitRegularMs;
|
||||
respectConcurrencyLimit = !wouldBeWaitingTooLong;
|
||||
}
|
||||
if (respectConcurrencyLimit) {
|
||||
worker.clearPreferredUid();
|
||||
// We're over the limit (because the TOP app scheduled a lot of EJs), 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();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
|
||||
updateCounterConfigLocked();
|
||||
@@ -1609,6 +1738,14 @@ class JobConcurrencyManager {
|
||||
mPkgConcurrencyLimitRegular = Math.max(1, Math.min(STANDARD_CONCURRENCY_LIMIT,
|
||||
properties.getInt(
|
||||
KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
|
||||
|
||||
mMaxWaitTimeBypassEnabled = properties.getBoolean(
|
||||
KEY_ENABLE_MAX_WAIT_TIME_BYPASS, DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS);
|
||||
// EJ max wait must be in the range [0, infinity).
|
||||
mMaxWaitEjMs = Math.max(0, properties.getLong(KEY_MAX_WAIT_EJ_MS, DEFAULT_MAX_WAIT_EJ_MS));
|
||||
// Regular max wait must be in the range [EJ max wait, infinity).
|
||||
mMaxWaitRegularMs = Math.max(mMaxWaitEjMs,
|
||||
properties.getLong(KEY_MAX_WAIT_REGULAR_MS, DEFAULT_MAX_WAIT_REGULAR_MS));
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@@ -1622,6 +1759,9 @@ class JobConcurrencyManager {
|
||||
pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
|
||||
pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
|
||||
pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
|
||||
pw.print(KEY_ENABLE_MAX_WAIT_TIME_BYPASS, mMaxWaitTimeBypassEnabled).println();
|
||||
pw.print(KEY_MAX_WAIT_EJ_MS, mMaxWaitEjMs).println();
|
||||
pw.print(KEY_MAX_WAIT_REGULAR_MS, mMaxWaitRegularMs).println();
|
||||
pw.println();
|
||||
CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
|
||||
pw.println();
|
||||
@@ -2382,6 +2522,7 @@ class JobConcurrencyManager {
|
||||
public int workType = WORK_TYPE_NONE;
|
||||
public String preemptReason;
|
||||
public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
|
||||
public long timeUntilStoppableMs;
|
||||
public String shouldStopJobReason;
|
||||
public JobStatus newJob;
|
||||
public int newWorkType = WORK_TYPE_NONE;
|
||||
@@ -2392,6 +2533,7 @@ class JobConcurrencyManager {
|
||||
workType = WORK_TYPE_NONE;
|
||||
preemptReason = null;
|
||||
preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
|
||||
timeUntilStoppableMs = 0;
|
||||
shouldStopJobReason = null;
|
||||
newJob = null;
|
||||
newWorkType = WORK_TYPE_NONE;
|
||||
@@ -2406,6 +2548,15 @@ class JobConcurrencyManager {
|
||||
final PackageStats packageStats =
|
||||
getPackageStatsForTesting(job.getSourceUserId(), job.getSourcePackageName());
|
||||
packageStats.adjustRunningCount(true, job.shouldTreatAsExpeditedJob());
|
||||
|
||||
final JobServiceContext context;
|
||||
if (mIdleContexts.size() > 0) {
|
||||
context = mIdleContexts.removeAt(mIdleContexts.size() - 1);
|
||||
} else {
|
||||
context = createNewJobServiceContext();
|
||||
}
|
||||
context.executeRunnableJob(job, mWorkCountTracker.canJobStart(getJobWorkTypes(job)));
|
||||
mActiveServices.add(context);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -454,6 +454,10 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
return mTimeoutElapsed;
|
||||
}
|
||||
|
||||
long getRemainingGuaranteedTimeMs(long nowElapsed) {
|
||||
return Math.max(0, mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis - nowElapsed);
|
||||
}
|
||||
|
||||
boolean isWithinExecutionGuaranteeTime() {
|
||||
return sElapsedRealtimeClock.millis()
|
||||
< mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
|
||||
|
||||
@@ -34,6 +34,8 @@ import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.anyLong;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -51,6 +53,7 @@ import android.content.res.Resources;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -98,12 +101,23 @@ public final class JobConcurrencyManagerTest {
|
||||
@Mock
|
||||
private IPackageManager mIPackageManager;
|
||||
|
||||
static class InjectorForTest extends JobConcurrencyManager.Injector {
|
||||
private static class InjectorForTest extends JobConcurrencyManager.Injector {
|
||||
public final ArrayMap<JobServiceContext, JobStatus> contexts = new ArrayMap<>();
|
||||
|
||||
@Override
|
||||
JobServiceContext createJobServiceContext(JobSchedulerService service,
|
||||
JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats,
|
||||
JobPackageTracker tracker, Looper looper) {
|
||||
return mock(JobServiceContext.class);
|
||||
final JobServiceContext context = mock(JobServiceContext.class);
|
||||
doAnswer((Answer<Boolean>) invocationOnMock -> {
|
||||
Object[] args = invocationOnMock.getArguments();
|
||||
final JobStatus job = (JobStatus) args[0];
|
||||
contexts.put(context, job);
|
||||
doReturn(job).when(context).getRunningJobLocked();
|
||||
return true;
|
||||
}).when(context).executeRunnableJob(any(), anyInt());
|
||||
contexts.put(context, null);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +156,13 @@ public final class JobConcurrencyManagerTest {
|
||||
doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
|
||||
doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
|
||||
mInjector = new InjectorForTest();
|
||||
doAnswer((Answer<Long>) invocationOnMock -> {
|
||||
Object[] args = invocationOnMock.getArguments();
|
||||
final JobStatus job = (JobStatus) args[0];
|
||||
return job.shouldTreatAsExpeditedJob()
|
||||
? JobSchedulerService.Constants.DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS
|
||||
: JobSchedulerService.Constants.DEFAULT_RUNTIME_MIN_GUARANTEE_MS;
|
||||
}).when(jobSchedulerService).getMinJobExecutionGuaranteeMs(any());
|
||||
mJobConcurrencyManager = new JobConcurrencyManager(jobSchedulerService, mInjector);
|
||||
mGracePeriodObserver = mock(GracePeriodObserver.class);
|
||||
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
|
||||
@@ -167,32 +188,55 @@ public final class JobConcurrencyManagerTest {
|
||||
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
|
||||
mJobConcurrencyManager
|
||||
final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
|
||||
.prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
|
||||
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
|
||||
assertEquals(0, preferredUidOnly.size());
|
||||
assertEquals(0, stoppable.size());
|
||||
assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrepareForAssignmentDetermination_onlyPendingJobs() {
|
||||
final ArraySet<JobStatus> jobs = new ArraySet<>();
|
||||
for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
|
||||
JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
|
||||
mPendingJobQueue.add(job);
|
||||
jobs.add(job);
|
||||
}
|
||||
|
||||
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
|
||||
mJobConcurrencyManager
|
||||
final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
|
||||
.prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
|
||||
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
|
||||
assertEquals(0, preferredUidOnly.size());
|
||||
assertEquals(0, stoppable.size());
|
||||
assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrepareForAssignmentDetermination_onlyPreferredUidOnly() {
|
||||
for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
|
||||
JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
|
||||
mJobConcurrencyManager.addRunningJobForTesting(job);
|
||||
}
|
||||
|
||||
for (int i = 0; i < mInjector.contexts.size(); ++i) {
|
||||
doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
|
||||
}
|
||||
|
||||
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
|
||||
final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
|
||||
.prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
|
||||
|
||||
assertEquals(0, idle.size());
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
|
||||
assertEquals(0, stoppable.size());
|
||||
assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -216,7 +260,8 @@ public final class JobConcurrencyManagerTest {
|
||||
mJobConcurrencyManager
|
||||
.prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
|
||||
mJobConcurrencyManager
|
||||
.determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable);
|
||||
.determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
|
||||
Long.MAX_VALUE);
|
||||
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size());
|
||||
for (int i = changed.size() - 1; i >= 0; --i) {
|
||||
@@ -225,6 +270,169 @@ public final class JobConcurrencyManagerTest {
|
||||
assertTrue("Some jobs weren't assigned", jobs.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetermineAssignments_allPreferredUidOnly_shortTimeLeft() throws Exception {
|
||||
mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
|
||||
setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
|
||||
new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
|
||||
for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT * 2; ++i) {
|
||||
final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
|
||||
final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
|
||||
setPackageUid(sourcePkgName, uid);
|
||||
final JobStatus job = createJob(uid, sourcePkgName);
|
||||
spyOn(job);
|
||||
doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
|
||||
if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
|
||||
mJobConcurrencyManager.addRunningJobForTesting(job);
|
||||
} else {
|
||||
mPendingJobQueue.add(job);
|
||||
}
|
||||
}
|
||||
|
||||
// Waiting time is too short, so we shouldn't create any extra contexts.
|
||||
final long remainingTimeMs = JobConcurrencyManager.DEFAULT_MAX_WAIT_EJ_MS / 2;
|
||||
for (int i = 0; i < mInjector.contexts.size(); ++i) {
|
||||
doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
|
||||
doReturn(remainingTimeMs)
|
||||
.when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
|
||||
}
|
||||
|
||||
final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
|
||||
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
|
||||
|
||||
long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
|
||||
.prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
|
||||
assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
|
||||
|
||||
mJobConcurrencyManager
|
||||
.determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
|
||||
minPreferredUidOnlyWaitingTimeMs);
|
||||
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
|
||||
assertEquals(0, changed.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetermineAssignments_allPreferredUidOnly_mediumTimeLeft() throws Exception {
|
||||
mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
|
||||
setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
|
||||
new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
|
||||
final ArraySet<JobStatus> jobs = new ArraySet<>();
|
||||
for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT * 2; ++i) {
|
||||
final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
|
||||
final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
|
||||
setPackageUid(sourcePkgName, uid);
|
||||
final JobStatus job = createJob(uid, sourcePkgName);
|
||||
spyOn(job);
|
||||
doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
|
||||
if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
|
||||
mJobConcurrencyManager.addRunningJobForTesting(job);
|
||||
} else {
|
||||
mPendingJobQueue.add(job);
|
||||
jobs.add(job);
|
||||
}
|
||||
}
|
||||
|
||||
// Waiting time is longer than the EJ waiting time, but shorter than regular job waiting
|
||||
// time, so we should only create an extra context for an EJ.
|
||||
final long remainingTimeMs = (JobConcurrencyManager.DEFAULT_MAX_WAIT_EJ_MS
|
||||
+ JobConcurrencyManager.DEFAULT_MAX_WAIT_REGULAR_MS) / 2;
|
||||
for (int i = 0; i < mInjector.contexts.size(); ++i) {
|
||||
doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
|
||||
doReturn(remainingTimeMs)
|
||||
.when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
|
||||
}
|
||||
|
||||
final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
|
||||
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
|
||||
|
||||
long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
|
||||
.prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
|
||||
assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
|
||||
|
||||
mJobConcurrencyManager
|
||||
.determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
|
||||
minPreferredUidOnlyWaitingTimeMs);
|
||||
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
|
||||
for (int i = changed.size() - 1; i >= 0; --i) {
|
||||
jobs.remove(changed.valueAt(i).newJob);
|
||||
}
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT - 1, jobs.size());
|
||||
assertEquals(1, changed.size());
|
||||
JobStatus assignedJob = changed.valueAt(0).newJob;
|
||||
assertTrue(assignedJob.shouldTreatAsExpeditedJob());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetermineAssignments_allPreferredUidOnly_longTimeLeft() throws Exception {
|
||||
mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
|
||||
setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
|
||||
new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
|
||||
final ArraySet<JobStatus> jobs = new ArraySet<>();
|
||||
for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT * 2; ++i) {
|
||||
final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
|
||||
final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
|
||||
setPackageUid(sourcePkgName, uid);
|
||||
final JobStatus job = createJob(uid, sourcePkgName);
|
||||
spyOn(job);
|
||||
doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
|
||||
if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
|
||||
mJobConcurrencyManager.addRunningJobForTesting(job);
|
||||
} else {
|
||||
mPendingJobQueue.add(job);
|
||||
jobs.add(job);
|
||||
}
|
||||
}
|
||||
|
||||
// Waiting time is longer than even the regular job waiting time, so we should
|
||||
// create an extra context for an EJ, and potentially one for a regular job.
|
||||
final long remainingTimeMs = 2 * JobConcurrencyManager.DEFAULT_MAX_WAIT_REGULAR_MS;
|
||||
for (int i = 0; i < mInjector.contexts.size(); ++i) {
|
||||
doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
|
||||
doReturn(remainingTimeMs)
|
||||
.when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
|
||||
}
|
||||
|
||||
final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
|
||||
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
|
||||
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
|
||||
|
||||
long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
|
||||
.prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
|
||||
assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
|
||||
|
||||
mJobConcurrencyManager
|
||||
.determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
|
||||
minPreferredUidOnlyWaitingTimeMs);
|
||||
|
||||
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
|
||||
// Depending on iteration order, we may create 1 or 2 contexts.
|
||||
final long numAssignedJobs = changed.size();
|
||||
assertTrue(numAssignedJobs > 0);
|
||||
assertTrue(numAssignedJobs <= 2);
|
||||
for (int i = 0; i < numAssignedJobs; ++i) {
|
||||
jobs.remove(changed.valueAt(i).newJob);
|
||||
}
|
||||
assertEquals(numAssignedJobs,
|
||||
JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT - jobs.size());
|
||||
JobStatus firstAssignedJob = changed.valueAt(0).newJob;
|
||||
if (!firstAssignedJob.shouldTreatAsExpeditedJob()) {
|
||||
assertEquals(2, numAssignedJobs);
|
||||
assertTrue(changed.valueAt(1).newJob.shouldTreatAsExpeditedJob());
|
||||
} else if (numAssignedJobs == 2) {
|
||||
assertFalse(changed.valueAt(1).newJob.shouldTreatAsExpeditedJob());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsPkgConcurrencyLimited_top() {
|
||||
final JobStatus topJob = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
|
||||
|
||||
Reference in New Issue
Block a user