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

Commit c327d1e6 authored by Sanath Kumar's avatar Sanath Kumar
Browse files

Fix JobScheduler time constraint evaluation delays

Addresses an issue where jobs with time constraints (deadlines and
delays) might not be evaluated correctly in evaluateStateLocked.

The problem stems from the fact that the elapsed time tracking for the
next alarm (used for deadlines/delays) is not reset after an alarm
fires. This can lead to the JobScheduler not re-evaluating jobs that
have reached their deadline or delay, causing them to be delayed or even
missed.

The fix ensures that the elapsed time tracking is properly reset after a
deadline/delay alarm. This allows evaluateStateLocked to accurately
identify and process jobs that have met their time constraints.

Additionally, time-constraint jobs rely on a non-exact alarm timers.
This patch adds an extra check during evaluateStateLocked to detect any
delays in the alarm firing. This will reduce job start latency.

Bug: 400440145
Test: atest --rerun-until-failure 500 CtsJobSchedulerTestCases:android.jobscheduler.cts.TimingConstraintsTest
Test: atest CtsJobSchedulerTestCases
Flag: com.android.server.job.fix_deadline_delay_job_stall

Change-Id: I32e34a1844ad5da4fef0b338aacba0b4ce21d51e
parent ff2a8c13
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -127,3 +127,13 @@ flag {
    }
}

flag {
    name: "fix_deadline_delay_job_stall"
    namespace: "backstage_power"
    description: "Fix deadline and delay job stall due to a incorrect time check in TimeController"
    bug: "400440145"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+45 −5
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.expresslog.Counter;
import com.android.server.AppSchedulingModuleThread;
import com.android.server.job.Flags;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;

@@ -143,6 +144,26 @@ public final class TimeController extends StateController {
        }
    }

    /**
     * Check if the delay alarm has been delayed past the next scheduled time.
     * This can happen since we use a in-exact alarm for the delay.
     * Checking here avoids unnecessary delays in starting the job.
     */
    private boolean isDelayAlarmDelayed() {
        return Flags.fixDeadlineDelayJobStall()
            && sElapsedRealtimeClock.millis() >= mNextDelayExpiredElapsedMillis;
    }

    /**
     * Check if the deadline alarm has been delayed past the next scheduled time.
     * This can happen since we use a in-exact alarm for the deadline.
     * Checking here avoids unnecessary delays in starting the job.
     */
    private boolean isDeadlineAlarmDelayed() {
        return Flags.fixDeadlineDelayJobStall()
            && sElapsedRealtimeClock.millis() >= mNextJobExpiredElapsedMillis;
    }

    @Override
    public void evaluateStateLocked(JobStatus job) {
        final long nowElapsedMillis = sElapsedRealtimeClock.millis();
@@ -151,7 +172,8 @@ public final class TimeController extends StateController {
        // unnecessary processing of the timing delay.
        if (job.hasDeadlineConstraint()
                && !job.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)
                && job.getLatestRunTimeElapsed() <= mNextJobExpiredElapsedMillis) {
                && (job.getLatestRunTimeElapsed() <= mNextJobExpiredElapsedMillis
                        || isDeadlineAlarmDelayed())) {
            if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
                if (job.isReady()) {
                    // If the job still isn't ready, there's no point trying to rush the
@@ -169,7 +191,8 @@ public final class TimeController extends StateController {
        }
        if (job.hasTimingDelayConstraint()
                && !job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)
                && job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) {
                && (job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis
                        || isDelayAlarmDelayed())) {
            // Since this is just the delay, we don't need to rush the Scheduler to run the job
            // immediately if the constraint is satisfied here.
            if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
@@ -401,8 +424,16 @@ public final class TimeController extends StateController {
            if (DEBUG) {
                Slog.d(TAG, "Deadline-expired alarm fired");
            }

            if (Flags.fixDeadlineDelayJobStall()) {
                synchronized (mLock) {
                    mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
                    checkExpiredDeadlinesAndResetAlarm();
                }
            } else {
                checkExpiredDeadlinesAndResetAlarm();
            }
        }
    };

    private final OnAlarmListener mNextDelayExpiredListener = new OnAlarmListener() {
@@ -411,9 +442,18 @@ public final class TimeController extends StateController {
            if (DEBUG) {
                Slog.d(TAG, "Delay-expired alarm fired");
            }

            if (Flags.fixDeadlineDelayJobStall()) {
                synchronized (mLock) {
                    mLastFiredDelayExpiredElapsedMillis = sElapsedRealtimeClock.millis();
                    mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
                    checkExpiredDelaysAndResetAlarm();
                }
            } else {
                mLastFiredDelayExpiredElapsedMillis = sElapsedRealtimeClock.millis();
                checkExpiredDelaysAndResetAlarm();
            }
        }
    };

    @Override