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

Commit e60997b1 authored by Kweku Adams's avatar Kweku Adams Committed by Automerger Merge Worker
Browse files

Merge "Change context assignment mechanism." into tm-dev am: 4f027ec4

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17325245

Change-Id: Ifc1456049bdbc4e466849a59643130a17b76aeef
parents 0afea469 4f027ec4
Loading
Loading
Loading
Loading
+228 −124
Original line number Diff line number Diff line
@@ -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();

            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"));
@@ -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);

            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);
@@ -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();
        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();
@@ -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;
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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;
+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;
    }