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

Commit 1477559f authored by Kweku Adams's avatar Kweku Adams
Browse files

Introduce concept of work types.

In preparation for dedicated Contexts for expedited jobs, introduce the
concept of work types. For now, this is just a refactor, so we only have
TOP and BG types. The refactoring also helps make it clearer how many
contexts are reserved for each type, though the numbers aren't changed
as part of the refactor.

Bug: 141645789
Bug: 171305774
Bug: 178119369
Test: atest CtsJobSchedulerTestCases
Test: atest FrameworksMockingServicesTests:JobSchedulerServiceTest
Test: atest FrameworksServicesTests:PrioritySchedulingTest
Test: atest FrameworksServicesTests:WorkCountTrackerTest
Test: atest FrameworksServicesTests:WorkTypeConfigTest
Change-Id: Ib6682cb16ef3944e1439d865400f8d48bf1d6890
parent b3a28d9d
Loading
Loading
Loading
Loading
+434 −237

File changed.

Preview size limit exceeded, changes collapsed.

+0 −182
Original line number Diff line number Diff line
@@ -380,7 +380,6 @@ public class JobSchedulerService extends com.android.server.SystemService
                        default:
                            if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
                                    && !concurrencyUpdated) {
                                mConstants.updateConcurrencyConstantsLocked();
                                mConcurrencyManager.updateConfigLocked();
                                concurrencyUpdated = true;
                            } else {
@@ -408,119 +407,6 @@ public class JobSchedulerService extends com.android.server.SystemService
                mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
    }

    static class MaxJobCounts {
        private final int mTotalDefault;
        private final String mTotalKey;
        private final int mMaxBgDefault;
        private final String mMaxBgKey;
        private final int mMinBgDefault;
        private final String mMinBgKey;
        private int mTotal;
        private int mMaxBg;
        private int mMinBg;

        MaxJobCounts(int totalDefault, String totalKey,
                int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey) {
            mTotalKey = totalKey;
            mTotal = mTotalDefault = totalDefault;
            mMaxBgKey = maxBgKey;
            mMaxBg = mMaxBgDefault = maxBgDefault;
            mMinBgKey = minBgKey;
            mMinBg = mMinBgDefault = minBgDefault;
        }

        public void update() {
            mTotal = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    mTotalKey, mTotalDefault);
            mMaxBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    mMaxBgKey, mMaxBgDefault);
            mMinBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    mMinBgKey, mMinBgDefault);

            // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT].
            mTotal = Math.min(Math.max(1, mTotal), MAX_JOB_CONTEXTS_COUNT);

            // Ensure maxBg in the range [1, total].
            mMaxBg = Math.min(Math.max(1, mMaxBg), mTotal);

            // Ensure minBg in the range [0, min(maxBg, total - 1)]
            mMinBg = Math.min(Math.max(0, mMinBg), Math.min(mMaxBg, mTotal - 1));
        }

        /** Total number of jobs to run simultaneously. */
        public int getMaxTotal() {
            return mTotal;
        }

        /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
        public int getMaxBg() {
            return mMaxBg;
        }

        /**
         * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
         * pending, rather than always running the TOTAL number of FG jobs.
         */
        public int getMinBg() {
            return mMinBg;
        }

        public void dump(PrintWriter pw, String prefix) {
            pw.print(prefix);
            pw.print(mTotalKey);
            pw.print("=");
            pw.print(mTotal);
            pw.println();

            pw.print(prefix);
            pw.print(mMaxBgKey);
            pw.print("=");
            pw.print(mMaxBg);
            pw.println();

            pw.print(prefix);
            pw.print(mMinBgKey);
            pw.print("=");
            pw.print(mMinBg);
            pw.println();
        }

        public void dumpProto(ProtoOutputStream proto, long fieldId) {
            final long token = proto.start(fieldId);
            proto.write(MaxJobCountsProto.TOTAL_JOBS, mTotal);
            proto.write(MaxJobCountsProto.MAX_BG, mMaxBg);
            proto.write(MaxJobCountsProto.MIN_BG, mMinBg);
            proto.end(token);
        }
    }

    /** {@link MaxJobCounts} for each memory trim level. */
    static class MaxJobCountsPerMemoryTrimLevel {
        public final MaxJobCounts normal;
        public final MaxJobCounts moderate;
        public final MaxJobCounts low;
        public final MaxJobCounts critical;

        MaxJobCountsPerMemoryTrimLevel(
                MaxJobCounts normal,
                MaxJobCounts moderate, MaxJobCounts low,
                MaxJobCounts critical) {
            this.normal = normal;
            this.moderate = moderate;
            this.low = low;
            this.critical = critical;
        }

        public void dumpProto(ProtoOutputStream proto, long fieldId) {
            final long token = proto.start(fieldId);
            normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL);
            moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE);
            low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW);
            critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL);
            proto.end(token);
        }
    }

    /**
     * All times are in milliseconds. Any access to this class or its fields should be done while
     * holding the JobSchedulerService.mLock lock.
@@ -580,49 +466,6 @@ public class JobSchedulerService extends com.android.server.SystemService
         */
        float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;

        /** Prefix for all of the max_job constants. */
        private static final String KEY_PREFIX_MAX_JOB =
                JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY + "max_job_";

        // Max job counts for screen on / off, for each memory trim level.
        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
                new MaxJobCountsPerMemoryTrimLevel(
                        new MaxJobCounts(
                                8, KEY_PREFIX_MAX_JOB + "total_on_normal",
                                6, KEY_PREFIX_MAX_JOB + "max_bg_on_normal",
                                2, KEY_PREFIX_MAX_JOB + "min_bg_on_normal"),
                        new MaxJobCounts(
                                8, KEY_PREFIX_MAX_JOB + "total_on_moderate",
                                4, KEY_PREFIX_MAX_JOB + "max_bg_on_moderate",
                                2, KEY_PREFIX_MAX_JOB + "min_bg_on_moderate"),
                        new MaxJobCounts(
                                5, KEY_PREFIX_MAX_JOB + "total_on_low",
                                1, KEY_PREFIX_MAX_JOB + "max_bg_on_low",
                                1, KEY_PREFIX_MAX_JOB + "min_bg_on_low"),
                        new MaxJobCounts(
                                5, KEY_PREFIX_MAX_JOB + "total_on_critical",
                                1, KEY_PREFIX_MAX_JOB + "max_bg_on_critical",
                                1, KEY_PREFIX_MAX_JOB + "min_bg_on_critical"));

        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
                new MaxJobCountsPerMemoryTrimLevel(
                        new MaxJobCounts(
                                10, KEY_PREFIX_MAX_JOB + "total_off_normal",
                                6, KEY_PREFIX_MAX_JOB + "max_bg_off_normal",
                                2, KEY_PREFIX_MAX_JOB + "min_bg_off_normal"),
                        new MaxJobCounts(
                                10, KEY_PREFIX_MAX_JOB + "total_off_moderate",
                                4, KEY_PREFIX_MAX_JOB + "max_bg_off_moderate",
                                2, KEY_PREFIX_MAX_JOB + "min_bg_off_moderate"),
                        new MaxJobCounts(
                                5, KEY_PREFIX_MAX_JOB + "total_off_low",
                                1, KEY_PREFIX_MAX_JOB + "max_bg_off_low",
                                1, KEY_PREFIX_MAX_JOB + "min_bg_off_low"),
                        new MaxJobCounts(
                                5, KEY_PREFIX_MAX_JOB + "total_off_critical",
                                1, KEY_PREFIX_MAX_JOB + "max_bg_off_critical",
                                1, KEY_PREFIX_MAX_JOB + "min_bg_off_critical"));

        /**
         * The minimum backoff time to allow for linear backoff.
         */
@@ -686,18 +529,6 @@ public class JobSchedulerService extends com.android.server.SystemService
                    DEFAULT_MODERATE_USE_FACTOR);
        }

        void updateConcurrencyConstantsLocked() {
            MAX_JOB_COUNTS_SCREEN_ON.normal.update();
            MAX_JOB_COUNTS_SCREEN_ON.moderate.update();
            MAX_JOB_COUNTS_SCREEN_ON.low.update();
            MAX_JOB_COUNTS_SCREEN_ON.critical.update();

            MAX_JOB_COUNTS_SCREEN_OFF.normal.update();
            MAX_JOB_COUNTS_SCREEN_OFF.moderate.update();
            MAX_JOB_COUNTS_SCREEN_OFF.low.update();
            MAX_JOB_COUNTS_SCREEN_OFF.critical.update();
        }

        private void updateBackoffConstantsLocked() {
            MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    KEY_MIN_LINEAR_BACKOFF_TIME_MS,
@@ -747,16 +578,6 @@ public class JobSchedulerService extends com.android.server.SystemService
            pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
            pw.print(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();

            MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, "");

            MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");

            pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
            pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
            pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
@@ -781,9 +602,6 @@ public class JobSchedulerService extends com.android.server.SystemService
            proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
            proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);

            MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
            MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);

            proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS);
            proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS);
            proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
+20 −12
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.job;

import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

@@ -124,9 +125,12 @@ public final class JobServiceContext implements ServiceConnection {
     *
     * Any reads (dereferences) not done from the handler thread must be synchronized on
     * {@link #mLock}.
     * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
     * Writes can only be done from the handler thread,
     * or {@link #executeRunnableJob(JobStatus, int)}.
     */
    private JobStatus mRunningJob;
    @JobConcurrencyManager.WorkType
    private int mRunningJobWorkType;
    private JobCallback mRunningCallback;
    /** Used to store next job to run when current job is to be preempted. */
    private int mPreferredUid;
@@ -181,30 +185,26 @@ public final class JobServiceContext implements ServiceConnection {

    JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
            JobPackageTracker tracker, Looper looper) {
        this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
    }

    @VisibleForTesting
    JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
            JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
        mContext = context;
        mLock = lock;
        mContext = service.getContext();
        mLock = service.getLock();
        mBatteryStats = batteryStats;
        mJobPackageTracker = tracker;
        mCallbackHandler = new JobServiceHandler(looper);
        mCompletedListener = completedListener;
        mCompletedListener = service;
        mAvailable = true;
        mVerb = VERB_FINISHED;
        mPreferredUid = NO_PREFERRED_UID;
    }

    /**
     * Give a job to this context for execution. Callers must first check {@link #getRunningJobLocked()}
     * Give a job to this context for execution. Callers must first check {@link
     * #getRunningJobLocked()}
     * and ensure it is null to make sure this is a valid context.
     *
     * @param job The status of the job that we are going to run.
     * @return True if the job is valid and is running. False if the job cannot be executed.
     */
    boolean executeRunnableJob(JobStatus job) {
    boolean executeRunnableJob(JobStatus job, @JobConcurrencyManager.WorkType int workType) {
        synchronized (mLock) {
            if (!mAvailable) {
                Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
@@ -214,6 +214,7 @@ public final class JobServiceContext implements ServiceConnection {
            mPreferredUid = NO_PREFERRED_UID;

            mRunningJob = job;
            mRunningJobWorkType = workType;
            mRunningCallback = new JobCallback();
            final boolean isDeadlineExpired =
                    job.hasDeadlineConstraint() &&
@@ -282,6 +283,7 @@ public final class JobServiceContext implements ServiceConnection {
                    Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
                }
                mRunningJob = null;
                mRunningJobWorkType = WORK_TYPE_NONE;
                mRunningCallback = null;
                mParams = null;
                mExecutionStartTimeElapsed = 0L;
@@ -326,6 +328,11 @@ public final class JobServiceContext implements ServiceConnection {
        return mRunningJob;
    }

    @JobConcurrencyManager.WorkType
    int getRunningJobWorkType() {
        return mRunningJobWorkType;
    }

    /**
     * Used only for debugging. Will return <code>"&lt;null&gt;"</code> if there is no job running.
     */
@@ -831,6 +838,7 @@ public final class JobServiceContext implements ServiceConnection {
        mContext.unbindService(JobServiceContext.this);
        mWakeLock = null;
        mRunningJob = null;
        mRunningJobWorkType = WORK_TYPE_NONE;
        mRunningCallback = null;
        mParams = null;
        mVerb = VERB_FINISHED;
+1 −0
Original line number Diff line number Diff line
@@ -305,6 +305,7 @@ public final class JobStatus {
    public Network network;
    public ServiceInfo serviceInfo;

    /** The evaluated priority of the job when it started running. */
    public int lastEvaluatedPriority;

    // If non-null, this is work that has been enqueued for the job.
+44 −22
Original line number Diff line number Diff line
@@ -16,36 +16,42 @@

package com.android.server.job;

import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;

import static com.google.common.truth.Truth.assertThat;

import android.util.Log;
import android.util.Pair;

import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.job.JobConcurrencyManager.JobCountTracker;
import com.android.server.job.JobConcurrencyManager.WorkCountTracker;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.List;
import java.util.Random;

import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;

/**
 * Test for {@link com.android.server.job.JobConcurrencyManager.JobCountTracker}.
 * Test for {@link WorkCountTracker}.
 */
@RunWith(AndroidJUnit4.class)
@MediumTest
public class JobCountTrackerTest {
    private static final String TAG = "JobCountTrackerTest";
public class WorkCountTrackerTest {
    private static final String TAG = "WorkerCountTrackerTest";

    private Random mRandom;
    private JobCountTracker mJobCountTracker;
    private WorkCountTracker mWorkCountTracker;

    @Before
    public void setUp() {
        mRandom = new Random(1); // Always use the same series of pseudo random values.
        mJobCountTracker = new JobCountTracker();
        mWorkCountTracker = new WorkCountTracker();
    }

    /**
@@ -83,44 +89,57 @@ public class JobCountTrackerTest {


    private void startPendingJobs(Jobs jobs, int totalMax, int maxBg, int minBg) {
        mJobCountTracker.reset(totalMax, maxBg, minBg);
        mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig("critical",
                totalMax,
                // defaultMin
                List.of(Pair.create(WORK_TYPE_TOP, totalMax - maxBg),
                        Pair.create(WORK_TYPE_BG, minBg)),
                // defaultMax
                List.of(Pair.create(WORK_TYPE_BG, maxBg))));
        mWorkCountTracker.resetCounts();

        for (int i = 0; i < jobs.runningFg; i++) {
            mJobCountTracker.incrementRunningJobCount(true);
            mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_TOP);
        }
        for (int i = 0; i < jobs.runningBg; i++) {
            mJobCountTracker.incrementRunningJobCount(false);
            mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_BG);
        }

        for (int i = 0; i < jobs.pendingFg; i++) {
            mJobCountTracker.incrementPendingJobCount(true);
            mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_TOP);
        }
        for (int i = 0; i < jobs.pendingBg; i++) {
            mJobCountTracker.incrementPendingJobCount(false);
            mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_BG);
        }

        mJobCountTracker.onCountDone();
        mWorkCountTracker.onCountDone();

        while ((jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true))
                || (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false))) {
        while ((jobs.pendingFg > 0
                && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
                || (jobs.pendingBg > 0
                && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) {
            final boolean isStartingFg = mRandom.nextBoolean();

            if (isStartingFg) {
                if (jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true)) {
                if (jobs.pendingFg > 0
                        && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) {
                    jobs.pendingFg--;
                    jobs.runningFg++;
                    mJobCountTracker.onStartingNewJob(true);
                    mWorkCountTracker.stageJob(WORK_TYPE_TOP);
                    mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
                }
            } else {
                if (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false)) {
                if (jobs.pendingBg > 0
                        && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
                    jobs.pendingBg--;
                    jobs.runningBg++;
                    mJobCountTracker.onStartingNewJob(false);
                    mWorkCountTracker.stageJob(WORK_TYPE_BG);
                    mWorkCountTracker.onJobStarted(WORK_TYPE_BG);
                }
            }
        }

        Log.i(TAG, "" + mJobCountTracker);
        Log.i(TAG, "" + mWorkCountTracker);
    }

    /**
@@ -277,6 +296,7 @@ public class JobCountTrackerTest {

        startPendingJobs(jobs, totalMax, maxBg, minBg);

//        fail(mWorkerCountTracker.toString());
        assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
        assertThat(jobs.runningBg).isEqualTo(resultRunningBg);

@@ -300,6 +320,8 @@ public class JobCountTrackerTest {
        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 1, /*res run/pen=*/ 5, 1, 5, 0);
        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 4, 2, 6, 1);

        checkSimple(8, 6, 2, /*run=*/ 0, 0, /*pen=*/ 0, 49, /*res run/pen=*/ 0, 6, 0, 43);

        checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3);
    }
}
Loading