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

Commit 3e1011c9 authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

Merge "Add cumulative execution limit for UI jobs." into udc-dev

parents 92b1fa16 22d9f2c3
Loading
Loading
Loading
Loading
+27 −3
Original line number Diff line number Diff line
@@ -482,6 +482,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                        case Constants.KEY_RUNTIME_UI_LIMIT_MS:
                        case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
                        case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS:
                        case Constants.KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS:
                        case Constants.KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS:
                            if (!runtimeUpdated) {
                                mConstants.updateRuntimeConstantsLocked();
@@ -582,6 +583,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                "runtime_min_ui_data_transfer_guarantee_buffer_factor";
        private static final String KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
                "runtime_min_ui_data_transfer_guarantee_ms";
        private static final String KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS =
                "runtime_cumulative_ui_limit_ms";
        private static final String KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS =
                "runtime_use_data_estimates_for_limits";

@@ -622,6 +625,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                1.35f;
        public static final long DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
                Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS);
        public static final long DEFAULT_RUNTIME_CUMULATIVE_UI_LIMIT_MS = 24 * HOUR_IN_MILLIS;
        public static final boolean DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false;
        static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
        static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000;
@@ -759,6 +763,12 @@ public class JobSchedulerService extends com.android.server.SystemService
        public long RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
                DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;

        /**
         * The maximum amount of cumulative time we will let a user-initiated job run for
         * before downgrading it.
         */
        public long RUNTIME_CUMULATIVE_UI_LIMIT_MS = DEFAULT_RUNTIME_CUMULATIVE_UI_LIMIT_MS;

        /**
         * Whether to use data estimates to determine execution limits for execution limits.
         */
@@ -881,6 +891,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                    KEY_RUNTIME_MIN_UI_GUARANTEE_MS,
                    KEY_RUNTIME_UI_LIMIT_MS,
                    KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
                    KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS,
                    KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS);

            // Make sure min runtime for regular jobs is at least 10 minutes.
@@ -915,6 +926,11 @@ public class JobSchedulerService extends com.android.server.SystemService
                    properties.getLong(
                            KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
                            DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
            // The cumulative runtime limit should be at least the max execution limit.
            RUNTIME_CUMULATIVE_UI_LIMIT_MS = Math.max(RUNTIME_UI_LIMIT_MS,
                    properties.getLong(
                            KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS,
                            DEFAULT_RUNTIME_CUMULATIVE_UI_LIMIT_MS));

            RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = properties.getBoolean(
                    KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS,
@@ -972,6 +988,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                    RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
            pw.print(KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
                    RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS).println();
            pw.print(KEY_RUNTIME_CUMULATIVE_UI_LIMIT_MS, RUNTIME_CUMULATIVE_UI_LIMIT_MS).println();
            pw.print(KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS,
                    RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS).println();

@@ -2452,11 +2469,16 @@ public class JobSchedulerService extends com.android.server.SystemService
        JobStatus newJob = new JobStatus(failureToReschedule,
                elapsedNowMillis + delayMillis,
                JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
                failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
                failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis(),
                failureToReschedule.getCumulativeExecutionTimeMs());
        if (stopReason == JobParameters.STOP_REASON_USER) {
            // Demote all jobs to regular for user stops so they don't keep privileges.
            newJob.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
        }
        if (newJob.getCumulativeExecutionTimeMs() >= mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS
                && newJob.shouldTreatAsUserInitiatedJob()) {
            newJob.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
        }
        if (job.isPeriodic()) {
            newJob.setOriginalLatestRunTimeElapsed(
                    failureToReschedule.getOriginalLatestRunTimeElapsed());
@@ -2548,7 +2570,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                    elapsedNow + period - flex, elapsedNow + period,
                    0 /* numFailures */, 0 /* numSystemStops */,
                    sSystemClock.millis() /* lastSuccessfulRunTime */,
                    periodicToReschedule.getLastFailedRunTime());
                    periodicToReschedule.getLastFailedRunTime(),
                    0 /* Reset cumulativeExecutionTime because of successful execution */);
        }

        final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed
@@ -2563,7 +2586,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
                0 /* numFailures */, 0 /* numSystemStops */,
                sSystemClock.millis() /* lastSuccessfulRunTime */,
                periodicToReschedule.getLastFailedRunTime());
                periodicToReschedule.getLastFailedRunTime(),
                0 /* Reset cumulativeExecutionTime because of successful execution */);
    }

    // JobCompletedListener implementations.
+18 −2
Original line number Diff line number Diff line
@@ -106,6 +106,7 @@ public final class JobServiceContext implements ServiceConnection {
    private static final long OP_TIMEOUT_MILLIS = 8 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
    /** Amount of time the JobScheduler will wait for a job to provide a required notification. */
    private static final long NOTIFICATION_TIMEOUT_MILLIS = 10_000L * Build.HW_TIMEOUT_MULTIPLIER;
    private static final long EXECUTION_DURATION_STAMP_PERIOD_MILLIS = 5 * 60_000L;

    private static final String[] VERB_STRINGS = {
            "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED"
@@ -191,6 +192,8 @@ public final class JobServiceContext implements ServiceConnection {
    private long mMaxExecutionTimeMillis;
    /** Whether this job is required to provide a notification and we're still waiting for it. */
    private boolean mAwaitingNotification;
    /** The last time we updated the job's execution duration, in the elapsed realtime timebase. */
    private long mLastExecutionDurationStampTimeElapsed;

    private long mEstimatedDownloadBytes;
    private long mEstimatedUploadBytes;
@@ -1245,7 +1248,15 @@ public final class JobServiceContext implements ServiceConnection {
                            /* anrMessage */ "required notification not provided",
                            /* triggerAnr */ true);
                } else {
                    final long timeSinceDurationStampTimeMs =
                            nowElapsed - mLastExecutionDurationStampTimeElapsed;
                    if (timeSinceDurationStampTimeMs < EXECUTION_DURATION_STAMP_PERIOD_MILLIS) {
                        Slog.e(TAG, "Unexpected op timeout while EXECUTING");
                    }
                    // Update the execution time even if this wasn't the pre-set time.
                    mRunningJob.incrementCumulativeExecutionTime(timeSinceDurationStampTimeMs);
                    mService.mJobs.touchJob(mRunningJob);
                    mLastExecutionDurationStampTimeElapsed = nowElapsed;
                    scheduleOpTimeOutLocked();
                }
                break;
@@ -1314,8 +1325,11 @@ public final class JobServiceContext implements ServiceConnection {
            Slog.d(TAG, "Cleaning up " + mRunningJob.toShortString()
                    + " reschedule=" + reschedule + " reason=" + loggingDebugReason);
        }
        final long nowElapsed = sElapsedRealtimeClock.millis();
        applyStoppedReasonLocked(loggingDebugReason);
        completedJob = mRunningJob;
        completedJob.incrementCumulativeExecutionTime(
                nowElapsed - mLastExecutionDurationStampTimeElapsed);
        // Use the JobParameters stop reasons for logging and metric purposes,
        // but if the job was marked for death, use that reason for rescheduling purposes.
        // The discrepancy could happen if a job ends up stopping for some reason
@@ -1342,7 +1356,7 @@ public final class JobServiceContext implements ServiceConnection {
        mPreviousJobHadSuccessfulFinish =
                (loggingInternalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
        if (!mPreviousJobHadSuccessfulFinish) {
            mLastUnsuccessfulFinishElapsed = sElapsedRealtimeClock.millis();
            mLastUnsuccessfulFinishElapsed = nowElapsed;
        }
        mJobPackageTracker.noteInactive(completedJob,
                loggingInternalStopReason, loggingDebugReason);
@@ -1411,6 +1425,7 @@ public final class JobServiceContext implements ServiceConnection {
        mDeathMarkStopReason = JobParameters.STOP_REASON_UNDEFINED;
        mDeathMarkInternalStopReason = 0;
        mDeathMarkDebugReason = null;
        mLastExecutionDurationStampTimeElapsed = 0;
        mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
        mPendingInternalStopReason = 0;
        mPendingDebugStopReason = null;
@@ -1460,6 +1475,7 @@ public final class JobServiceContext implements ServiceConnection {
                if (mAwaitingNotification) {
                    minTimeout = Math.min(minTimeout, NOTIFICATION_TIMEOUT_MILLIS);
                }
                minTimeout = Math.min(minTimeout, EXECUTION_DURATION_STAMP_PERIOD_MILLIS);
                timeoutMillis = minTimeout;
                break;

+11 −3
Original line number Diff line number Diff line
@@ -236,7 +236,8 @@ public final class JobStore {
                        convertRtcBoundsToElapsed(utcTimes, elapsedNow);
                JobStatus newJob = new JobStatus(job,
                        elapsedRuntimes.first, elapsedRuntimes.second,
                        0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime());
                        0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime(),
                        job.getCumulativeExecutionTimeMs());
                newJob.prepareLocked();
                toAdd.add(newJob);
                toRemove.add(job);
@@ -786,7 +787,7 @@ public final class JobStore {
         * Write out a tag with data comprising the required fields and bias of this job and
         * its client.
         */
        private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
        private void addAttributesToJobTag(TypedXmlSerializer out, JobStatus jobStatus)
                throws IOException {
            out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId()));
            out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName());
@@ -813,6 +814,9 @@ public final class JobStore {
                    String.valueOf(jobStatus.getLastSuccessfulRunTime()));
            out.attribute(null, "lastFailedRunTime",
                    String.valueOf(jobStatus.getLastFailedRunTime()));

            out.attributeLong(null, "cumulativeExecutionTime",
                    jobStatus.getCumulativeExecutionTimeMs());
        }

        private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
@@ -1190,6 +1194,7 @@ public final class JobStore {
            int uid, sourceUserId;
            long lastSuccessfulRunTime;
            long lastFailedRunTime;
            long cumulativeExecutionTime;
            int internalFlags = 0;

            // Read out job identifier attributes and bias.
@@ -1230,6 +1235,9 @@ public final class JobStore {

                val = parser.getAttributeValue(null, "lastFailedRunTime");
                lastFailedRunTime = val == null ? 0 : Long.parseLong(val);

                cumulativeExecutionTime =
                        parser.getAttributeLong(null, "cumulativeExecutionTime", 0);
            } catch (NumberFormatException e) {
                Slog.e(TAG, "Error parsing job's required fields, skipping");
                return null;
@@ -1402,7 +1410,7 @@ public final class JobStore {
                    builtJob, uid, sourcePackageName, sourceUserId,
                    appBucket, namespace, sourceTag,
                    elapsedRuntimes.first, elapsedRuntimes.second,
                    lastSuccessfulRunTime, lastFailedRunTime,
                    lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTime,
                    (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0);
            if (jobWorkItems != null) {
                for (int i = 0; i < jobWorkItems.size(); ++i) {
+39 −5
Original line number Diff line number Diff line
@@ -375,6 +375,11 @@ public final class JobStatus {
     * and is thus considered demoted from whatever privileged state it had in the past.
     */
    public static final int INTERNAL_FLAG_DEMOTED_BY_USER = 1 << 1;
    /**
     * Flag for {@link #mInternalFlags}: this job is demoted by the system
     * from running as a user-initiated job.
     */
    public static final int INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ = 1 << 2;

    /** Minimum difference between start and end time to have flexible constraint */
    @VisibleForTesting
@@ -385,6 +390,12 @@ public final class JobStatus {
     */
    private int mInternalFlags;

    /**
     * The cumulative amount of time this job has run for, including previous executions.
     * This is reset for periodic jobs upon a successful job execution.
     */
    private long mCumulativeExecutionTimeMs;

    // These are filled in by controllers when preparing for execution.
    public ArraySet<Uri> changedUris;
    public ArraySet<String> changedAuthorities;
@@ -550,7 +561,8 @@ public final class JobStatus {
            int sourceUserId, int standbyBucket, @Nullable String namespace, String tag,
            int numFailures, int numSystemStops,
            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
            long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags,
            long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs,
            int internalFlags,
            int dynamicConstraints) {
        this.job = job;
        this.callingUid = callingUid;
@@ -650,6 +662,8 @@ public final class JobStatus {
            mReadyDynamicSatisfied = false;
        }

        mCumulativeExecutionTimeMs = cumulativeExecutionTimeMs;

        mLastSuccessfulRunTime = lastSuccessfulRunTime;
        mLastFailedRunTime = lastFailedRunTime;

@@ -684,6 +698,7 @@ public final class JobStatus {
                jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(),
                jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
                jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
                jobStatus.getCumulativeExecutionTimeMs(),
                jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints);
        mPersistedUtcTimes = jobStatus.mPersistedUtcTimes;
        if (jobStatus.mPersistedUtcTimes != null) {
@@ -711,13 +726,15 @@ public final class JobStatus {
            int standbyBucket, @Nullable String namespace, String sourceTag,
            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
            long lastSuccessfulRunTime, long lastFailedRunTime,
            long cumulativeExecutionTimeMs,
            Pair<Long, Long> persistedExecutionTimesUTC,
            int innerFlags, int dynamicConstraints) {
        this(job, callingUid, sourcePkgName, sourceUserId,
                standbyBucket, namespace,
                sourceTag, /* numFailures */ 0, /* numSystemStops */ 0,
                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints);
                lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs,
                innerFlags, dynamicConstraints);

        // Only during initial inflation do we record the UTC-timebase execution bounds
        // read from the persistent store.  If we ever have to recreate the JobStatus on
@@ -735,14 +752,16 @@ public final class JobStatus {
    public JobStatus(JobStatus rescheduling,
            long newEarliestRuntimeElapsedMillis,
            long newLatestRuntimeElapsedMillis, int numFailures, int numSystemStops,
            long lastSuccessfulRunTime, long lastFailedRunTime) {
            long lastSuccessfulRunTime, long lastFailedRunTime,
            long cumulativeExecutionTimeMs) {
        this(rescheduling.job, rescheduling.getUid(),
                rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
                rescheduling.getStandbyBucket(), rescheduling.getNamespace(),
                rescheduling.getSourceTag(), numFailures, numSystemStops,
                newEarliestRuntimeElapsedMillis,
                newLatestRuntimeElapsedMillis,
                lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(),
                lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs,
                rescheduling.getInternalFlags(),
                rescheduling.mDynamicConstraints);
    }

@@ -780,6 +799,7 @@ public final class JobStatus {
                standbyBucket, namespace, tag, /* numFailures */ 0, /* numSystemStops */ 0,
                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
                /* cumulativeExecutionTime */ 0,
                /*innerFlags=*/ 0, /* dynamicConstraints */ 0);
    }

@@ -1312,6 +1332,14 @@ public final class JobStatus {
        return job.isPersisted();
    }

    public long getCumulativeExecutionTimeMs() {
        return mCumulativeExecutionTimeMs;
    }

    public void incrementCumulativeExecutionTime(long incrementMs) {
        mCumulativeExecutionTimeMs += incrementMs;
    }

    public long getEarliestRunTime() {
        return earliestRunTimeElapsedMillis;
    }
@@ -1405,7 +1433,8 @@ public final class JobStatus {
     */
    public boolean shouldTreatAsUserInitiatedJob() {
        return getJob().isUserInitiated()
                && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) == 0;
                && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_USER) == 0
                && (getInternalFlags() & INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ) == 0;
    }

    /**
@@ -2655,6 +2684,11 @@ public final class JobStatus {
        pw.print(", original latest=");
        formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis, NO_LATEST_RUNTIME, nowElapsed);
        pw.println();
        if (mCumulativeExecutionTimeMs != 0) {
            pw.print("Cumulative execution time=");
            TimeUtils.formatDuration(mCumulativeExecutionTimeMs, pw);
            pw.println();
        }
        if (numFailures != 0) {
            pw.print("Num failures: "); pw.println(numFailures);
        }
+44 −0
Original line number Diff line number Diff line
@@ -337,6 +337,50 @@ public class JobSchedulerServiceTest {
                mService.getMaxJobExecutionTimeMs(jobUIDT));
    }

    /**
     * Confirm that
     * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
     * returns a job that is no longer allowed to run as a user-initiated job after it hits
     * the cumulative execution limit.
     */
    @Test
    public void testGetRescheduleJobForFailure_cumulativeExecution() {
        JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
                createJobInfo()
                        .setUserInitiated(true)
                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
        assertTrue(originalJob.shouldTreatAsUserInitiatedJob());

        // Cumulative time = 0
        JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
                JobParameters.STOP_REASON_UNDEFINED,
                JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
        assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());

        // Cumulative time = 50% of limit
        rescheduledJob.incrementCumulativeExecutionTime(
                mService.mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS / 2);
        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
                JobParameters.STOP_REASON_UNDEFINED,
                JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
        assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());

        // Cumulative time = 99.999999% of limit
        rescheduledJob.incrementCumulativeExecutionTime(
                mService.mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS / 2 - 1);
        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
                JobParameters.STOP_REASON_UNDEFINED,
                JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
        assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());

        // Cumulative time = 100+% of limit
        rescheduledJob.incrementCumulativeExecutionTime(2);
        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
                JobParameters.STOP_REASON_UNDEFINED,
                JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
        assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
    }

    /**
     * Confirm that
     * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
Loading