From 1c99942dbb697c69a97b870d09367d7b45e25444 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Tue, 19 Jul 2022 17:44:43 +0000 Subject: [PATCH] Apply thermal throttling to long-running jobs. Don't let jobs continue past their minimum runtime limit if they would otherwise be throttled due to a restriction (such as thermals). Bug: 230807327 Test: atest FrameworksMockingServicesTests:ThermalStatusRestrictionTest Test: atest CtsJobSchedulerTestCases Change-Id: I3de5a5ca0d5f0f12a65936791dd6463e8320f5f8 --- .../server/job/JobConcurrencyManager.java | 50 +++++++++++++++++++ .../server/job/JobSchedulerService.java | 17 +++++++ .../android/server/job/JobServiceContext.java | 4 +- .../server/job/StateChangedListener.java | 11 ++++ .../ThermalStatusRestriction.java | 16 +++--- .../ThermalStatusRestrictionTest.java | 40 +++++++++++---- 6 files changed, 121 insertions(+), 17 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 28116a8df1809..586038427c8d3 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -542,6 +542,30 @@ class JobConcurrencyManager { return mRunningJobs.contains(job); } + /** + * Return {@code true} if the specified job has been executing for longer than the minimum + * execution guarantee. + */ + @GuardedBy("mLock") + boolean isJobLongRunningLocked(@NonNull JobStatus job) { + if (!mRunningJobs.contains(job)) { + return false; + } + + for (int i = mActiveServices.size() - 1; i >= 0; --i) { + final JobServiceContext jsc = mActiveServices.get(i); + final JobStatus jobStatus = jsc.getRunningJobLocked(); + + if (jobStatus == job) { + return !jsc.isWithinExecutionGuaranteeTime(); + } + } + + Slog.wtf(TAG, "Couldn't find long running job on a context"); + mRunningJobs.remove(job); + return false; + } + /** * Returns true if a job that is "similar" to the provided job is currently running. * "Similar" in this context means any job that the {@link JobStore} would consider equivalent @@ -989,6 +1013,26 @@ class JobConcurrencyManager { } } + /** + * Stops any jobs that have run for more than their minimum execution guarantee and are + * restricted by the given {@link JobRestriction}. + */ + @GuardedBy("mLock") + void maybeStopLongRunningJobsLocked(@NonNull JobRestriction restriction) { + for (int i = mActiveServices.size() - 1; i >= 0; --i) { + final JobServiceContext jsc = mActiveServices.get(i); + final JobStatus jobStatus = jsc.getRunningJobLocked(); + + if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime() + && restriction.isJobRestricted(jobStatus)) { + jsc.cancelExecutingJobLocked(restriction.getReason(), + restriction.getInternalReason(), + JobParameters.getInternalReasonCodeDescription( + restriction.getInternalReason())); + } + } + } + @GuardedBy("mLock") void stopNonReadyActiveJobsLocked() { for (int i = 0; i < mActiveServices.size(); i++) { @@ -1349,6 +1393,12 @@ class JobConcurrencyManager { if (mPowerManager.isDeviceIdleMode()) { return "deep doze"; } + final JobRestriction jobRestriction; + if ((jobRestriction = mService.checkIfRestricted(js)) != null) { + return "restriction:" + + JobParameters.getInternalReasonCodeDescription( + jobRestriction.getInternalReason()); + } // Update config in case memory usage has changed significantly. updateCounterConfigLocked(); 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 60afdc76d249a..f16d8cf1d6084 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1772,6 +1772,12 @@ public class JobSchedulerService extends com.android.server.SystemService return mConcurrencyManager.isJobRunningLocked(job); } + /** @see JobConcurrencyManager#isJobLongRunningLocked(JobStatus) */ + @GuardedBy("mLock") + public boolean isLongRunningLocked(JobStatus job) { + return mConcurrencyManager.isJobLongRunningLocked(job); + } + private void noteJobPending(JobStatus job) { mJobPackageTracker.notePending(job); } @@ -2049,6 +2055,17 @@ public class JobSchedulerService extends com.android.server.SystemService } } + @Override + public void onRestrictionStateChanged(@NonNull JobRestriction restriction, + boolean stopLongRunningJobs) { + mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); + if (stopLongRunningJobs) { + synchronized (mLock) { + mConcurrencyManager.maybeStopLongRunningJobsLocked(restriction); + } + } + } + @Override public void onRunJobNow(JobStatus jobStatus) { if (jobStatus == null) { 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 68801bcac4ba4..06fc6504cb17e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -734,10 +734,10 @@ public final class JobServiceContext implements ServiceConnection { @GuardedBy("mLock") private void doCancelLocked(@JobParameters.StopReason int stopReasonCode, int internalStopReasonCode, @Nullable String debugReason) { - if (mVerb == VERB_FINISHED) { + if (mVerb == VERB_FINISHED || mVerb == VERB_STOPPING) { if (DEBUG) { Slog.d(TAG, - "Trying to process cancel for torn-down context, ignoring."); + "Too late to process cancel for context (verb=" + mVerb + "), ignoring."); } return; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java index 1068bda975fb8..d7bd03025fde7 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java +++ b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.util.ArraySet; import com.android.server.job.controllers.JobStatus; +import com.android.server.job.restrictions.JobRestriction; import java.util.List; @@ -36,6 +37,16 @@ public interface StateChangedListener { */ void onControllerStateChanged(@Nullable ArraySet changedJobs); + /** + * Called by a {@link com.android.server.job.restrictions.JobRestriction} to notify the + * JobScheduler that it should check on the state of all jobs. + * + * @param stopLongRunningJobs Whether to stop any jobs that have run for more than their minimum + * execution guarantee and are restricted by the changed restriction + */ + void onRestrictionStateChanged(@NonNull JobRestriction restriction, + boolean stopLongRunningJobs); + /** * Called by the controller to notify the JobManager that regardless of the state of the task, * it must be run immediately. diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java index 40244e8cc3862..a007a69a29e34 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java +++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java @@ -73,9 +73,10 @@ public class ThermalStatusRestriction extends JobRestriction { // bucket (thus resulting in us beginning to enforce the tightest // restrictions). || (mThermalStatus < UPPER_THRESHOLD && status > UPPER_THRESHOLD); + final boolean increased = mThermalStatus < status; mThermalStatus = status; if (significantChange) { - mService.onControllerStateChanged(null); + mService.onRestrictionStateChanged(ThermalStatusRestriction.this, increased); } } }); @@ -89,16 +90,19 @@ public class ThermalStatusRestriction extends JobRestriction { final int priority = job.getEffectivePriority(); if (mThermalStatus >= HIGHER_PRIORITY_THRESHOLD) { // For moderate throttling, only let expedited jobs and high priority regular jobs that - // are already running run. + // haven't been running for long run. return !job.shouldTreatAsExpeditedJob() && !(priority == JobInfo.PRIORITY_HIGH - && mService.isCurrentlyRunningLocked(job)); + && mService.isCurrentlyRunningLocked(job) + && !mService.isLongRunningLocked(job)); } if (mThermalStatus >= LOW_PRIORITY_THRESHOLD) { // For light throttling, throttle all min priority jobs and all low priority jobs that - // aren't already running. - return (priority == JobInfo.PRIORITY_LOW && !mService.isCurrentlyRunningLocked(job)) - || priority == JobInfo.PRIORITY_MIN; + // aren't already running or have been running for long enough. + return priority == JobInfo.PRIORITY_MIN + || (priority == JobInfo.PRIORITY_LOW + && (!mService.isCurrentlyRunningLocked(job) + || mService.isLongRunningLocked(job))); } return false; } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java index ab08573356ffa..aa959167b7f1b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java @@ -33,6 +33,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -111,42 +113,46 @@ public class ThermalStatusRestrictionTest { InOrder inOrder = inOrder(mJobSchedulerService); mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_NONE); - inOrder.verify(mJobSchedulerService, never()).onControllerStateChanged(any()); + inOrder.verify(mJobSchedulerService, never()) + .onRestrictionStateChanged(any(), anyBoolean()); assertEquals(THERMAL_STATUS_NONE, mThermalStatusRestriction.getThermalStatus()); // Moving within LOW and UPPER thresholds mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_LIGHT); - inOrder.verify(mJobSchedulerService).onControllerStateChanged(any()); + inOrder.verify(mJobSchedulerService).onRestrictionStateChanged(any(), eq(true)); assertEquals(THERMAL_STATUS_LIGHT, mThermalStatusRestriction.getThermalStatus()); mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_MODERATE); - inOrder.verify(mJobSchedulerService).onControllerStateChanged(any()); + inOrder.verify(mJobSchedulerService).onRestrictionStateChanged(any(), eq(true)); assertEquals(THERMAL_STATUS_MODERATE, mThermalStatusRestriction.getThermalStatus()); mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SEVERE); - inOrder.verify(mJobSchedulerService).onControllerStateChanged(any()); + inOrder.verify(mJobSchedulerService).onRestrictionStateChanged(any(), eq(true)); assertEquals(THERMAL_STATUS_SEVERE, mThermalStatusRestriction.getThermalStatus()); // Changing outside of range mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_CRITICAL); - inOrder.verify(mJobSchedulerService, never()).onControllerStateChanged(any()); + inOrder.verify(mJobSchedulerService, never()) + .onRestrictionStateChanged(any(), eq(true)); assertEquals(THERMAL_STATUS_CRITICAL, mThermalStatusRestriction.getThermalStatus()); mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_EMERGENCY); - inOrder.verify(mJobSchedulerService, never()).onControllerStateChanged(any()); + inOrder.verify(mJobSchedulerService, never()) + .onRestrictionStateChanged(any(), anyBoolean()); assertEquals(THERMAL_STATUS_EMERGENCY, mThermalStatusRestriction.getThermalStatus()); mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SHUTDOWN); - inOrder.verify(mJobSchedulerService, never()).onControllerStateChanged(any()); + inOrder.verify(mJobSchedulerService, never()) + .onRestrictionStateChanged(any(), anyBoolean()); assertEquals(THERMAL_STATUS_SHUTDOWN, mThermalStatusRestriction.getThermalStatus()); // Cross values we care about mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_NONE); - inOrder.verify(mJobSchedulerService).onControllerStateChanged(any()); + inOrder.verify(mJobSchedulerService).onRestrictionStateChanged(any(), eq(false)); assertEquals(THERMAL_STATUS_NONE, mThermalStatusRestriction.getThermalStatus()); mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_EMERGENCY); - inOrder.verify(mJobSchedulerService).onControllerStateChanged(any()); + inOrder.verify(mJobSchedulerService).onRestrictionStateChanged(any(), eq(true)); assertEquals(THERMAL_STATUS_EMERGENCY, mThermalStatusRestriction.getThermalStatus()); } @@ -160,12 +166,16 @@ public class ThermalStatusRestrictionTest { createJobBuilder(2).setPriority(JobInfo.PRIORITY_LOW).build()); final JobStatus jobLowPriorityRunning = createJobStatus("testIsJobRestricted", createJobBuilder(3).setPriority(JobInfo.PRIORITY_LOW).build()); + final JobStatus jobLowPriorityRunningLong = createJobStatus("testIsJobRestricted", + createJobBuilder(9).setPriority(JobInfo.PRIORITY_LOW).build()); final JobStatus jobDefaultPriority = createJobStatus("testIsJobRestricted", createJobBuilder(4).setPriority(JobInfo.PRIORITY_DEFAULT).build()); final JobStatus jobHighPriority = createJobStatus("testIsJobRestricted", createJobBuilder(5).setPriority(JobInfo.PRIORITY_HIGH).build()); final JobStatus jobHighPriorityRunning = createJobStatus("testIsJobRestricted", createJobBuilder(6).setPriority(JobInfo.PRIORITY_HIGH).build()); + final JobStatus jobHighPriorityRunningLong = createJobStatus("testIsJobRestricted", + createJobBuilder(10).setPriority(JobInfo.PRIORITY_HIGH).build()); final JobStatus ejDowngraded = createJobStatus("testIsJobRestricted", createJobBuilder(7).setExpedited(true).build()); final JobStatus ej = spy(createJobStatus("testIsJobRestricted", @@ -174,13 +184,17 @@ public class ThermalStatusRestrictionTest { when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunning)).thenReturn(true); when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunning)) .thenReturn(true); + when(mJobSchedulerService.isLongRunningLocked(jobLowPriorityRunningLong)).thenReturn(true); + when(mJobSchedulerService.isLongRunningLocked(jobHighPriorityRunningLong)).thenReturn(true); assertFalse(mThermalStatusRestriction.isJobRestricted(jobMinPriority)); assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriority)); assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning)); + assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong)); assertFalse(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority)); assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriority)); assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning)); + assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong)); assertFalse(mThermalStatusRestriction.isJobRestricted(ej)); assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded)); @@ -189,9 +203,11 @@ public class ThermalStatusRestrictionTest { assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority)); assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning)); + assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong)); assertFalse(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority)); assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriority)); assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning)); + assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong)); assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded)); assertFalse(mThermalStatusRestriction.isJobRestricted(ej)); @@ -200,9 +216,11 @@ public class ThermalStatusRestrictionTest { assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning)); + assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority)); assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning)); + assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong)); assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded)); assertFalse(mThermalStatusRestriction.isJobRestricted(ej)); @@ -211,9 +229,11 @@ public class ThermalStatusRestrictionTest { assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning)); + assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning)); + assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong)); assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded)); assertTrue(mThermalStatusRestriction.isJobRestricted(ej)); @@ -222,9 +242,11 @@ public class ThermalStatusRestrictionTest { assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning)); + assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority)); assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning)); + assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong)); assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded)); assertTrue(mThermalStatusRestriction.isJobRestricted(ej)); }