diff options
| author | 2021-03-23 16:25:42 +0000 | |
|---|---|---|
| committer | 2021-03-23 16:25:42 +0000 | |
| commit | 31a695490000cf68d40acbc06c560b44d9c5ffe9 (patch) | |
| tree | d1e7828358e31b44cc41fc3fbf03aa8a289f17e2 | |
| parent | 934f10e67d7d45c966d58e805d90bf263e41607d (diff) | |
| parent | 5871539df1f82ad6ff91b9b0d6422d101d76b618 (diff) | |
Merge "Make sure out-of-quota EJs stop in time." into sc-dev
6 files changed, 82 insertions, 22 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 325be1b5c3a5..d94d638a7021 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -891,7 +891,7 @@ class JobConcurrencyManager { } // Only expedited jobs can replace expedited jobs. - if (js.shouldTreatAsExpeditedJob()) { + if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) { // Keep fg/bg user distinction. if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) { // Let any important bg user job replace a bg user expedited job. diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index a041f8c0b512..8ac237e63877 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -2187,6 +2187,10 @@ public class JobSchedulerService extends com.android.server.SystemService */ @VisibleForTesting boolean isReadyToBeExecutedLocked(JobStatus job) { + return isReadyToBeExecutedLocked(job, true); + } + + boolean isReadyToBeExecutedLocked(JobStatus job, boolean rejectActive) { final boolean jobReady = job.isReady(); if (DEBUG) { @@ -2225,7 +2229,7 @@ public class JobSchedulerService extends com.android.server.SystemService } final boolean jobPending = mPendingJobs.contains(job); - final boolean jobActive = isCurrentlyActiveLocked(job); + final boolean jobActive = rejectActive && isCurrentlyActiveLocked(job); if (DEBUG) { Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index e8bcbfbb2c58..790fae0860de 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -151,6 +151,14 @@ public final class JobServiceContext implements ServiceConnection { /** The absolute maximum amount of time the job can run */ private long mMaxExecutionTimeMillis; + /** + * The stop reason for a pending cancel. If there's not pending cancel, then the value should be + * {@link JobParameters#STOP_REASON_UNDEFINED}. + */ + private int mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; + private int mPendingLegacyStopReason; + private String mPendingDebugStopReason; + // Debugging: reason this job was last stopped. public String mStoppedReason; @@ -328,6 +336,7 @@ public final class JobServiceContext implements ServiceConnection { mAvailable = false; mStoppedReason = null; mStoppedTime = 0; + job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob(); return true; } } @@ -625,6 +634,19 @@ public final class JobServiceContext implements ServiceConnection { } return; } + if (mRunningJob.startedAsExpeditedJob + && stopReasonCode == JobParameters.STOP_REASON_QUOTA) { + // EJs should be able to run for at least the min upper limit regardless of quota. + final long earliestStopTimeElapsed = + mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis; + final long nowElapsed = sElapsedRealtimeClock.millis(); + if (nowElapsed < earliestStopTimeElapsed) { + mPendingStopReason = stopReasonCode; + mPendingLegacyStopReason = legacyStopReason; + mPendingDebugStopReason = debugReason; + return; + } + } mParams.setStopReason(stopReasonCode, legacyStopReason, debugReason); if (legacyStopReason == JobParameters.REASON_PREEMPT) { mPreferredUid = mRunningJob != null ? mRunningJob.getUid() : @@ -777,6 +799,23 @@ public final class JobServiceContext implements ServiceConnection { closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping"); break; case VERB_EXECUTING: + if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) { + if (mService.isReadyToBeExecutedLocked(mRunningJob, false)) { + // Job became ready again while we were waiting to stop it (for example, + // the device was temporarily taken off the charger). Ignore the pending + // stop and see what the manager says. + mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; + mPendingLegacyStopReason = 0; + mPendingDebugStopReason = null; + } else { + Slog.i(TAG, "JS was waiting to stop this job." + + " Sending onStop: " + getRunningJobNameLocked()); + mParams.setStopReason(mPendingStopReason, mPendingLegacyStopReason, + mPendingDebugStopReason); + sendStopMessageLocked(mPendingDebugStopReason); + break; + } + } final long latestStopTimeElapsed = mExecutionStartTimeElapsed + mMaxExecutionTimeMillis; final long nowElapsed = sElapsedRealtimeClock.millis(); @@ -886,6 +925,9 @@ public final class JobServiceContext implements ServiceConnection { mCancelled = false; service = null; mAvailable = true; + mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; + mPendingLegacyStopReason = 0; + mPendingDebugStopReason = null; removeOpTimeOutLocked(); mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule); mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType); @@ -972,7 +1014,16 @@ public final class JobServiceContext implements ServiceConnection { pw.print(", "); TimeUtils.formatDuration( (mExecutionStartTimeElapsed + mMaxExecutionTimeMillis) - nowElapsed, pw); - pw.println("]"); + pw.print("]"); + if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) { + pw.print(" Pending stop because "); + pw.print(mPendingStopReason); + pw.print("/"); + pw.print(mPendingLegacyStopReason); + pw.print("/"); + pw.print(mPendingDebugStopReason); + } + pw.println(); pw.decreaseIndent(); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 5e580064ee6d..8d999e1e7e36 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -328,6 +328,12 @@ public final class JobStatus { /** The evaluated priority of the job when it started running. */ public int lastEvaluatedPriority; + /** + * Whether or not this particular JobStatus instance was treated as an EJ when it started + * running. This isn't copied over when a job is rescheduled. + */ + public boolean startedAsExpeditedJob = false; + // If non-null, this is work that has been enqueued for the job. public ArrayList<JobWorkItem> pendingWork; @@ -1122,18 +1128,19 @@ public final class JobStatus { */ public boolean canRunInDoze() { return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 - || (shouldTreatAsExpeditedJob() + || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob) && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0); } boolean canRunInBatterySaver() { return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0 - || (shouldTreatAsExpeditedJob() + || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob) && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0); } boolean shouldIgnoreNetworkBlocking() { - return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || shouldTreatAsExpeditedJob(); + return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 + || (shouldTreatAsExpeditedJob() || startedAsExpeditedJob); } /** @return true if the constraint was changed, false otherwise. */ @@ -2032,7 +2039,10 @@ public final class JobStatus { pw.println(serviceInfo != null); if ((getFlags() & JobInfo.FLAG_EXPEDITED) != 0) { pw.print("readyWithinExpeditedQuota: "); - pw.println(mReadyWithinExpeditedQuota); + pw.print(mReadyWithinExpeditedQuota); + pw.print(" (started as EJ: "); + pw.print(startedAsExpeditedJob); + pw.println(")"); } pw.decreaseIndent(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 2b79969f4378..91189e4b6e74 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -849,10 +849,9 @@ public final class QuotaController extends StateController { return true; } // A job is within quota if one of the following is true: - // 1. it's already running (already executing expedited jobs should be allowed to finish) - // 2. the app is currently in the foreground - // 3. the app overall is within its quota - // 4. It's on the temp allowlist (or within the grace period) + // 1. the app is currently in the foreground + // 2. the app overall is within its quota + // 3. It's on the temp allowlist (or within the grace period) if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) { return true; } @@ -873,13 +872,6 @@ public final class QuotaController extends StateController { return true; } - Timer ejTimer = mEJPkgTimers.get(jobStatus.getSourceUserId(), - jobStatus.getSourcePackageName()); - // Any already executing expedited jobs should be allowed to finish. - if (ejTimer != null && ejTimer.isRunning(jobStatus)) { - return true; - } - return 0 < getRemainingEJExecutionTimeLocked( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); } @@ -4153,6 +4145,8 @@ public final class QuotaController extends StateController { pw.print(", "); if (js.shouldTreatAsExpeditedJob()) { pw.print("within EJ quota"); + } else if (js.startedAsExpeditedJob) { + pw.print("out of EJ quota"); } else if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) { pw.print("within regular quota"); } else { @@ -4163,6 +4157,8 @@ public final class QuotaController extends StateController { pw.print(getRemainingEJExecutionTimeLocked( js.getSourceUserId(), js.getSourcePackageName())); pw.print("ms remaining in EJ quota"); + } else if (js.startedAsExpeditedJob) { + pw.print("should be stopped after min execution time"); } else { pw.print(getRemainingExecutionTimeLocked(js)); pw.print("ms remaining in quota"); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index ee1a4f4b3578..4bab8e51874c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -5416,9 +5416,8 @@ public class QuotaControllerTest { inOrder.verify(mJobSchedulerService, timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(); - // Top and bg EJs should still be allowed to run since they started before the app ran - // out of quota. - assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); + // Top should still be "in quota" since it started before the app ran on top out of quota. + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertFalse( jobUnstarted.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); @@ -5467,7 +5466,7 @@ public class QuotaControllerTest { // wasn't started. Top should remain in quota since it started when the app was in TOP. assertTrue(jobTop2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); - assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); trackJobs(jobBg2); assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertFalse( |