Loading apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +37 −23 Original line number Diff line number Diff line Loading @@ -327,6 +327,7 @@ class JobConcurrencyManager { private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>(); private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>(); private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>(); private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo(); private final Pools.Pool<ContextAssignment> mContextAssignmentPool = new Pools.SimplePool<>(MAX_RETAINED_OBJECTS); Loading Loading @@ -414,7 +415,7 @@ class JobConcurrencyManager { @VisibleForTesting JobConcurrencyManager(JobSchedulerService service, Injector injector) { mService = service; mLock = mService.mLock; mLock = mService.getLock(); mContext = service.getTestableContext(); mInjector = injector; Loading Loading @@ -693,8 +694,9 @@ class JobConcurrencyManager { return; } final long minPreferredUidOnlyWaitingTimeMs = prepareForAssignmentDeterminationLocked( mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable); prepareForAssignmentDeterminationLocked( mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable, mRecycledAssignmentInfo); if (DEBUG) { Slog.d(TAG, printAssignments("running jobs initial", Loading @@ -703,7 +705,7 @@ class JobConcurrencyManager { determineAssignmentsLocked( mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable, minPreferredUidOnlyWaitingTimeMs); mRecycledAssignmentInfo); if (DEBUG) { Slog.d(TAG, printAssignments("running jobs final", Loading @@ -715,17 +717,18 @@ class JobConcurrencyManager { carryOutAssignmentChangesLocked(mRecycledChanged); cleanUpAfterAssignmentChangesLocked( mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable); mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable, mRecycledAssignmentInfo); noteConcurrency(); } /** @return the minimum remaining execution time for preferred UID only JobServiceContexts. */ @VisibleForTesting @GuardedBy("mLock") long prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle, void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable) { final List<ContextAssignment> stoppable, final AssignmentInfo info) { final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue(); final List<JobServiceContext> activeServices = mActiveServices; Loading Loading @@ -755,6 +758,9 @@ class JobConcurrencyManager { if (js != null) { mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType()); assignment.workType = jsc.getRunningJobWorkType(); if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { info.numRunningTopEj++; } } assignment.preferredUid = jsc.getPreferredUid(); Loading Loading @@ -789,9 +795,10 @@ class JobConcurrencyManager { } mWorkCountTracker.onCountDone(); // Return 0 if there were no preferred UID only contexts to indicate no waiting time due // Set 0 if there were no preferred UID only contexts to indicate no waiting time due // to such jobs. return minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE info.minPreferredUidOnlyWaitingTimeMs = minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE ? 0 : minPreferredUidOnlyWaitingTimeMs; } Loading @@ -801,7 +808,7 @@ class JobConcurrencyManager { final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable, long minPreferredUidOnlyWaitingTimeMs) { @NonNull AssignmentInfo info) { final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue(); final List<JobServiceContext> activeServices = mActiveServices; pendingJobQueue.resetIterator(); Loading Loading @@ -832,7 +839,7 @@ class JobConcurrencyManager { // 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); Math.min(info.minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs); // Find an available slot for nextPending. The context should be one of the following: // 1. Unused Loading Loading @@ -861,13 +868,6 @@ class JobConcurrencyManager { } } if (selectedContext == null && stoppable.size() > 0) { int topEjCount = 0; for (int r = mRunningJobs.size() - 1; r >= 0; --r) { JobStatus js = mRunningJobs.valueAt(r); if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { topEjCount++; } } for (int s = stoppable.size() - 1; s >= 0; --s) { final ContextAssignment assignment = stoppable.get(s); final JobStatus runningJob = assignment.context.getRunningJobLocked(); Loading @@ -888,7 +888,8 @@ class JobConcurrencyManager { final int currentJobBias = mService.evaluateJobBiasLocked(runningJob); canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3 || topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4 // Case 4 || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal(); } if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5 if (nextPending.shouldTreatAsExpeditedJob()) { Loading Loading @@ -955,7 +956,7 @@ class JobConcurrencyManager { if (selectedContext != null) { selectedContext.newJob = nextPending; preferredUidOnly.remove(selectedContext); minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs; info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs; } } // Make sure to run EJs for the TOP app immediately. Loading Loading @@ -1072,7 +1073,8 @@ class JobConcurrencyManager { private void cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed, final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable) { final List<ContextAssignment> stoppable, final AssignmentInfo assignmentInfo) { for (int s = stoppable.size() - 1; s >= 0; --s) { final ContextAssignment assignment = stoppable.get(s); assignment.clear(); Loading @@ -1093,6 +1095,7 @@ class JobConcurrencyManager { idle.clear(); stoppable.clear(); preferredUidOnly.clear(); assignmentInfo.clear(); mWorkCountTracker.resetStagingCount(); mActivePkgStats.forEach(mPackageStatsStagingCountClearer); } Loading Loading @@ -2540,6 +2543,17 @@ class JobConcurrencyManager { } } @VisibleForTesting static final class AssignmentInfo { public long minPreferredUidOnlyWaitingTimeMs; public int numRunningTopEj; void clear() { minPreferredUidOnlyWaitingTimeMs = 0; numRunningTopEj = 0; } } // TESTING HELPERS @VisibleForTesting Loading services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java +89 −24 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.job; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; Loading @@ -34,16 +35,20 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.IActivityManager; import android.app.job.JobInfo; import android.content.ComponentName; import android.content.Context; Loading @@ -51,6 +56,8 @@ import android.content.pm.IPackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArrayMap; Loading Loading @@ -149,12 +156,14 @@ public final class JobConcurrencyManagerTest { R.bool.config_jobSchedulerRestrictBackgroundUser); when(mContext.getResources()).thenReturn(mResources); doReturn(mContext).when(jobSchedulerService).getTestableContext(); doReturn(jobSchedulerService).when(jobSchedulerService).getLock(); mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER); doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock -> mConfigBuilder.build()) .when(() -> DeviceConfig.getProperties(eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER))); mPendingJobQueue = new PendingJobQueue(); doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue(); doReturn(mIPackageManager).when(AppGlobals::getPackageManager); doReturn(mock(PowerManager.class)).when(mContext).getSystemService(PowerManager.class); mInjector = new InjectorForTest(); doAnswer((Answer<Long>) invocationOnMock -> { Object[] args = invocationOnMock.getArguments(); Loading @@ -171,6 +180,16 @@ public final class JobConcurrencyManagerTest { createCurrentUser(true); mNextUserId = 10; mJobConcurrencyManager.mGracePeriodObserver = mGracePeriodObserver; IActivityManager activityManager = ActivityManager.getService(); spyOn(activityManager); try { doNothing().when(activityManager).registerUserSwitchObserver(any(), anyString()); } catch (RemoteException e) { fail("registerUserSwitchObserver threw exception: " + e.getMessage()); } mJobConcurrencyManager.onSystemReady(); } @After Loading @@ -188,13 +207,16 @@ 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<>(); final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size()); assertEquals(0, preferredUidOnly.size()); assertEquals(0, stoppable.size()); assertEquals(0, minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.numRunningTopEj); } @Test Loading @@ -207,13 +229,16 @@ 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<>(); final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size()); assertEquals(0, preferredUidOnly.size()); assertEquals(0, stoppable.size()); assertEquals(0, minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.numRunningTopEj); } @Test Loading @@ -230,13 +255,45 @@ 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<>(); final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(0, idle.size()); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); assertEquals(0, stoppable.size()); assertEquals(0, minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.numRunningTopEj); } @Test public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() { for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) { JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i); job.startedAsExpeditedJob = true; job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP; mJobConcurrencyManager.addRunningJobForTesting(job); } for (int i = 0; i < mInjector.contexts.size(); ++i) { doReturn(i % 2 == 0).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 JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(0, idle.size()); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, preferredUidOnly.size()); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size()); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, assignmentInfo.numRunningTopEj); } @Test Loading @@ -257,11 +314,13 @@ 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 .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); mJobConcurrencyManager .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable, Long.MAX_VALUE); assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size()); for (int i = changed.size() - 1; i >= 0; --i) { Loading Loading @@ -301,15 +360,17 @@ 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<>(); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); mJobConcurrencyManager .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable, minPreferredUidOnlyWaitingTimeMs); assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); assertEquals(0, changed.size()); Loading Loading @@ -350,15 +411,17 @@ 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<>(); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); mJobConcurrencyManager .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable, minPreferredUidOnlyWaitingTimeMs); assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); for (int i = changed.size() - 1; i >= 0; --i) { Loading Loading @@ -404,15 +467,17 @@ 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<>(); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); mJobConcurrencyManager .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable, minPreferredUidOnlyWaitingTimeMs); assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); // Depending on iteration order, we may create 1 or 2 contexts. Loading Loading
apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +37 −23 Original line number Diff line number Diff line Loading @@ -327,6 +327,7 @@ class JobConcurrencyManager { private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>(); private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>(); private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>(); private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo(); private final Pools.Pool<ContextAssignment> mContextAssignmentPool = new Pools.SimplePool<>(MAX_RETAINED_OBJECTS); Loading Loading @@ -414,7 +415,7 @@ class JobConcurrencyManager { @VisibleForTesting JobConcurrencyManager(JobSchedulerService service, Injector injector) { mService = service; mLock = mService.mLock; mLock = mService.getLock(); mContext = service.getTestableContext(); mInjector = injector; Loading Loading @@ -693,8 +694,9 @@ class JobConcurrencyManager { return; } final long minPreferredUidOnlyWaitingTimeMs = prepareForAssignmentDeterminationLocked( mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable); prepareForAssignmentDeterminationLocked( mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable, mRecycledAssignmentInfo); if (DEBUG) { Slog.d(TAG, printAssignments("running jobs initial", Loading @@ -703,7 +705,7 @@ class JobConcurrencyManager { determineAssignmentsLocked( mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable, minPreferredUidOnlyWaitingTimeMs); mRecycledAssignmentInfo); if (DEBUG) { Slog.d(TAG, printAssignments("running jobs final", Loading @@ -715,17 +717,18 @@ class JobConcurrencyManager { carryOutAssignmentChangesLocked(mRecycledChanged); cleanUpAfterAssignmentChangesLocked( mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable); mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable, mRecycledAssignmentInfo); noteConcurrency(); } /** @return the minimum remaining execution time for preferred UID only JobServiceContexts. */ @VisibleForTesting @GuardedBy("mLock") long prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle, void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable) { final List<ContextAssignment> stoppable, final AssignmentInfo info) { final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue(); final List<JobServiceContext> activeServices = mActiveServices; Loading Loading @@ -755,6 +758,9 @@ class JobConcurrencyManager { if (js != null) { mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType()); assignment.workType = jsc.getRunningJobWorkType(); if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { info.numRunningTopEj++; } } assignment.preferredUid = jsc.getPreferredUid(); Loading Loading @@ -789,9 +795,10 @@ class JobConcurrencyManager { } mWorkCountTracker.onCountDone(); // Return 0 if there were no preferred UID only contexts to indicate no waiting time due // Set 0 if there were no preferred UID only contexts to indicate no waiting time due // to such jobs. return minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE info.minPreferredUidOnlyWaitingTimeMs = minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE ? 0 : minPreferredUidOnlyWaitingTimeMs; } Loading @@ -801,7 +808,7 @@ class JobConcurrencyManager { final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable, long minPreferredUidOnlyWaitingTimeMs) { @NonNull AssignmentInfo info) { final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue(); final List<JobServiceContext> activeServices = mActiveServices; pendingJobQueue.resetIterator(); Loading Loading @@ -832,7 +839,7 @@ class JobConcurrencyManager { // 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); Math.min(info.minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs); // Find an available slot for nextPending. The context should be one of the following: // 1. Unused Loading Loading @@ -861,13 +868,6 @@ class JobConcurrencyManager { } } if (selectedContext == null && stoppable.size() > 0) { int topEjCount = 0; for (int r = mRunningJobs.size() - 1; r >= 0; --r) { JobStatus js = mRunningJobs.valueAt(r); if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { topEjCount++; } } for (int s = stoppable.size() - 1; s >= 0; --s) { final ContextAssignment assignment = stoppable.get(s); final JobStatus runningJob = assignment.context.getRunningJobLocked(); Loading @@ -888,7 +888,8 @@ class JobConcurrencyManager { final int currentJobBias = mService.evaluateJobBiasLocked(runningJob); canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3 || topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4 // Case 4 || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal(); } if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5 if (nextPending.shouldTreatAsExpeditedJob()) { Loading Loading @@ -955,7 +956,7 @@ class JobConcurrencyManager { if (selectedContext != null) { selectedContext.newJob = nextPending; preferredUidOnly.remove(selectedContext); minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs; info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs; } } // Make sure to run EJs for the TOP app immediately. Loading Loading @@ -1072,7 +1073,8 @@ class JobConcurrencyManager { private void cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed, final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable) { final List<ContextAssignment> stoppable, final AssignmentInfo assignmentInfo) { for (int s = stoppable.size() - 1; s >= 0; --s) { final ContextAssignment assignment = stoppable.get(s); assignment.clear(); Loading @@ -1093,6 +1095,7 @@ class JobConcurrencyManager { idle.clear(); stoppable.clear(); preferredUidOnly.clear(); assignmentInfo.clear(); mWorkCountTracker.resetStagingCount(); mActivePkgStats.forEach(mPackageStatsStagingCountClearer); } Loading Loading @@ -2540,6 +2543,17 @@ class JobConcurrencyManager { } } @VisibleForTesting static final class AssignmentInfo { public long minPreferredUidOnlyWaitingTimeMs; public int numRunningTopEj; void clear() { minPreferredUidOnlyWaitingTimeMs = 0; numRunningTopEj = 0; } } // TESTING HELPERS @VisibleForTesting Loading
services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java +89 −24 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.job; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; Loading @@ -34,16 +35,20 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.IActivityManager; import android.app.job.JobInfo; import android.content.ComponentName; import android.content.Context; Loading @@ -51,6 +56,8 @@ import android.content.pm.IPackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArrayMap; Loading Loading @@ -149,12 +156,14 @@ public final class JobConcurrencyManagerTest { R.bool.config_jobSchedulerRestrictBackgroundUser); when(mContext.getResources()).thenReturn(mResources); doReturn(mContext).when(jobSchedulerService).getTestableContext(); doReturn(jobSchedulerService).when(jobSchedulerService).getLock(); mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER); doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock -> mConfigBuilder.build()) .when(() -> DeviceConfig.getProperties(eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER))); mPendingJobQueue = new PendingJobQueue(); doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue(); doReturn(mIPackageManager).when(AppGlobals::getPackageManager); doReturn(mock(PowerManager.class)).when(mContext).getSystemService(PowerManager.class); mInjector = new InjectorForTest(); doAnswer((Answer<Long>) invocationOnMock -> { Object[] args = invocationOnMock.getArguments(); Loading @@ -171,6 +180,16 @@ public final class JobConcurrencyManagerTest { createCurrentUser(true); mNextUserId = 10; mJobConcurrencyManager.mGracePeriodObserver = mGracePeriodObserver; IActivityManager activityManager = ActivityManager.getService(); spyOn(activityManager); try { doNothing().when(activityManager).registerUserSwitchObserver(any(), anyString()); } catch (RemoteException e) { fail("registerUserSwitchObserver threw exception: " + e.getMessage()); } mJobConcurrencyManager.onSystemReady(); } @After Loading @@ -188,13 +207,16 @@ 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<>(); final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size()); assertEquals(0, preferredUidOnly.size()); assertEquals(0, stoppable.size()); assertEquals(0, minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.numRunningTopEj); } @Test Loading @@ -207,13 +229,16 @@ 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<>(); final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size()); assertEquals(0, preferredUidOnly.size()); assertEquals(0, stoppable.size()); assertEquals(0, minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.numRunningTopEj); } @Test Loading @@ -230,13 +255,45 @@ 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<>(); final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(0, idle.size()); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); assertEquals(0, stoppable.size()); assertEquals(0, minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(0, assignmentInfo.numRunningTopEj); } @Test public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() { for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) { JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i); job.startedAsExpeditedJob = true; job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP; mJobConcurrencyManager.addRunningJobForTesting(job); } for (int i = 0; i < mInjector.contexts.size(); ++i) { doReturn(i % 2 == 0).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 JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(0, idle.size()); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, preferredUidOnly.size()); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size()); assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, assignmentInfo.numRunningTopEj); } @Test Loading @@ -257,11 +314,13 @@ 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 .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); mJobConcurrencyManager .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable, Long.MAX_VALUE); assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size()); for (int i = changed.size() - 1; i >= 0; --i) { Loading Loading @@ -301,15 +360,17 @@ 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<>(); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); mJobConcurrencyManager .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable, minPreferredUidOnlyWaitingTimeMs); assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); assertEquals(0, changed.size()); Loading Loading @@ -350,15 +411,17 @@ 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<>(); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); mJobConcurrencyManager .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable, minPreferredUidOnlyWaitingTimeMs); assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); for (int i = changed.size() - 1; i >= 0; --i) { Loading Loading @@ -404,15 +467,17 @@ 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<>(); final JobConcurrencyManager.AssignmentInfo assignmentInfo = new JobConcurrencyManager.AssignmentInfo(); long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable); assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs); mJobConcurrencyManager.prepareForAssignmentDeterminationLocked( idle, preferredUidOnly, stoppable, assignmentInfo); assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); mJobConcurrencyManager .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable, minPreferredUidOnlyWaitingTimeMs); assignmentInfo); assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size()); // Depending on iteration order, we may create 1 or 2 contexts. Loading