diff options
author | Kweku Adams <kwekua@google.com> | 2023-11-17 19:06:59 +0000 |
---|---|---|
committer | Kweku Adams <kwekua@google.com> | 2023-12-01 21:16:49 +0000 |
commit | da434496c9455e51d4343050b238664e9c27e034 (patch) | |
tree | a04bdb37f12381d3b36707b17a0cef8f4146f869 /apex | |
parent | 0c7bac3f44a5699e79740231edf0e2940e3a5f87 (diff) | |
download | base-da434496c9455e51d4343050b238664e9c27e034.tar.gz |
Enforce minimum job time windows.
Set a minimum difference between a job's latency and deadline to give
the system time to optimize execution decisions.
Bug: 311402873
Bug: 297106511
Bug: 299329948
Bug: 236261941
Test: atest CtsJobSchedulerSharedUidTestCases
Test: atest CtsJobSchedulerTestCases
Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job
Test: atset frameworks/base/services/tests/servicestests/src/com/android/server/job
Change-Id: Ia6a7f1ddfdcef3fb7298543170b03ba679e0779c
Diffstat (limited to 'apex')
5 files changed, 98 insertions, 8 deletions
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig index f5e33a80211b..e73b434042af 100644 --- a/apex/jobscheduler/framework/aconfig/job.aconfig +++ b/apex/jobscheduler/framework/aconfig/job.aconfig @@ -1,6 +1,13 @@ package: "android.app.job" flag { + name: "enforce_minimum_time_windows" + namespace: "backstage_power" + description: "Enforce a minimum time window for job latencies & deadlines" + bug: "311402873" +} + +flag { name: "job_debug_info_apis" namespace: "backstage_power" description: "Add APIs to let apps attach debug information to jobs" diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 742ed5f2eeb7..4bc73130db29 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -23,6 +23,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.util.TimeUtils.formatDuration; import android.annotation.BytesLong; @@ -36,6 +37,7 @@ import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.compat.annotation.EnabledSince; +import android.compat.annotation.Overridable; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ComponentName; @@ -48,7 +50,9 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import android.os.Process; import android.os.Trace; +import android.os.UserHandle; import android.util.ArraySet; import android.util.Log; @@ -113,6 +117,16 @@ public class JobInfo implements Parcelable { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long REJECT_NEGATIVE_NETWORK_ESTIMATES = 253665015L; + /** + * Enforce a minimum time window between job latencies and deadlines. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Overridable // Aid in testing + public static final long ENFORCE_MINIMUM_TIME_WINDOWS = 311402873L; + /** @hide */ @IntDef(prefix = { "NETWORK_TYPE_" }, value = { NETWORK_TYPE_NONE, @@ -1866,10 +1880,40 @@ public class JobInfo implements Parcelable { * Set deadline which is the maximum scheduling latency. The job will be run by this * deadline even if other requirements (including a delay set through * {@link #setMinimumLatency(long)}) are not met. + * {@link JobParameters#isOverrideDeadlineExpired()} will return {@code true} if the job's + * deadline has passed. + * * <p> * Because it doesn't make sense setting this property on a periodic job, doing so will * throw an {@link java.lang.IllegalArgumentException} when * {@link android.app.job.JobInfo.Builder#build()} is called. + * + * <p class="note"> + * Since a job will run once the deadline has passed regardless of the status of other + * constraints, setting a deadline of 0 with other constraints makes those constraints + * meaningless when it comes to execution decisions. Avoid doing this. + * </p> + * + * <p> + * Short deadlines hinder the system's ability to optimize scheduling behavior and may + * result in running jobs at inopportune times. Therefore, starting in Android version + * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, minimum time windows will be + * enforced to help make it easier to better optimize job execution. Time windows are + * defined as the time between a job's {@link #setMinimumLatency(long) minimum latency} + * and its deadline. If the minimum latency is not set, it is assumed to be 0. + * The following minimums will be enforced: + * <ul> + * <li> + * Jobs with {@link #PRIORITY_DEFAULT} or higher priorities have a minimum time + * window of one hour. + * </li> + * <li>Jobs with {@link #PRIORITY_LOW} have a minimum time window of 6 hours.</li> + * <li>Jobs with {@link #PRIORITY_MIN} have a minimum time window of 12 hours.</li> + * </ul> + * + * Work that must happen immediately should use {@link #setExpedited(boolean)} or + * {@link #setUserInitiated(boolean)} in the appropriate manner. + * * @see JobInfo#getMaxExecutionDelayMillis() */ public Builder setOverrideDeadline(long maxExecutionDelayMillis) { @@ -2143,12 +2187,14 @@ public class JobInfo implements Parcelable { */ public JobInfo build() { return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS), - Compatibility.isChangeEnabled(REJECT_NEGATIVE_NETWORK_ESTIMATES)); + Compatibility.isChangeEnabled(REJECT_NEGATIVE_NETWORK_ESTIMATES), + Compatibility.isChangeEnabled(ENFORCE_MINIMUM_TIME_WINDOWS)); } /** @hide */ public JobInfo build(boolean disallowPrefetchDeadlines, - boolean rejectNegativeNetworkEstimates) { + boolean rejectNegativeNetworkEstimates, + boolean enforceMinimumTimeWindows) { // This check doesn't need to be inside enforceValidity. It's an unnecessary legacy // check that would ideally be phased out instead. if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { @@ -2157,7 +2203,8 @@ public class JobInfo implements Parcelable { " setRequiresDeviceIdle is an error."); } JobInfo jobInfo = new JobInfo(this); - jobInfo.enforceValidity(disallowPrefetchDeadlines, rejectNegativeNetworkEstimates); + jobInfo.enforceValidity(disallowPrefetchDeadlines, rejectNegativeNetworkEstimates, + enforceMinimumTimeWindows); return jobInfo; } @@ -2176,7 +2223,8 @@ public class JobInfo implements Parcelable { * @hide */ public final void enforceValidity(boolean disallowPrefetchDeadlines, - boolean rejectNegativeNetworkEstimates) { + boolean rejectNegativeNetworkEstimates, + boolean enforceMinimumTimeWindows) { // Check that network estimates require network type and are reasonable values. if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0) && networkRequest == null) { @@ -2291,6 +2339,39 @@ public class JobInfo implements Parcelable { throw new IllegalArgumentException("Invalid priority level provided: " + mPriority); } + if (enforceMinimumTimeWindows + && Flags.enforceMinimumTimeWindows() + // TODO(312197030): remove exemption for the system + && !UserHandle.isCore(Process.myUid()) + && hasLateConstraint && !isPeriodic) { + final long windowStart = hasEarlyConstraint ? minLatencyMillis : 0; + if (mPriority >= PRIORITY_DEFAULT) { + if (maxExecutionDelayMillis - windowStart < HOUR_IN_MILLIS) { + throw new IllegalArgumentException( + getPriorityString(mPriority) + + " cannot have a time window less than 1 hour." + + " Delay=" + windowStart + + ", deadline=" + maxExecutionDelayMillis); + } + } else if (mPriority >= PRIORITY_LOW) { + if (maxExecutionDelayMillis - windowStart < 6 * HOUR_IN_MILLIS) { + throw new IllegalArgumentException( + getPriorityString(mPriority) + + " cannot have a time window less than 6 hours." + + " Delay=" + windowStart + + ", deadline=" + maxExecutionDelayMillis); + } + } else { + if (maxExecutionDelayMillis - windowStart < 12 * HOUR_IN_MILLIS) { + throw new IllegalArgumentException( + getPriorityString(mPriority) + + " cannot have a time window less than 12 hours." + + " Delay=" + windowStart + + ", deadline=" + maxExecutionDelayMillis); + } + } + } + if (isExpedited) { if (hasEarlyConstraint) { throw new IllegalArgumentException("An expedited job cannot have a time delay"); 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 fecd2fd60b1d..f97100bf2e9b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -4388,7 +4388,7 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.w(TAG, "Uid " + uid + " set bias on its job"); return new JobInfo.Builder(job) .setBias(JobInfo.BIAS_DEFAULT) - .build(false, false); + .build(false, false, false); } } @@ -4410,7 +4410,9 @@ public class JobSchedulerService extends com.android.server.SystemService job.enforceValidity( CompatChanges.isChangeEnabled( JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid), - rejectNegativeNetworkEstimates); + rejectNegativeNetworkEstimates, + CompatChanges.isChangeEnabled( + JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS, callingUid)); if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index afcbddad611e..53b14d616ecc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -1495,7 +1495,7 @@ public final class JobStore { // return value), the deadline is dropped. Periodic jobs require all constraints // to be met, so there's no issue with their deadlines. // The same logic applies for other target SDK-based validation checks. - builtJob = jobBuilder.build(false, false); + builtJob = jobBuilder.build(false, false, false); } catch (Exception e) { Slog.w(TAG, "Unable to build job from XML, ignoring: " + jobBuilder.summarize(), e); return null; 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 13bea6bd1dd1..b74806494a60 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 @@ -636,7 +636,7 @@ public final class JobStatus { .build()); // Don't perform validation checks at this point since we've already passed the // initial validation check. - job = builder.build(false, false); + job = builder.build(false, false, false); } this.job = job; |