Merge "Make sure out-of-quota EJs stop in time." into sc-dev
This commit is contained in:
@@ -891,7 +891,7 @@ class JobConcurrencyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only expedited jobs can replace expedited jobs.
|
// Only expedited jobs can replace expedited jobs.
|
||||||
if (js.shouldTreatAsExpeditedJob()) {
|
if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) {
|
||||||
// Keep fg/bg user distinction.
|
// Keep fg/bg user distinction.
|
||||||
if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) {
|
if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) {
|
||||||
// Let any important bg user job replace a bg user expedited job.
|
// Let any important bg user job replace a bg user expedited job.
|
||||||
|
|||||||
@@ -2187,6 +2187,10 @@ public class JobSchedulerService extends com.android.server.SystemService
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
boolean isReadyToBeExecutedLocked(JobStatus job) {
|
boolean isReadyToBeExecutedLocked(JobStatus job) {
|
||||||
|
return isReadyToBeExecutedLocked(job, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isReadyToBeExecutedLocked(JobStatus job, boolean rejectActive) {
|
||||||
final boolean jobReady = job.isReady();
|
final boolean jobReady = job.isReady();
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -2225,7 +2229,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
|||||||
}
|
}
|
||||||
|
|
||||||
final boolean jobPending = mPendingJobs.contains(job);
|
final boolean jobPending = mPendingJobs.contains(job);
|
||||||
final boolean jobActive = isCurrentlyActiveLocked(job);
|
final boolean jobActive = rejectActive && isCurrentlyActiveLocked(job);
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
|
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
|
||||||
|
|||||||
@@ -151,6 +151,14 @@ public final class JobServiceContext implements ServiceConnection {
|
|||||||
/** The absolute maximum amount of time the job can run */
|
/** The absolute maximum amount of time the job can run */
|
||||||
private long mMaxExecutionTimeMillis;
|
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.
|
// Debugging: reason this job was last stopped.
|
||||||
public String mStoppedReason;
|
public String mStoppedReason;
|
||||||
|
|
||||||
@@ -328,6 +336,7 @@ public final class JobServiceContext implements ServiceConnection {
|
|||||||
mAvailable = false;
|
mAvailable = false;
|
||||||
mStoppedReason = null;
|
mStoppedReason = null;
|
||||||
mStoppedTime = 0;
|
mStoppedTime = 0;
|
||||||
|
job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -625,6 +634,19 @@ public final class JobServiceContext implements ServiceConnection {
|
|||||||
}
|
}
|
||||||
return;
|
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);
|
mParams.setStopReason(stopReasonCode, legacyStopReason, debugReason);
|
||||||
if (legacyStopReason == JobParameters.REASON_PREEMPT) {
|
if (legacyStopReason == JobParameters.REASON_PREEMPT) {
|
||||||
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
|
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
|
||||||
@@ -777,6 +799,23 @@ public final class JobServiceContext implements ServiceConnection {
|
|||||||
closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
|
closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
|
||||||
break;
|
break;
|
||||||
case VERB_EXECUTING:
|
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 =
|
final long latestStopTimeElapsed =
|
||||||
mExecutionStartTimeElapsed + mMaxExecutionTimeMillis;
|
mExecutionStartTimeElapsed + mMaxExecutionTimeMillis;
|
||||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||||
@@ -886,6 +925,9 @@ public final class JobServiceContext implements ServiceConnection {
|
|||||||
mCancelled = false;
|
mCancelled = false;
|
||||||
service = null;
|
service = null;
|
||||||
mAvailable = true;
|
mAvailable = true;
|
||||||
|
mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
|
||||||
|
mPendingLegacyStopReason = 0;
|
||||||
|
mPendingDebugStopReason = null;
|
||||||
removeOpTimeOutLocked();
|
removeOpTimeOutLocked();
|
||||||
mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule);
|
mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule);
|
||||||
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
|
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
|
||||||
@@ -972,7 +1014,16 @@ public final class JobServiceContext implements ServiceConnection {
|
|||||||
pw.print(", ");
|
pw.print(", ");
|
||||||
TimeUtils.formatDuration(
|
TimeUtils.formatDuration(
|
||||||
(mExecutionStartTimeElapsed + mMaxExecutionTimeMillis) - nowElapsed, pw);
|
(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();
|
pw.decreaseIndent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,6 +328,12 @@ public final class JobStatus {
|
|||||||
/** The evaluated priority of the job when it started running. */
|
/** The evaluated priority of the job when it started running. */
|
||||||
public int lastEvaluatedPriority;
|
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.
|
// If non-null, this is work that has been enqueued for the job.
|
||||||
public ArrayList<JobWorkItem> pendingWork;
|
public ArrayList<JobWorkItem> pendingWork;
|
||||||
|
|
||||||
@@ -1122,18 +1128,19 @@ public final class JobStatus {
|
|||||||
*/
|
*/
|
||||||
public boolean canRunInDoze() {
|
public boolean canRunInDoze() {
|
||||||
return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
|
return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
|
||||||
|| (shouldTreatAsExpeditedJob()
|
|| ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
|
||||||
&& (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
|
&& (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean canRunInBatterySaver() {
|
boolean canRunInBatterySaver() {
|
||||||
return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0
|
return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0
|
||||||
|| (shouldTreatAsExpeditedJob()
|
|| ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
|
||||||
&& (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
|
&& (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean shouldIgnoreNetworkBlocking() {
|
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. */
|
/** @return true if the constraint was changed, false otherwise. */
|
||||||
@@ -2032,7 +2039,10 @@ public final class JobStatus {
|
|||||||
pw.println(serviceInfo != null);
|
pw.println(serviceInfo != null);
|
||||||
if ((getFlags() & JobInfo.FLAG_EXPEDITED) != 0) {
|
if ((getFlags() & JobInfo.FLAG_EXPEDITED) != 0) {
|
||||||
pw.print("readyWithinExpeditedQuota: ");
|
pw.print("readyWithinExpeditedQuota: ");
|
||||||
pw.println(mReadyWithinExpeditedQuota);
|
pw.print(mReadyWithinExpeditedQuota);
|
||||||
|
pw.print(" (started as EJ: ");
|
||||||
|
pw.print(startedAsExpeditedJob);
|
||||||
|
pw.println(")");
|
||||||
}
|
}
|
||||||
pw.decreaseIndent();
|
pw.decreaseIndent();
|
||||||
|
|
||||||
|
|||||||
@@ -849,10 +849,9 @@ public final class QuotaController extends StateController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// A job is within quota if one of the following is 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)
|
// 1. the app is currently in the foreground
|
||||||
// 2. the app is currently in the foreground
|
// 2. the app overall is within its quota
|
||||||
// 3. the app overall is within its quota
|
// 3. It's on the temp allowlist (or within the grace period)
|
||||||
// 4. It's on the temp allowlist (or within the grace period)
|
|
||||||
if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) {
|
if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -873,13 +872,6 @@ public final class QuotaController extends StateController {
|
|||||||
return true;
|
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(
|
return 0 < getRemainingEJExecutionTimeLocked(
|
||||||
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
|
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
|
||||||
}
|
}
|
||||||
@@ -4153,6 +4145,8 @@ public final class QuotaController extends StateController {
|
|||||||
pw.print(", ");
|
pw.print(", ");
|
||||||
if (js.shouldTreatAsExpeditedJob()) {
|
if (js.shouldTreatAsExpeditedJob()) {
|
||||||
pw.print("within EJ quota");
|
pw.print("within EJ quota");
|
||||||
|
} else if (js.startedAsExpeditedJob) {
|
||||||
|
pw.print("out of EJ quota");
|
||||||
} else if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
|
} else if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
|
||||||
pw.print("within regular quota");
|
pw.print("within regular quota");
|
||||||
} else {
|
} else {
|
||||||
@@ -4163,6 +4157,8 @@ public final class QuotaController extends StateController {
|
|||||||
pw.print(getRemainingEJExecutionTimeLocked(
|
pw.print(getRemainingEJExecutionTimeLocked(
|
||||||
js.getSourceUserId(), js.getSourcePackageName()));
|
js.getSourceUserId(), js.getSourcePackageName()));
|
||||||
pw.print("ms remaining in EJ quota");
|
pw.print("ms remaining in EJ quota");
|
||||||
|
} else if (js.startedAsExpeditedJob) {
|
||||||
|
pw.print("should be stopped after min execution time");
|
||||||
} else {
|
} else {
|
||||||
pw.print(getRemainingExecutionTimeLocked(js));
|
pw.print(getRemainingExecutionTimeLocked(js));
|
||||||
pw.print("ms remaining in quota");
|
pw.print("ms remaining in quota");
|
||||||
|
|||||||
@@ -5416,9 +5416,8 @@ public class QuotaControllerTest {
|
|||||||
inOrder.verify(mJobSchedulerService,
|
inOrder.verify(mJobSchedulerService,
|
||||||
timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
|
timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
|
||||||
.onControllerStateChanged();
|
.onControllerStateChanged();
|
||||||
// Top and bg EJs should still be allowed to run since they started before the app ran
|
// Top should still be "in quota" since it started before the app ran on top out of quota.
|
||||||
// out of quota.
|
assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
|
||||||
assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
|
|
||||||
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
|
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
|
||||||
assertFalse(
|
assertFalse(
|
||||||
jobUnstarted.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
|
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.
|
// 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));
|
assertTrue(jobTop2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
|
||||||
assertFalse(jobFg.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);
|
trackJobs(jobBg2);
|
||||||
assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
|
assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA));
|
||||||
assertFalse(
|
assertFalse(
|
||||||
|
|||||||
Reference in New Issue
Block a user