diff options
| -rw-r--r-- | apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java | 60 | ||||
| -rw-r--r-- | services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java | 113 |
2 files changed, 126 insertions, 47 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 53f5c6e64129..f9dd0b379cb2 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -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); @@ -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; @@ -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", @@ -703,7 +705,7 @@ class JobConcurrencyManager { determineAssignmentsLocked( mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable, - minPreferredUidOnlyWaitingTimeMs); + mRecycledAssignmentInfo); if (DEBUG) { Slog.d(TAG, printAssignments("running jobs final", @@ -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; @@ -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(); @@ -789,10 +795,11 @@ 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 - ? 0 : minPreferredUidOnlyWaitingTimeMs; + info.minPreferredUidOnlyWaitingTimeMs = + minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE + ? 0 : minPreferredUidOnlyWaitingTimeMs; } @VisibleForTesting @@ -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(); @@ -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 @@ -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(); @@ -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()) { @@ -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. @@ -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(); @@ -1093,6 +1095,7 @@ class JobConcurrencyManager { idle.clear(); stoppable.clear(); preferredUidOnly.clear(); + assignmentInfo.clear(); mWorkCountTracker.resetStagingCount(); mActivePkgStats.forEach(mPackageStatsStagingCountClearer); } @@ -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 diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java index 6279b87eb603..6e4d21456cd0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java @@ -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; @@ -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; @@ -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; @@ -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(); @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) { @@ -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()); @@ -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) { @@ -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. |