Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 58f3e933 authored by Kweku Adams's avatar Kweku Adams
Browse files

Change context assignment mechanism.

Attempt to assign to empty contexts before trying to stop running jobs.

Bug: 141645789
Bug: 223437753
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job
Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job
Test: atest CtsJobSchedulerTestCases
Change-Id: Id6b0602547e13abd3d23a869a14857f07606a274
parent b0ea1d0b
Loading
Loading
Loading
Loading
+228 −124
Original line number Diff line number Diff line
@@ -274,23 +274,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();
@@ -379,7 +378,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()));
        }
@@ -583,12 +582,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.
@@ -599,20 +596,47 @@ class JobConcurrencyManager {
        // shouldStopRunningJobLocked().
        updateNonRunningPrioritiesLocked(pendingJobs, 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();

            ContextAssignment assignment = mContextAssignmentPool.acquire();
            if (assignment == null) {
                assignment = new ContextAssignment();
            }

            assignment.context = jsc;

            if (js != null) {
                mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
                assignment.workType = jsc.getRunningJobWorkType();
            }

            if ((contextIdToJobMap[i] = status) != null) {
                mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType());
                workTypeForContext[i] = js.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();
            }

            slotChanged[i] = false;
            preferredUidForContext[i] = js.getPreferredUid();
            preemptReasonForContext[i] = null;
            preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED;
            shouldStopJobReason[i] = shouldStopRunningJobLocked(js);
            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"));
@@ -627,83 +651,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);

            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.
                        selectedContextId = j;
                    selectedContext = assignment;
                    startingJob = true;
                        break;
                    idle.removeAt(idx);
                    assignment.newJob = nextPending;
                    assignment.newWorkType = workType;
                }
                    // 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()) {
            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) {
                    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.
                        preemptReason = reason;
                        preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
                            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;
                        }
                    }
                }
            }
            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(job);
                    final int jobBias = mService.evaluateJobBiasLocked(runningJob);
                    if (jobBias >= nextPending.lastEvaluatedBias) {
                        continue;
                    }

                if (minBiasForPreemption > jobBias) {
                    if (selectedContext == null || lowestBiasSeen > 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;
                        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.
                        // actually starting a job, so don't set startingJob to true.
                    }
                }
                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);
@@ -715,37 +753,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();
        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: "
                                + activeServices.get(i).getRunningJobLocked());
                    Slog.d(TAG, "preempting job: " + js);
                }
                // 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;
                assignment.context.cancelExecutingJobLocked(
                        assignment.preemptReasonCode,
                        JobParameters.INTERNAL_STOP_REASON_PREEMPT, assignment.preemptReason);
            } else {
                    final JobStatus pendingJob = contextIdToJobMap[i];
                final JobStatus pendingJob = assignment.newJob;
                if (DEBUG) {
                    Slog.d(TAG, "About to run job on context "
                                + i + ", job: " + pendingJob);
                            + assignment.context.getId() + ", job: " + pendingJob);
                }
                    startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]);
                startJobLocked(assignment.context, pendingJob, assignment.newWorkType);
            }

            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);
        }
            if (!preservePreferredUid) {
                activeServices.get(i).clearPreferredUid();
        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();
@@ -788,7 +841,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();

@@ -930,6 +983,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) {
@@ -1090,7 +1147,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
@@ -1198,6 +1255,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: ");
@@ -1322,10 +1387,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();
@@ -1334,7 +1402,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) {
@@ -1356,6 +1425,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) {
@@ -1984,4 +2066,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;
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -3959,7 +3959,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;
+4 −0
Original line number Diff line number Diff line
@@ -433,6 +433,10 @@ public final class JobServiceContext implements ServiceConnection {
        mPreferredUid = NO_PREFERRED_UID;
    }

    int getId() {
        return hashCode();
    }

    long getExecutionStartTimeElapsed() {
        return mExecutionStartTimeElapsed;
    }