JobScheduler: Move the concurrency calculation to a separate class
Bug: 111360323
Test: atest CtsJobSchedulerTestCases
Summary
-------
CtsJobSchedulerTestCases: Passed: 28, Failed: 5
5 tests failed
--------------
android.jobscheduler.cts.ConnectivityConstraintTest#testConnectivityConstraintExecutes_metered
android.jobscheduler.cts.ConnectivityConstraintTest#testConnectivityConstraintExecutes_withMobile
android.jobscheduler.cts.ConnectivityConstraintTest#testConnectivityConstraintExecutes_withWifi
android.jobscheduler.cts.ConnectivityConstraintTest#testUnmeteredConstraintExecutes_withWifi
android.jobscheduler.cts.ConnectivityConstraintTest#testUnmeteredConstraintFails_withMobile
* Connectivity test failures are expected.
Change-Id: I0cf7553dc846b310887da6ae48abd5c19d25b4b4
diff --git a/services/core/java/com/android/server/job/JobConcurrencyManager.java b/services/core/java/com/android/server/job/JobConcurrencyManager.java
new file mode 100644
index 0000000..ee1d847
--- /dev/null
+++ b/services/core/java/com/android/server/job/JobConcurrencyManager.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job;
+
+import android.app.ActivityManager;
+import android.app.job.JobInfo;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.app.procstats.ProcessStats;
+import com.android.server.job.controllers.JobStatus;
+import com.android.server.job.controllers.StateController;
+
+import java.util.Iterator;
+import java.util.List;
+
+class JobConcurrencyManager {
+ private static final String TAG = JobSchedulerService.TAG;
+ private static final boolean DEBUG = JobSchedulerService.DEBUG;
+
+ private final Object mLock;
+ private final JobSchedulerService mService;
+ private final JobSchedulerService.Constants mConstants;
+
+ private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
+
+ /**
+ * This array essentially stores the state of mActiveServices array.
+ * The ith index stores the job present on the ith JobServiceContext.
+ * We manipulate this array until we arrive at what jobs should be running on
+ * what JobServiceContext.
+ */
+ JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
+
+ /**
+ * Indicates whether we need to act on this jobContext id
+ */
+ boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT];
+
+ /**
+ * The uid whose jobs we would like to assign to a context.
+ */
+ int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
+
+ JobConcurrencyManager(JobSchedulerService service) {
+ mService = service;
+ mLock = mService.mLock;
+ mConstants = service.mConstants;
+ }
+
+ /**
+ * Takes jobs from pending queue and runs them on available contexts.
+ * If no contexts are available, preempts lower priority jobs to
+ * run higher priority ones.
+ * Lock on mJobs before calling this function.
+ */
+ void assignJobsToContextsLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, printPendingQueueLocked());
+ }
+
+ final JobPackageTracker tracker = mService.mJobPackageTracker;
+ final List<JobStatus> pendingJobs = mService.mPendingJobs;
+ final List<JobServiceContext> activeServices = mService.mActiveServices;
+ final List<StateController> controllers = mService.mControllers;
+
+ int memLevel;
+ try {
+ memLevel = ActivityManager.getService().getMemoryTrimLevel();
+ } catch (RemoteException e) {
+ memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+ }
+ switch (memLevel) {
+ case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
+ mService.mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_LOW:
+ mService.mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
+ mService.mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
+ break;
+ default:
+ mService.mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
+ break;
+ }
+
+ JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
+ boolean[] act = mTmpAssignAct;
+ int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
+ int numActive = 0;
+ int numForeground = 0;
+ for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
+ final JobServiceContext js = mService.mActiveServices.get(i);
+ final JobStatus status = js.getRunningJobLocked();
+ if ((contextIdToJobMap[i] = status) != null) {
+ numActive++;
+ if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+ numForeground++;
+ }
+ }
+ act[i] = false;
+ preferredUidForContext[i] = js.getPreferredUid();
+ }
+ if (DEBUG) {
+ Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
+ }
+ for (int i=0; i<pendingJobs.size(); i++) {
+ JobStatus nextPending = pendingJobs.get(i);
+
+ // If job is already running, go to next job.
+ int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
+ if (jobRunningContext != -1) {
+ continue;
+ }
+
+ final int priority = mService.evaluateJobPriorityLocked(nextPending);
+ nextPending.lastEvaluatedPriority = priority;
+
+ // Find a context for nextPending. The context should be available OR
+ // it should have lowest priority among all running jobs
+ // (sharing the same Uid as nextPending)
+ int minPriority = Integer.MAX_VALUE;
+ int minPriorityContextId = -1;
+ for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
+ JobStatus job = contextIdToJobMap[j];
+ int preferredUid = preferredUidForContext[j];
+ if (job == null) {
+ if ((numActive < mService.mMaxActiveJobs ||
+ (priority >= JobInfo.PRIORITY_TOP_APP &&
+ numForeground < mConstants.FG_JOB_COUNT)) &&
+ (preferredUid == nextPending.getUid() ||
+ preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
+ // This slot is free, and we haven't yet hit the limit on
+ // concurrent jobs... we can just throw the job in to here.
+ minPriorityContextId = j;
+ break;
+ }
+ // No job on this context, but nextPending can't run here because
+ // the context has a preferred Uid or we have reached the limit on
+ // concurrent jobs.
+ continue;
+ }
+ if (job.getUid() != nextPending.getUid()) {
+ continue;
+ }
+ if (mService.evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
+ continue;
+ }
+ if (minPriority > nextPending.lastEvaluatedPriority) {
+ minPriority = nextPending.lastEvaluatedPriority;
+ minPriorityContextId = j;
+ }
+ }
+ if (minPriorityContextId != -1) {
+ contextIdToJobMap[minPriorityContextId] = nextPending;
+ act[minPriorityContextId] = true;
+ numActive++;
+ if (priority >= JobInfo.PRIORITY_TOP_APP) {
+ numForeground++;
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
+ }
+ tracker.noteConcurrency(numActive, numForeground);
+ for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
+ boolean preservePreferredUid = false;
+ if (act[i]) {
+ JobStatus js = activeServices.get(i).getRunningJobLocked();
+ if (js != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "preempting job: "
+ + activeServices.get(i).getRunningJobLocked());
+ }
+ // preferredUid will be set to uid of currently running job.
+ activeServices.get(i).preemptExecutingJobLocked();
+ preservePreferredUid = true;
+ } else {
+ final JobStatus pendingJob = contextIdToJobMap[i];
+ if (DEBUG) {
+ Slog.d(TAG, "About to run job on context "
+ + String.valueOf(i) + ", job: " + pendingJob);
+ }
+ for (int ic=0; ic<controllers.size(); ic++) {
+ controllers.get(ic).prepareForExecutionLocked(pendingJob);
+ }
+ if (!activeServices.get(i).executeRunnableJob(pendingJob)) {
+ Slog.d(TAG, "Error executing " + pendingJob);
+ }
+ if (pendingJobs.remove(pendingJob)) {
+ tracker.noteNonpending(pendingJob);
+ }
+ }
+ }
+ if (!preservePreferredUid) {
+ activeServices.get(i).clearPreferredUid();
+ }
+ }
+ }
+
+ int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
+ for (int i=0; i<map.length; i++) {
+ if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private String printPendingQueueLocked() {
+ StringBuilder s = new StringBuilder("Pending queue: ");
+ Iterator<JobStatus> it = mService.mPendingJobs.iterator();
+ while (it.hasNext()) {
+ JobStatus js = it.next();
+ s.append("(")
+ .append(js.getJob().getId())
+ .append(", ")
+ .append(js.getUid())
+ .append(") ");
+ }
+ return s.toString();
+ }
+
+ private String printContextIdToJobMap(JobStatus[] map, String initial) {
+ StringBuilder s = new StringBuilder(initial + ": ");
+ for (int i=0; i<map.length; i++) {
+ s.append("(")
+ .append(map[i] == null? -1: map[i].getJobId())
+ .append(map[i] == null? -1: map[i].getUid())
+ .append(")" );
+ }
+ return s.toString();
+ }
+
+}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index ea295de..173f074 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -77,7 +77,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
-import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -111,7 +110,6 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -135,7 +133,7 @@
public static final boolean DEBUG_STANDBY = DEBUG || false;
/** The maximum number of concurrent jobs we run at one time. */
- private static final int MAX_JOB_CONTEXTS_COUNT = 16;
+ static final int MAX_JOB_CONTEXTS_COUNT = 16;
/** Enforce a per-app limit on scheduled jobs? */
private static final boolean ENFORCE_MAX_JOBS = true;
/** The maximum number of jobs that we allow an unprivileged app to schedule */
@@ -156,6 +154,7 @@
final StandbyTracker mStandbyTracker;
/** Tracking amount of time each package runs for. */
final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
+ final JobConcurrencyManager mConcurrencyManager;
static final int MSG_JOB_EXPIRED = 0;
static final int MSG_CHECK_JOB = 1;
@@ -173,7 +172,7 @@
final List<JobServiceContext> mActiveServices = new ArrayList<>();
/** List of controllers that will notify this service of updates to jobs. */
- private final List<StateController> mControllers;
+ final List<StateController> mControllers;
/** Need direct access to this for testing. */
private final BatteryController mBatteryController;
/** Need direct access to this for testing. */
@@ -268,22 +267,6 @@
// -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
- /**
- * This array essentially stores the state of mActiveServices array.
- * The ith index stores the job present on the ith JobServiceContext.
- * We manipulate this array until we arrive at what jobs should be running on
- * what JobServiceContext.
- */
- JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
- /**
- * Indicates whether we need to act on this jobContext id
- */
- boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT];
- /**
- * The uid whose jobs we would like to assign to a context.
- */
- int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
-
private class ConstantsObserver extends ContentObserver {
private ContentResolver mResolver;
@@ -1267,6 +1250,8 @@
mConstantsObserver = new ConstantsObserver(mHandler);
mJobSchedulerStub = new JobSchedulerStub();
+ mConcurrencyManager = new JobConcurrencyManager(this);
+
// Set up the app standby bucketing tracker
mStandbyTracker = new StandbyTracker();
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
@@ -2193,7 +2178,7 @@
if (DEBUG) {
Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
}
- assignJobsToContextsLocked();
+ mConcurrencyManager.assignJobsToContextsLocked();
reportActiveLocked();
}
@@ -2209,7 +2194,7 @@
return curPriority;
}
- private int evaluateJobPriorityLocked(JobStatus job) {
+ int evaluateJobPriorityLocked(JobStatus job) {
int priority = job.getPriority();
if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
return adjustJobPriority(priority, job);
@@ -2221,161 +2206,6 @@
return adjustJobPriority(priority, job);
}
- /**
- * Takes jobs from pending queue and runs them on available contexts.
- * If no contexts are available, preempts lower priority jobs to
- * run higher priority ones.
- * Lock on mJobs before calling this function.
- */
- private void assignJobsToContextsLocked() {
- if (DEBUG) {
- Slog.d(TAG, printPendingQueue());
- }
-
- int memLevel;
- try {
- memLevel = ActivityManager.getService().getMemoryTrimLevel();
- } catch (RemoteException e) {
- memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
- }
- switch (memLevel) {
- case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
- mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_LOW:
- mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
- mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
- break;
- default:
- mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
- break;
- }
-
- JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
- boolean[] act = mTmpAssignAct;
- int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
- int numActive = 0;
- int numForeground = 0;
- for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
- final JobServiceContext js = mActiveServices.get(i);
- final JobStatus status = js.getRunningJobLocked();
- if ((contextIdToJobMap[i] = status) != null) {
- numActive++;
- if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
- numForeground++;
- }
- }
- act[i] = false;
- preferredUidForContext[i] = js.getPreferredUid();
- }
- if (DEBUG) {
- Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
- }
- for (int i=0; i<mPendingJobs.size(); i++) {
- JobStatus nextPending = mPendingJobs.get(i);
-
- // If job is already running, go to next job.
- int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
- if (jobRunningContext != -1) {
- continue;
- }
-
- final int priority = evaluateJobPriorityLocked(nextPending);
- nextPending.lastEvaluatedPriority = priority;
-
- // Find a context for nextPending. The context should be available OR
- // it should have lowest priority among all running jobs
- // (sharing the same Uid as nextPending)
- int minPriority = Integer.MAX_VALUE;
- int minPriorityContextId = -1;
- for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
- JobStatus job = contextIdToJobMap[j];
- int preferredUid = preferredUidForContext[j];
- if (job == null) {
- if ((numActive < mMaxActiveJobs ||
- (priority >= JobInfo.PRIORITY_TOP_APP &&
- numForeground < mConstants.FG_JOB_COUNT)) &&
- (preferredUid == nextPending.getUid() ||
- preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
- // This slot is free, and we haven't yet hit the limit on
- // concurrent jobs... we can just throw the job in to here.
- minPriorityContextId = j;
- break;
- }
- // No job on this context, but nextPending can't run here because
- // the context has a preferred Uid or we have reached the limit on
- // concurrent jobs.
- continue;
- }
- if (job.getUid() != nextPending.getUid()) {
- continue;
- }
- if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
- continue;
- }
- if (minPriority > nextPending.lastEvaluatedPriority) {
- minPriority = nextPending.lastEvaluatedPriority;
- minPriorityContextId = j;
- }
- }
- if (minPriorityContextId != -1) {
- contextIdToJobMap[minPriorityContextId] = nextPending;
- act[minPriorityContextId] = true;
- numActive++;
- if (priority >= JobInfo.PRIORITY_TOP_APP) {
- numForeground++;
- }
- }
- }
- if (DEBUG) {
- Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
- }
- mJobPackageTracker.noteConcurrency(numActive, numForeground);
- for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
- boolean preservePreferredUid = false;
- if (act[i]) {
- JobStatus js = mActiveServices.get(i).getRunningJobLocked();
- if (js != null) {
- if (DEBUG) {
- Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJobLocked());
- }
- // preferredUid will be set to uid of currently running job.
- mActiveServices.get(i).preemptExecutingJobLocked();
- preservePreferredUid = true;
- } else {
- final JobStatus pendingJob = contextIdToJobMap[i];
- if (DEBUG) {
- Slog.d(TAG, "About to run job on context "
- + String.valueOf(i) + ", job: " + pendingJob);
- }
- for (int ic=0; ic<mControllers.size(); ic++) {
- mControllers.get(ic).prepareForExecutionLocked(pendingJob);
- }
- if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
- Slog.d(TAG, "Error executing " + pendingJob);
- }
- if (mPendingJobs.remove(pendingJob)) {
- mJobPackageTracker.noteNonpending(pendingJob);
- }
- }
- }
- if (!preservePreferredUid) {
- mActiveServices.get(i).clearPreferredUid();
- }
- }
- }
-
- int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
- for (int i=0; i<map.length; i++) {
- if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
- return i;
- }
- }
- return -1;
- }
-
final class LocalService implements JobSchedulerInternal {
/**
@@ -3150,31 +2980,6 @@
getContext().sendBroadcastAsUser(dockIntent, UserHandle.ALL);
}
- private String printContextIdToJobMap(JobStatus[] map, String initial) {
- StringBuilder s = new StringBuilder(initial + ": ");
- for (int i=0; i<map.length; i++) {
- s.append("(")
- .append(map[i] == null? -1: map[i].getJobId())
- .append(map[i] == null? -1: map[i].getUid())
- .append(")" );
- }
- return s.toString();
- }
-
- private String printPendingQueue() {
- StringBuilder s = new StringBuilder("Pending queue: ");
- Iterator<JobStatus> it = mPendingJobs.iterator();
- while (it.hasNext()) {
- JobStatus js = it.next();
- s.append("(")
- .append(js.getJob().getId())
- .append(", ")
- .append(js.getUid())
- .append(") ");
- }
- return s.toString();
- }
-
static void dumpHelp(PrintWriter pw) {
pw.println("Job Scheduler (jobscheduler) dump options:");
pw.println(" [-h] [package] ...");