Merge "Change context assignment mechanism." into tm-dev

This commit is contained in:
Kweku Adams
2022-03-24 14:35:10 +00:00
committed by Android (Google) Code Review
3 changed files with 231 additions and 123 deletions

View File

@@ -273,23 +273,22 @@ class JobConcurrencyManager {
*/
JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
boolean[] mRecycledSlotChanged = new boolean[MAX_JOB_CONTEXTS_COUNT];
private final ArraySet<ContextAssignment> mRecycledChanged = new ArraySet<>();
private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>();
private final ArraySet<ContextAssignment> mRecycledPreferredUidOnly = new ArraySet<>();
private final ArraySet<ContextAssignment> mRecycledStoppable = new ArraySet<>();
int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT];
int[] mRecycledPreemptReasonCodeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT];
private final Pools.Pool<ContextAssignment> mContextAssignmentPool =
new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT);
/**
* Set of JobServiceContexts that we use to run jobs.
* Set of JobServiceContexts that are actively running jobs.
*/
final List<JobServiceContext> mActiveServices = new ArrayList<>();
/** Set of JobServiceContexts that aren't currently running any jobs. */
final ArraySet<JobServiceContext> mIdleContexts = new ArraySet<>();
private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();
@@ -378,7 +377,7 @@ class JobConcurrencyManager {
final IBatteryStats batteryStats = IBatteryStats.Stub.asInterface(
ServiceManager.getService(BatteryStats.SERVICE_NAME));
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
mActiveServices.add(
mIdleContexts.add(
new JobServiceContext(mService, this, batteryStats,
mService.mJobPackageTracker, mContext.getMainLooper()));
}
@@ -582,12 +581,10 @@ class JobConcurrencyManager {
// To avoid GC churn, we recycle the arrays.
JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
boolean[] slotChanged = mRecycledSlotChanged;
int[] preferredUidForContext = mRecycledPreferredUidForContext;
int[] workTypeForContext = mRecycledWorkTypeForContext;
String[] preemptReasonForContext = mRecycledPreemptReasonForContext;
int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext;
String[] shouldStopJobReason = mRecycledShouldStopJobReason;
final ArraySet<ContextAssignment> changed = mRecycledChanged;
final ArraySet<ContextAssignment> idle = mRecycledIdle;
final ArraySet<ContextAssignment> preferredUidOnly = mRecycledPreferredUidOnly;
final ArraySet<ContextAssignment> stoppable = mRecycledStoppable;
updateCounterConfigLocked();
// Reset everything since we'll re-evaluate the current state.
@@ -598,20 +595,47 @@ class JobConcurrencyManager {
// shouldStopRunningJobLocked().
updateNonRunningPrioritiesLocked(pendingJobQueue, true);
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
final JobServiceContext js = activeServices.get(i);
final JobStatus status = js.getRunningJobLocked();
final int numRunningJobs = activeServices.size();
for (int i = 0; i < numRunningJobs; ++i) {
final JobServiceContext jsc = activeServices.get(i);
final JobStatus js = jsc.getRunningJobLocked();
if ((contextIdToJobMap[i] = status) != null) {
mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType());
workTypeForContext[i] = js.getRunningJobWorkType();
ContextAssignment assignment = mContextAssignmentPool.acquire();
if (assignment == null) {
assignment = new ContextAssignment();
}
slotChanged[i] = false;
preferredUidForContext[i] = js.getPreferredUid();
preemptReasonForContext[i] = null;
preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED;
shouldStopJobReason[i] = shouldStopRunningJobLocked(js);
assignment.context = jsc;
if (js != null) {
mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
assignment.workType = jsc.getRunningJobWorkType();
}
assignment.preferredUid = jsc.getPreferredUid();
if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) {
stoppable.add(assignment);
} else {
preferredUidOnly.add(assignment);
}
}
for (int i = numRunningJobs; i < MAX_JOB_CONTEXTS_COUNT; ++i) {
final JobServiceContext jsc;
final int numIdleContexts = mIdleContexts.size();
if (numIdleContexts > 0) {
jsc = mIdleContexts.removeAt(numIdleContexts - 1);
} else {
Slog.wtf(TAG, "Had fewer than " + MAX_JOB_CONTEXTS_COUNT + " in existence");
jsc = createNewJobServiceContext();
}
ContextAssignment assignment = mContextAssignmentPool.acquire();
if (assignment == null) {
assignment = new ContextAssignment();
}
assignment.context = jsc;
idle.add(assignment);
}
if (DEBUG) {
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
@@ -626,83 +650,97 @@ class JobConcurrencyManager {
continue;
}
// Find an available slot for nextPending. The context should be available OR
// it should have the lowest bias among all running jobs
// (sharing the same Uid as nextPending)
int minBiasForPreemption = Integer.MAX_VALUE;
int selectedContextId = -1;
int allWorkTypes = getJobWorkTypes(nextPending);
int workType = mWorkCountTracker.canJobStart(allWorkTypes);
boolean startingJob = false;
int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
String preemptReason = null;
// 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
// 3. Its job should have the lowest bias among all running jobs (sharing the same UID
// as nextPending)
ContextAssignment selectedContext = null;
final int allWorkTypes = getJobWorkTypes(nextPending);
final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
// TODO(141645789): rewrite this to look at empty contexts first so we don't
// unnecessarily preempt
for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
JobStatus job = contextIdToJobMap[j];
int preferredUid = preferredUidForContext[j];
if (job == null) {
final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
|| (preferredUid == JobServiceContext.NO_PREFERRED_UID);
if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) {
// This slot is free, and we haven't yet hit the limit on
// concurrent jobs... we can just throw the job in to here.
selectedContextId = j;
startingJob = true;
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;
boolean startingJob = false;
if (idle.size() > 0) {
final int idx = idle.size() - 1;
final ContextAssignment assignment = idle.valueAt(idx);
final boolean preferredUidOkay = (assignment.preferredUid == nextPending.getUid())
|| (assignment.preferredUid == JobServiceContext.NO_PREFERRED_UID);
int workType = mWorkCountTracker.canJobStart(allWorkTypes);
if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) {
// This slot is free, and we haven't yet hit the limit on
// concurrent jobs... we can just throw the job in to here.
selectedContext = assignment;
startingJob = true;
idle.removeAt(idx);
assignment.newJob = nextPending;
assignment.newWorkType = workType;
}
if (job.getUid() != nextPending.getUid()) {
}
if (selectedContext == null) {
for (int s = stoppable.size() - 1; s >= 0; --s) {
ContextAssignment assignment = stoppable.valueAt(s);
JobStatus runningJob = assignment.context.getRunningJobLocked();
// Maybe stop the job if it has had its day in the sun. Don't let a different
// app preempt jobs started for TOP apps though.
final String reason = shouldStopJobReason[j];
if (job.lastEvaluatedBias < JobInfo.BIAS_TOP_APP
&& reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) {
// Right now, the way the code is set up, we don't need to explicitly
// assign the new job to this context since we'll reassign when the
// preempted job finally stops.
preemptReason = reason;
preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
if (runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP
&& assignment.shouldStopJobReason != null) {
int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes,
assignment.context.getRunningJobWorkType());
if (replaceWorkType != WORK_TYPE_NONE) {
// Right now, the way the code is set up, we don't need to explicitly
// assign the new job to this context since we'll reassign when the
// preempted job finally stops.
assignment.preemptReason = assignment.shouldStopJobReason;
assignment.preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
selectedContext = assignment;
stoppable.removeAt(s);
assignment.newJob = nextPending;
assignment.newWorkType = replaceWorkType;
// Don't preserve the UID since we're stopping the job because
// something is pending (eg. EJs).
assignment.context.clearPreferredUid();
break;
}
}
continue;
}
}
if (selectedContext == null) {
int lowestBiasSeen = Integer.MAX_VALUE;
for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
final ContextAssignment assignment = preferredUidOnly.valueAt(p);
final JobStatus runningJob = assignment.context.getRunningJobLocked();
if (runningJob.getUid() != nextPending.getUid()) {
continue;
}
final int jobBias = mService.evaluateJobBiasLocked(runningJob);
if (jobBias >= nextPending.lastEvaluatedBias) {
continue;
}
final int jobBias = mService.evaluateJobBiasLocked(job);
if (jobBias >= nextPending.lastEvaluatedBias) {
continue;
if (selectedContext == null || lowestBiasSeen > jobBias) {
// Step down the preemption threshold - wind up replacing
// the lowest-bias running job
lowestBiasSeen = jobBias;
selectedContext = assignment;
assignment.preemptReason = "higher bias job found";
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.
}
}
if (minBiasForPreemption > jobBias) {
// Step down the preemption threshold - wind up replacing
// the lowest-bias running job
minBiasForPreemption = jobBias;
selectedContextId = j;
preemptReason = "higher bias job found";
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.
if (selectedContext != null) {
selectedContext.newJob = nextPending;
preferredUidOnly.remove(selectedContext);
}
}
final PackageStats packageStats = getPkgStatsLocked(
nextPending.getSourceUserId(), nextPending.getSourcePackageName());
if (selectedContextId != -1) {
contextIdToJobMap[selectedContextId] = nextPending;
slotChanged[selectedContextId] = true;
preemptReasonCodeForContext[selectedContextId] = preemptReasonCode;
preemptReasonForContext[selectedContextId] = preemptReason;
if (selectedContext != null) {
changed.add(selectedContext);
packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
}
if (startingJob) {
// Increase the counters when we're going to start a job.
workTypeForContext[selectedContextId] = workType;
mWorkCountTracker.stageJob(workType, allWorkTypes);
mWorkCountTracker.stageJob(selectedContext.newWorkType, allWorkTypes);
mActivePkgStats.add(
nextPending.getSourceUserId(), nextPending.getSourcePackageName(),
packageStats);
@@ -714,37 +752,52 @@ class JobConcurrencyManager {
Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
}
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
boolean preservePreferredUid = false;
if (slotChanged[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).cancelExecutingJobLocked(
preemptReasonCodeForContext[i],
JobParameters.INTERNAL_STOP_REASON_PREEMPT, preemptReasonForContext[i]);
// Only preserve the UID if we're preempting for the same UID. If we're stopping
// the job because something is pending (eg. EJs), then we shouldn't preserve
// the UID.
preservePreferredUid =
preemptReasonCodeForContext[i] == JobParameters.STOP_REASON_PREEMPT;
} else {
final JobStatus pendingJob = contextIdToJobMap[i];
if (DEBUG) {
Slog.d(TAG, "About to run job on context "
+ i + ", job: " + pendingJob);
}
startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]);
for (int c = changed.size() - 1; c >= 0; --c) {
final ContextAssignment assignment = changed.valueAt(c);
final JobStatus js = assignment.context.getRunningJobLocked();
if (js != null) {
if (DEBUG) {
Slog.d(TAG, "preempting job: " + js);
}
// preferredUid will be set to uid of currently running job.
assignment.context.cancelExecutingJobLocked(
assignment.preemptReasonCode,
JobParameters.INTERNAL_STOP_REASON_PREEMPT, assignment.preemptReason);
} else {
final JobStatus pendingJob = assignment.newJob;
if (DEBUG) {
Slog.d(TAG, "About to run job on context "
+ assignment.context.getId() + ", job: " + pendingJob);
}
startJobLocked(assignment.context, pendingJob, assignment.newWorkType);
}
if (!preservePreferredUid) {
activeServices.get(i).clearPreferredUid();
}
assignment.clear();
mContextAssignmentPool.release(assignment);
}
for (int s = stoppable.size() - 1; s >= 0; --s) {
final ContextAssignment assignment = stoppable.valueAt(s);
assignment.context.clearPreferredUid();
assignment.clear();
mContextAssignmentPool.release(assignment);
}
for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
final ContextAssignment assignment = preferredUidOnly.valueAt(p);
assignment.context.clearPreferredUid();
assignment.clear();
mContextAssignmentPool.release(assignment);
}
for (int i = idle.size() - 1; i >= 0; --i) {
final ContextAssignment assignment = idle.valueAt(i);
mIdleContexts.add(assignment.context);
assignment.context.clearPreferredUid();
assignment.clear();
mContextAssignmentPool.release(assignment);
}
changed.clear();
idle.clear();
stoppable.clear();
preferredUidOnly.clear();
mWorkCountTracker.resetStagingCount();
mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
noteConcurrency();
@@ -787,7 +840,7 @@ class JobConcurrencyManager {
@GuardedBy("mLock")
private void stopLongRunningJobsLocked(@NonNull String debugReason) {
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; ++i) {
for (int i = 0; i < mActiveServices.size(); ++i) {
final JobServiceContext jsc = mActiveServices.get(i);
final JobStatus jobStatus = jsc.getRunningJobLocked();
@@ -931,6 +984,8 @@ class JobConcurrencyManager {
}
} else {
mRunningJobs.add(jobStatus);
mActiveServices.add(worker);
mIdleContexts.remove(worker);
mWorkCountTracker.onJobStarted(workType);
packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob());
mActivePkgStats.add(
@@ -950,6 +1005,8 @@ class JobConcurrencyManager {
@WorkType final int workType) {
mWorkCountTracker.onJobFinished(workType);
mRunningJobs.remove(jobStatus);
mActiveServices.remove(worker);
mIdleContexts.add(worker);
final PackageStats packageStats =
mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
if (packageStats == null) {
@@ -1093,7 +1150,7 @@ class JobConcurrencyManager {
/**
* Returns {@code null} if the job can continue running and a non-null String if the job should
* be stopped. The non-null String details the reason for stopping the job. A job will generally
* be stopped if there similar job types waiting to be run and stopping this job would allow
* be stopped if there are similar job types waiting to be run and stopping this job would allow
* another job to run, or if system state suggests the job should stop.
*/
@Nullable
@@ -1202,6 +1259,14 @@ class JobConcurrencyManager {
return foundSome;
}
@NonNull
private JobServiceContext createNewJobServiceContext() {
return new JobServiceContext(mService, this,
IBatteryStats.Stub.asInterface(
ServiceManager.getService(BatteryStats.SERVICE_NAME)),
mService.mJobPackageTracker, mContext.getMainLooper());
}
@GuardedBy("mLock")
private String printPendingQueueLocked() {
StringBuilder s = new StringBuilder("Pending queue: ");
@@ -1327,10 +1392,13 @@ class JobConcurrencyManager {
}
@GuardedBy("mLock")
void dumpActiveJobsLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate,
void dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate,
long nowElapsed, long nowUptime) {
pw.println("Active jobs:");
pw.increaseIndent();
if (mActiveServices.size() == 0) {
pw.println("N/A");
}
for (int i = 0; i < mActiveServices.size(); i++) {
JobServiceContext jsc = mActiveServices.get(i);
final JobStatus job = jsc.getRunningJobLocked();
@@ -1339,7 +1407,8 @@ class JobConcurrencyManager {
continue;
}
pw.print("Slot #"); pw.print(i); pw.print(": ");
pw.print("Slot #"); pw.print(i);
pw.print("(ID="); pw.print(jsc.getId()); pw.print("): ");
jsc.dumpLocked(pw, nowElapsed);
if (job != null) {
@@ -1361,6 +1430,19 @@ class JobConcurrencyManager {
}
}
pw.decreaseIndent();
pw.println();
pw.print("Idle contexts (");
pw.print(mIdleContexts.size());
pw.println("):");
pw.increaseIndent();
for (int i = 0; i < mIdleContexts.size(); i++) {
JobServiceContext jsc = mIdleContexts.valueAt(i);
pw.print("ID="); pw.print(jsc.getId()); pw.print(": ");
jsc.dumpLocked(pw, nowElapsed);
}
pw.decreaseIndent();
}
public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
@@ -1989,4 +2071,26 @@ class JobConcurrencyManager {
pw.println("}");
}
}
private static final class ContextAssignment {
public JobServiceContext context;
public int preferredUid = JobServiceContext.NO_PREFERRED_UID;
public int workType = WORK_TYPE_NONE;
public String preemptReason;
public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
public String shouldStopJobReason;
public JobStatus newJob;
public int newWorkType = WORK_TYPE_NONE;
void clear() {
context = null;
preferredUid = JobServiceContext.NO_PREFERRED_UID;
workType = WORK_TYPE_NONE;
preemptReason = null;
preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
shouldStopJobReason = null;
newJob = null;
newWorkType = WORK_TYPE_NONE;
}
}
}

View File

@@ -3770,7 +3770,7 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.decreaseIndent();
pw.println();
mConcurrencyManager.dumpActiveJobsLocked(pw, predicate, nowElapsed, nowUptime);
mConcurrencyManager.dumpContextInfoLocked(pw, predicate, nowElapsed, nowUptime);
pw.println();
boolean recentPrinted = false;

View File

@@ -433,6 +433,10 @@ public final class JobServiceContext implements ServiceConnection {
mPreferredUid = NO_PREFERRED_UID;
}
int getId() {
return hashCode();
}
long getExecutionStartTimeElapsed() {
return mExecutionStartTimeElapsed;
}