From 2856542813ca9b17689fcafcd8d46ab0eb1796bc Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Mon, 14 Mar 2022 18:48:43 +0000 Subject: [PATCH] Move JobServiceContext handling to the concurrency manager. Move handling of the set of JobServiceContexts to the concurrency manager to make it easier to make the list dynamic and flexible in the future. Bug: 141645789 Bug: 223437753 Test: atest CtsJobSchedulerTestCases Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job Change-Id: I9cf61131700321509800aef0867c911178f80383 --- .../server/job/JobConcurrencyManager.java | 162 ++++++++++++++- .../server/job/JobSchedulerService.java | 190 ++---------------- .../server/job/controllers/JobStatus.java | 5 - 3 files changed, 181 insertions(+), 176 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 18f63b7ad902e..23056b5670b96 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -17,6 +17,7 @@ package com.android.server.job; import static com.android.server.job.JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; +import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.annotation.IntDef; @@ -32,9 +33,11 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.UserInfo; +import android.os.BatteryStats; import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArraySet; @@ -51,19 +54,24 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.IBatteryStats; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.util.StatLogger; import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; import com.android.server.job.controllers.JobStatus; import com.android.server.job.controllers.StateController; +import com.android.server.job.restrictions.JobRestriction; import com.android.server.pm.UserManagerInternal; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; +import java.util.function.Predicate; /** * This class decides, given the various configuration and the system status, which jobs can start @@ -278,6 +286,11 @@ class JobConcurrencyManager { String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT]; + /** + * Set of JobServiceContexts that we use to run jobs. + */ + final List mActiveServices = new ArrayList<>(); + private final ArraySet mRunningJobs = new ArraySet<>(); private final WorkCountTracker mWorkCountTracker = new WorkCountTracker(); @@ -358,6 +371,20 @@ class JobConcurrencyManager { onInteractiveStateChanged(mPowerManager.isInteractive()); } + /** + * Called when the boot phase reaches + * {@link com.android.server.SystemService#PHASE_THIRD_PARTY_APPS_CAN_START}. + */ + void onThirdPartyAppsCanStart() { + final IBatteryStats batteryStats = IBatteryStats.Stub.asInterface( + ServiceManager.getService(BatteryStats.SERVICE_NAME)); + for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { + mActiveServices.add( + new JobServiceContext(mService, this, batteryStats, + mService.mJobPackageTracker, mContext.getMainLooper())); + } + } + @GuardedBy("mLock") void onAppRemovedLocked(String pkgName, int uid) { final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName); @@ -390,6 +417,7 @@ class JobConcurrencyManager { case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: if (mPowerManager != null && mPowerManager.isDeviceIdleMode()) { synchronized (mLock) { + stopUnexemptedJobsForDoze(); stopLongRunningJobsLocked("deep doze"); } } @@ -470,6 +498,11 @@ class JobConcurrencyManager { } } + @GuardedBy("mLock") + ArraySet getRunningJobsLocked() { + return mRunningJobs; + } + @GuardedBy("mLock") boolean isJobRunningLocked(JobStatus job) { return mRunningJobs.contains(job); @@ -546,7 +579,7 @@ class JobConcurrencyManager { } final List pendingJobs = mService.mPendingJobs; - final List activeServices = mService.mActiveServices; + final List activeServices = mActiveServices; // To avoid GC churn, we recycle the arrays. JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap; @@ -718,10 +751,45 @@ class JobConcurrencyManager { noteConcurrency(); } + @GuardedBy("mLock") + boolean stopJobOnServiceContextLocked(JobStatus job, + @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) { + if (!mRunningJobs.contains(job)) { + return false; + } + + for (int i = 0; i < mActiveServices.size(); i++) { + JobServiceContext jsc = mActiveServices.get(i); + final JobStatus executing = jsc.getRunningJobLocked(); + if (executing != null && executing.matches(job.getUid(), job.getJobId())) { + jsc.cancelExecutingJobLocked(reason, internalReasonCode, debugReason); + return true; + } + } + Slog.wtf(TAG, "Couldn't find running job on a context"); + mRunningJobs.remove(job); + return false; + } + + @GuardedBy("mLock") + private void stopUnexemptedJobsForDoze() { + // When becoming idle, make sure no jobs are actively running, + // except those using the idle exemption flag. + for (int i = 0; i < mActiveServices.size(); i++) { + JobServiceContext jsc = mActiveServices.get(i); + final JobStatus executing = jsc.getRunningJobLocked(); + if (executing != null && !executing.canRunInDoze()) { + jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE, + JobParameters.INTERNAL_STOP_REASON_DEVICE_IDLE, + "cancelled due to doze"); + } + } + } + @GuardedBy("mLock") private void stopLongRunningJobsLocked(@NonNull String debugReason) { for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; ++i) { - final JobServiceContext jsc = mService.mActiveServices.get(i); + final JobServiceContext jsc = mActiveServices.get(i); final JobStatus jobStatus = jsc.getRunningJobLocked(); if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) { @@ -731,6 +799,41 @@ class JobConcurrencyManager { } } + @GuardedBy("mLock") + void stopNonReadyActiveJobsLocked() { + for (int i = 0; i < mActiveServices.size(); i++) { + JobServiceContext serviceContext = mActiveServices.get(i); + final JobStatus running = serviceContext.getRunningJobLocked(); + if (running == null) { + continue; + } + if (!running.isReady()) { + if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX + && running.getStopReason() == JobParameters.STOP_REASON_APP_STANDBY) { + serviceContext.cancelExecutingJobLocked( + running.getStopReason(), + JobParameters.INTERNAL_STOP_REASON_RESTRICTED_BUCKET, + "cancelled due to restricted bucket"); + } else { + serviceContext.cancelExecutingJobLocked( + running.getStopReason(), + JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED, + "cancelled due to unsatisfied constraints"); + } + } else { + final JobRestriction restriction = mService.checkIfRestricted(running); + if (restriction != null) { + final int internalReasonCode = restriction.getInternalReason(); + serviceContext.cancelExecutingJobLocked(restriction.getReason(), + internalReasonCode, + "restricted due to " + + JobParameters.getInternalReasonCodeDescription( + internalReasonCode)); + } + } + } + } + private void noteConcurrency() { mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(), // TODO: log per type instead of only TOP @@ -1077,6 +1180,24 @@ class JobConcurrencyManager { return null; } + @GuardedBy("mLock") + boolean executeTimeoutCommandLocked(PrintWriter pw, String pkgName, int userId, + boolean hasJobId, int jobId) { + boolean foundSome = false; + for (int i = 0; i < mActiveServices.size(); i++) { + final JobServiceContext jc = mActiveServices.get(i); + final JobStatus js = jc.getRunningJobLocked(); + if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) { + foundSome = true; + pw.print("Timing out: "); + js.printUniqueId(pw); + pw.print(" "); + pw.println(js.getServiceComponent().flattenToShortString()); + } + } + return foundSome; + } + @GuardedBy("mLock") private String printPendingQueueLocked() { StringBuilder s = new StringBuilder("Pending queue: "); @@ -1200,6 +1321,43 @@ class JobConcurrencyManager { } } + @GuardedBy("mLock") + void dumpActiveJobsLocked(IndentingPrintWriter pw, Predicate predicate, + long nowElapsed, long nowUptime) { + pw.println("Active jobs:"); + pw.increaseIndent(); + for (int i = 0; i < mActiveServices.size(); i++) { + JobServiceContext jsc = mActiveServices.get(i); + final JobStatus job = jsc.getRunningJobLocked(); + + if (job != null && !predicate.test(job)) { + continue; + } + + pw.print("Slot #"); pw.print(i); pw.print(": "); + jsc.dumpLocked(pw, nowElapsed); + + if (job != null) { + pw.increaseIndent(); + + pw.increaseIndent(); + job.dump(pw, false, nowElapsed); + pw.decreaseIndent(); + + pw.print("Evaluated bias: "); + pw.println(JobInfo.getBiasString(job.lastEvaluatedBias)); + + pw.print("Active at "); + TimeUtils.formatDuration(job.madeActive - nowUptime, pw); + pw.print(", pending for "); + TimeUtils.formatDuration(job.madeActive - job.madePending, pw); + pw.decreaseIndent(); + pw.println(); + } + } + pw.decreaseIndent(); + } + public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) { final long token = proto.start(tag); 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 b9362789c6c68..3d74bc98ad324 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -57,7 +57,6 @@ import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.BatteryManager; import android.os.BatteryManagerInternal; -import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Binder; import android.os.Handler; @@ -67,7 +66,6 @@ import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; @@ -90,7 +88,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.IBatteryStats; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; @@ -100,7 +97,6 @@ import com.android.server.AppStateTrackerImpl; import com.android.server.DeviceIdleInternal; import com.android.server.JobSchedulerBackgroundThread; import com.android.server.LocalServices; -import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob; import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob; import com.android.server.job.controllers.BackgroundJobsController; import com.android.server.job.controllers.BatteryController; @@ -243,12 +239,6 @@ public class JobSchedulerService extends com.android.server.SystemService static final int MSG_CHECK_CHANGED_JOB_LIST = 8; static final int MSG_CHECK_MEDIA_EXEMPTION = 9; - /** - * Track Services that have currently active or pending jobs. The index is provided by - * {@link JobStatus#getServiceToken()} - */ - final List mActiveServices = new ArrayList<>(); - /** List of controllers that will notify this service of updates to jobs. */ final List mControllers; /** @@ -307,7 +297,6 @@ public class JobSchedulerService extends com.android.server.SystemService PackageManagerInternal mLocalPM; ActivityManagerInternal mActivityManagerInternal; - IBatteryStats mBatteryStats; DeviceIdleInternal mLocalDeviceIdleController; @VisibleForTesting AppStateTrackerImpl mAppStateTracker; @@ -1578,7 +1567,8 @@ public class JobSchedulerService extends com.android.server.SystemService mJobPackageTracker.noteNonpending(cancelled); } // Cancel if running. - stopJobOnServiceContextLocked(cancelled, reason, internalReasonCode, debugReason); + mConcurrencyManager.stopJobOnServiceContextLocked( + cancelled, reason, internalReasonCode, debugReason); // If this is a replacement, bring in the new version of the job if (incomingJob != null) { if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString()); @@ -1627,19 +1617,7 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "Doze state changed: " + deviceIdle); } - if (deviceIdle) { - // When becoming idle, make sure no jobs are actively running, - // except those using the idle exemption flag. - for (int i=0; i 0; if (mPendingJobs.size() <= 0) { - for (int i=0; i runningJobs = mConcurrencyManager.getRunningJobsLocked(); + for (int i = runningJobs.size() - 1; i >= 0; --i) { + final JobStatus job = runningJobs.valueAt(i); + if (!job.canRunInDoze()) { // We will report active if we have a job running and it does not have an // exception that allows it to run in Doze. active = true; @@ -1895,16 +1873,9 @@ public class JobSchedulerService extends com.android.server.SystemService synchronized (mLock) { // Let's go! mReadyToRock = true; - mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( - BatteryStats.SERVICE_NAME)); mLocalDeviceIdleController = LocalServices.getService(DeviceIdleInternal.class); - // Create the "runners". - for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { - mActiveServices.add( - new JobServiceContext(this, mConcurrencyManager, mBatteryStats, - mJobPackageTracker, getContext().getMainLooper())); - } + mConcurrencyManager.onThirdPartyAppsCanStart(); // Attach jobs to their controllers. mJobs.forEachJob((job) -> { for (int controller = 0; controller < mControllers.size(); controller++) { @@ -1961,19 +1932,6 @@ public class JobSchedulerService extends com.android.server.SystemService return removed; } - private boolean stopJobOnServiceContextLocked(JobStatus job, - @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) { - for (int i = 0; i < mActiveServices.size(); i++) { - JobServiceContext jsc = mActiveServices.get(i); - final JobStatus executing = jsc.getRunningJobLocked(); - if (executing != null && executing.matches(job.getUid(), job.getJobId())) { - jsc.cancelExecutingJobLocked(reason, internalReasonCode, debugReason); - return true; - } - } - return false; - } - /** Return {@code true} if the specified job is currently executing. */ @GuardedBy("mLock") public boolean isCurrentlyRunningLocked(JobStatus job) { @@ -2383,7 +2341,8 @@ public class JobSchedulerService extends com.android.server.SystemService * - if passes all the restrictions or has {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias * or higher. */ - private JobRestriction checkIfRestricted(JobStatus job) { + @GuardedBy("mLock") + JobRestriction checkIfRestricted(JobStatus job) { if (evaluateJobBiasLocked(job) >= JobInfo.BIAS_FOREGROUND_SERVICE) { // Jobs with BIAS_FOREGROUND_SERVICE or higher should not be restricted return null; @@ -2397,38 +2356,9 @@ public class JobSchedulerService extends com.android.server.SystemService return null; } + @GuardedBy("mLock") private void stopNonReadyActiveJobsLocked() { - for (int i=0; i runningJobs; synchronized (mLock) { - runningJobs = new ArrayList<>(mActiveServices.size()); - for (JobServiceContext jsc : mActiveServices) { - final JobStatus job = jsc.getRunningJobLocked(); + final ArraySet runningJobStatuses = + mConcurrencyManager.getRunningJobsLocked(); + runningJobs = new ArrayList<>(runningJobStatuses.size()); + for (int i = runningJobStatuses.size() - 1; i >= 0; --i) { + final JobStatus job = runningJobStatuses.valueAt(i); if (job != null) { runningJobs.add(job.getJob()); } @@ -3599,18 +3531,8 @@ public class JobSchedulerService extends com.android.server.SystemService } synchronized (mLock) { - boolean foundSome = false; - for (int i = 0; i < mActiveServices.size(); i++) { - final JobServiceContext jc = mActiveServices.get(i); - final JobStatus js = jc.getRunningJobLocked(); - if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) { - foundSome = true; - pw.print("Timing out: "); - js.printUniqueId(pw); - pw.print(" "); - pw.println(js.getServiceComponent().flattenToShortString()); - } - } + final boolean foundSome = mConcurrencyManager.executeTimeoutCommandLocked(pw, + pkgName, userId, hasJobId, jobId); if (!foundSome) { pw.println("No matching executing jobs found."); } @@ -4037,38 +3959,7 @@ public class JobSchedulerService extends com.android.server.SystemService pw.decreaseIndent(); pw.println(); - pw.println("Active jobs:"); - pw.increaseIndent(); - for (int i=0; i