Merge "Move JobServiceContext handling to the concurrency manager." into tm-dev

This commit is contained in:
Kweku Adams
2022-03-16 19:16:16 +00:00
committed by Android (Google) Code Review
3 changed files with 181 additions and 176 deletions

View File

@@ -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<JobServiceContext> mActiveServices = new ArrayList<>();
private final ArraySet<JobStatus> 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<JobStatus> getRunningJobsLocked() {
return mRunningJobs;
}
@GuardedBy("mLock")
boolean isJobRunningLocked(JobStatus job) {
return mRunningJobs.contains(job);
@@ -546,7 +579,7 @@ class JobConcurrencyManager {
}
final List<JobStatus> pendingJobs = mService.mPendingJobs;
final List<JobServiceContext> activeServices = mService.mActiveServices;
final List<JobServiceContext> 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<JobStatus> 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);

View File

@@ -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<JobServiceContext> mActiveServices = new ArrayList<>();
/** List of controllers that will notify this service of updates to jobs. */
final List<StateController> 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<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");
}
}
} else {
if (!deviceIdle) {
// When coming out of idle, allow thing to start back up.
if (mReadyToRock) {
if (mLocalDeviceIdleController != null) {
@@ -1682,10 +1660,10 @@ public class JobSchedulerService extends com.android.server.SystemService
// active is true if pending queue contains jobs OR some job is running.
boolean active = mPendingJobs.size() > 0;
if (mPendingJobs.size() <= 0) {
for (int i=0; i<mActiveServices.size(); i++) {
final JobServiceContext jsc = mActiveServices.get(i);
final JobStatus job = jsc.getRunningJobLocked();
if (job != null && !job.canRunInDoze()) {
final ArraySet<JobStatus> 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<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 = checkIfRestricted(running);
if (restriction != null) {
final int internalReasonCode = restriction.getInternalReason();
serviceContext.cancelExecutingJobLocked(restriction.getReason(),
internalReasonCode,
"restricted due to "
+ JobParameters.getInternalReasonCodeDescription(
internalReasonCode));
}
}
}
mConcurrencyManager.stopNonReadyActiveJobsLocked();
}
/**
@@ -2598,7 +2528,7 @@ public class JobSchedulerService extends com.android.server.SystemService
debugReason = "couldn't figure out why the job should stop running";
}
}
stopJobOnServiceContextLocked(job, job.getStopReason(),
mConcurrencyManager.stopJobOnServiceContextLocked(job, job.getStopReason(),
internalStopReason, debugReason);
} else if (mPendingJobs.remove(job)) {
noteJobNonPending(job);
@@ -3516,9 +3446,11 @@ public class JobSchedulerService extends com.android.server.SystemService
final ArrayList<JobInfo> runningJobs;
synchronized (mLock) {
runningJobs = new ArrayList<>(mActiveServices.size());
for (JobServiceContext jsc : mActiveServices) {
final JobStatus job = jsc.getRunningJobLocked();
final ArraySet<JobStatus> 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<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();
mConcurrencyManager.dumpActiveJobsLocked(pw, predicate, nowElapsed, nowUptime);
pw.println();
boolean recentPrinted = false;
@@ -4228,45 +4119,6 @@ public class JobSchedulerService extends com.android.server.SystemService
proto.end(pjToken);
}
for (JobServiceContext jsc : mActiveServices) {
final long ajToken = proto.start(JobSchedulerServiceDumpProto.ACTIVE_JOBS);
final JobStatus job = jsc.getRunningJobLocked();
if (job == null) {
final long ijToken = proto.start(ActiveJob.INACTIVE);
proto.write(ActiveJob.InactiveJob.TIME_SINCE_STOPPED_MS,
nowElapsed - jsc.mStoppedTime);
if (jsc.mStoppedReason != null) {
proto.write(ActiveJob.InactiveJob.STOPPED_REASON,
jsc.mStoppedReason);
}
proto.end(ijToken);
} else {
final long rjToken = proto.start(ActiveJob.RUNNING);
job.writeToShortProto(proto, ActiveJob.RunningJob.INFO);
proto.write(ActiveJob.RunningJob.RUNNING_DURATION_MS,
nowElapsed - jsc.getExecutionStartTimeElapsed());
proto.write(ActiveJob.RunningJob.TIME_UNTIL_TIMEOUT_MS,
jsc.getTimeoutElapsed() - nowElapsed);
job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY,
evaluateJobBiasLocked(job));
proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
nowUptime - job.madeActive);
proto.write(ActiveJob.RunningJob.PENDING_DURATION_MS,
job.madeActive - job.madePending);
proto.end(rjToken);
}
proto.end(ajToken);
}
if (filterUid == -1) {
proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock);
proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive);

View File

@@ -441,11 +441,6 @@ public final class JobStatus {
/** The reason a job most recently went from ready to not ready. */
private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
/** Provide a handle to the service that this job will be run on. */
public int getServiceToken() {
return callingUid;
}
/**
* Core constructor for JobStatus instances. All other ctors funnel down to this one.
*