1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.job; 18 19 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; 20 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 21 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.ActivityManager; 26 import android.app.ActivityManagerInternal; 27 import android.app.UserSwitchObserver; 28 import android.app.job.JobInfo; 29 import android.app.job.JobParameters; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.UserInfo; 35 import android.os.BatteryStats; 36 import android.os.Handler; 37 import android.os.PowerManager; 38 import android.os.RemoteException; 39 import android.os.ServiceManager; 40 import android.os.UserHandle; 41 import android.provider.DeviceConfig; 42 import android.util.ArraySet; 43 import android.util.IndentingPrintWriter; 44 import android.util.Pair; 45 import android.util.Pools; 46 import android.util.Slog; 47 import android.util.SparseArrayMap; 48 import android.util.SparseIntArray; 49 import android.util.SparseLongArray; 50 import android.util.TimeUtils; 51 import android.util.proto.ProtoOutputStream; 52 53 import com.android.internal.R; 54 import com.android.internal.annotations.GuardedBy; 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.internal.app.IBatteryStats; 57 import com.android.internal.app.procstats.ProcessStats; 58 import com.android.internal.util.StatLogger; 59 import com.android.server.JobSchedulerBackgroundThread; 60 import com.android.server.LocalServices; 61 import com.android.server.job.controllers.JobStatus; 62 import com.android.server.job.controllers.StateController; 63 import com.android.server.job.restrictions.JobRestriction; 64 import com.android.server.pm.UserManagerInternal; 65 66 import java.io.PrintWriter; 67 import java.lang.annotation.Retention; 68 import java.lang.annotation.RetentionPolicy; 69 import java.util.ArrayList; 70 import java.util.Collection; 71 import java.util.Comparator; 72 import java.util.List; 73 import java.util.function.Consumer; 74 import java.util.function.Predicate; 75 76 /** 77 * This class decides, given the various configuration and the system status, which jobs can start 78 * and which {@link JobServiceContext} to run each job on. 79 */ 80 class JobConcurrencyManager { 81 private static final String TAG = JobSchedulerService.TAG + ".Concurrency"; 82 private static final boolean DEBUG = JobSchedulerService.DEBUG; 83 84 /** The maximum number of concurrent jobs we'll aim to run at one time. */ 85 public static final int STANDARD_CONCURRENCY_LIMIT = 16; 86 /** The maximum number of objects we should retain in memory when not in use. */ 87 private static final int MAX_RETAINED_OBJECTS = (int) (1.5 * STANDARD_CONCURRENCY_LIMIT); 88 89 static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_"; 90 private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 91 CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; 92 private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; 93 @VisibleForTesting 94 static final String KEY_PKG_CONCURRENCY_LIMIT_EJ = 95 CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej"; 96 private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3; 97 @VisibleForTesting 98 static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR = 99 CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular"; 100 private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = STANDARD_CONCURRENCY_LIMIT / 2; 101 102 /** 103 * Set of possible execution types that a job can have. The actual type(s) of a job are based 104 * on the {@link JobStatus#lastEvaluatedBias}, which is typically evaluated right before 105 * execution (when we're trying to determine which jobs to run next) and won't change after the 106 * job has started executing. 107 * 108 * Try to give higher priority types lower values. 109 * 110 * @see #getJobWorkTypes(JobStatus) 111 */ 112 113 /** Job shouldn't run or qualify as any other work type. */ 114 static final int WORK_TYPE_NONE = 0; 115 /** The job is for an app in the TOP state for a currently active user. */ 116 static final int WORK_TYPE_TOP = 1 << 0; 117 /** 118 * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher 119 * state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user. 120 */ 121 static final int WORK_TYPE_FGS = 1 << 1; 122 /** The job is allowed to run as an expedited job for a currently active user. */ 123 static final int WORK_TYPE_EJ = 1 << 2; 124 /** 125 * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP}, 126 * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so 127 * can run as a background job. 128 */ 129 static final int WORK_TYPE_BG = 1 << 3; 130 /** 131 * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher 132 * state, or is allowed to run as an expedited job, but is for a completely background user. 133 */ 134 static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 4; 135 /** 136 * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP}, 137 * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user, 138 * so can run as a background user job. 139 */ 140 static final int WORK_TYPE_BGUSER = 1 << 5; 141 @VisibleForTesting 142 static final int NUM_WORK_TYPES = 6; 143 private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1; 144 145 @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = { 146 WORK_TYPE_NONE, 147 WORK_TYPE_TOP, 148 WORK_TYPE_FGS, 149 WORK_TYPE_EJ, 150 WORK_TYPE_BG, 151 WORK_TYPE_BGUSER_IMPORTANT, 152 WORK_TYPE_BGUSER 153 }) 154 @Retention(RetentionPolicy.SOURCE) 155 public @interface WorkType { 156 } 157 158 @VisibleForTesting workTypeToString(@orkType int workType)159 static String workTypeToString(@WorkType int workType) { 160 switch (workType) { 161 case WORK_TYPE_NONE: 162 return "NONE"; 163 case WORK_TYPE_TOP: 164 return "TOP"; 165 case WORK_TYPE_FGS: 166 return "FGS"; 167 case WORK_TYPE_EJ: 168 return "EJ"; 169 case WORK_TYPE_BG: 170 return "BG"; 171 case WORK_TYPE_BGUSER: 172 return "BGUSER"; 173 case WORK_TYPE_BGUSER_IMPORTANT: 174 return "BGUSER_IMPORTANT"; 175 default: 176 return "WORK(" + workType + ")"; 177 } 178 } 179 180 private final Object mLock; 181 private final JobSchedulerService mService; 182 private final Context mContext; 183 private final Handler mHandler; 184 185 private PowerManager mPowerManager; 186 187 private boolean mCurrentInteractiveState; 188 private boolean mEffectiveInteractiveState; 189 190 private long mLastScreenOnRealtime; 191 private long mLastScreenOffRealtime; 192 193 private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON = 194 new WorkConfigLimitsPerMemoryTrimLevel( 195 new WorkTypeConfig("screen_on_normal", 11, 196 // defaultMin 197 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 198 Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2), 199 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), 200 // defaultMax 201 List.of(Pair.create(WORK_TYPE_BG, 6), 202 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2), 203 Pair.create(WORK_TYPE_BGUSER, 3)) 204 ), 205 new WorkTypeConfig("screen_on_moderate", 9, 206 // defaultMin 207 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 208 Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1), 209 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), 210 // defaultMax 211 List.of(Pair.create(WORK_TYPE_BG, 4), 212 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 213 Pair.create(WORK_TYPE_BGUSER, 1)) 214 ), 215 new WorkTypeConfig("screen_on_low", 6, 216 // defaultMin 217 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 218 Pair.create(WORK_TYPE_EJ, 1)), 219 // defaultMax 220 List.of(Pair.create(WORK_TYPE_BG, 2), 221 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 222 Pair.create(WORK_TYPE_BGUSER, 1)) 223 ), 224 new WorkTypeConfig("screen_on_critical", 6, 225 // defaultMin 226 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 227 Pair.create(WORK_TYPE_EJ, 1)), 228 // defaultMax 229 List.of(Pair.create(WORK_TYPE_BG, 1), 230 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 231 Pair.create(WORK_TYPE_BGUSER, 1)) 232 ) 233 ); 234 private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF = 235 new WorkConfigLimitsPerMemoryTrimLevel( 236 new WorkTypeConfig("screen_off_normal", 16, 237 // defaultMin 238 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2), 239 Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2), 240 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), 241 // defaultMax 242 List.of(Pair.create(WORK_TYPE_BG, 10), 243 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2), 244 Pair.create(WORK_TYPE_BGUSER, 3)) 245 ), 246 new WorkTypeConfig("screen_off_moderate", 14, 247 // defaultMin 248 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2), 249 Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2), 250 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)), 251 // defaultMax 252 List.of(Pair.create(WORK_TYPE_BG, 7), 253 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 254 Pair.create(WORK_TYPE_BGUSER, 1)) 255 ), 256 new WorkTypeConfig("screen_off_low", 9, 257 // defaultMin 258 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 259 Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)), 260 // defaultMax 261 List.of(Pair.create(WORK_TYPE_BG, 3), 262 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 263 Pair.create(WORK_TYPE_BGUSER, 1)) 264 ), 265 new WorkTypeConfig("screen_off_critical", 6, 266 // defaultMin 267 List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1), 268 Pair.create(WORK_TYPE_EJ, 1)), 269 // defaultMax 270 List.of(Pair.create(WORK_TYPE_BG, 1), 271 Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1), 272 Pair.create(WORK_TYPE_BGUSER, 1)) 273 ) 274 ); 275 276 /** 277 * Comparator to sort the determination lists, putting the ContextAssignments that we most 278 * prefer to use at the end of the list. 279 */ 280 private static final Comparator<ContextAssignment> sDeterminationComparator = (ca1, ca2) -> { 281 if (ca1 == ca2) { 282 return 0; 283 } 284 final JobStatus js1 = ca1.context.getRunningJobLocked(); 285 final JobStatus js2 = ca2.context.getRunningJobLocked(); 286 // Prefer using an empty context over one with a running job. 287 if (js1 == null) { 288 if (js2 == null) { 289 return 0; 290 } 291 return 1; 292 } else if (js2 == null) { 293 return -1; 294 } 295 // We would prefer to replace bg jobs over TOP jobs. 296 if (js1.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { 297 if (js2.lastEvaluatedBias != JobInfo.BIAS_TOP_APP) { 298 return -1; 299 } 300 } else if (js2.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { 301 return 1; 302 } 303 // Prefer replacing the job that has been running the longest. 304 return Long.compare( 305 ca2.context.getExecutionStartTimeElapsed(), 306 ca1.context.getExecutionStartTimeElapsed()); 307 }; 308 309 // We reuse the lists to avoid GC churn. 310 private final ArraySet<ContextAssignment> mRecycledChanged = new ArraySet<>(); 311 private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>(); 312 private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>(); 313 private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>(); 314 315 private final Pools.Pool<ContextAssignment> mContextAssignmentPool = 316 new Pools.SimplePool<>(MAX_RETAINED_OBJECTS); 317 318 /** 319 * Set of JobServiceContexts that are actively running jobs. 320 */ 321 final List<JobServiceContext> mActiveServices = new ArrayList<>(); 322 323 /** Set of JobServiceContexts that aren't currently running any jobs. */ 324 private final ArraySet<JobServiceContext> mIdleContexts = new ArraySet<>(); 325 326 private int mNumDroppedContexts = 0; 327 328 private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>(); 329 330 private final WorkCountTracker mWorkCountTracker = new WorkCountTracker(); 331 332 private final Pools.Pool<PackageStats> mPkgStatsPool = 333 new Pools.SimplePool<>(MAX_RETAINED_OBJECTS); 334 335 private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>(); 336 337 private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal; 338 339 /** Wait for this long after screen off before adjusting the job concurrency. */ 340 private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS; 341 342 /** 343 * The maximum number of expedited jobs a single userId-package can have running simultaneously. 344 * TOP apps are not limited. 345 */ 346 private int mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ; 347 348 /** 349 * The maximum number of regular jobs a single userId-package can have running simultaneously. 350 * TOP apps are not limited. 351 */ 352 private int mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR; 353 354 /** Current memory trim level. */ 355 private int mLastMemoryTrimLevel; 356 357 /** Used to throttle heavy API calls. */ 358 private long mNextSystemStateRefreshTime; 359 private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000; 360 361 private final Consumer<PackageStats> mPackageStatsStagingCountClearer = 362 PackageStats::resetStagedCount; 363 364 private final StatLogger mStatLogger = new StatLogger(new String[]{ 365 "assignJobsToContexts", 366 "refreshSystemState", 367 }); 368 @VisibleForTesting 369 GracePeriodObserver mGracePeriodObserver; 370 @VisibleForTesting 371 boolean mShouldRestrictBgUser; 372 373 interface Stats { 374 int ASSIGN_JOBS_TO_CONTEXTS = 0; 375 int REFRESH_SYSTEM_STATE = 1; 376 377 int COUNT = REFRESH_SYSTEM_STATE + 1; 378 } 379 JobConcurrencyManager(JobSchedulerService service)380 JobConcurrencyManager(JobSchedulerService service) { 381 mService = service; 382 mLock = mService.mLock; 383 mContext = service.getTestableContext(); 384 385 mHandler = JobSchedulerBackgroundThread.getHandler(); 386 387 mGracePeriodObserver = new GracePeriodObserver(mContext); 388 mShouldRestrictBgUser = mContext.getResources().getBoolean( 389 R.bool.config_jobSchedulerRestrictBackgroundUser); 390 } 391 onSystemReady()392 public void onSystemReady() { 393 mPowerManager = mContext.getSystemService(PowerManager.class); 394 395 final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); 396 filter.addAction(Intent.ACTION_SCREEN_OFF); 397 filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); 398 filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); 399 mContext.registerReceiver(mReceiver, filter); 400 try { 401 ActivityManager.getService().registerUserSwitchObserver(mGracePeriodObserver, TAG); 402 } catch (RemoteException e) { 403 } 404 405 onInteractiveStateChanged(mPowerManager.isInteractive()); 406 } 407 408 /** 409 * Called when the boot phase reaches 410 * {@link com.android.server.SystemService#PHASE_THIRD_PARTY_APPS_CAN_START}. 411 */ onThirdPartyAppsCanStart()412 void onThirdPartyAppsCanStart() { 413 final IBatteryStats batteryStats = IBatteryStats.Stub.asInterface( 414 ServiceManager.getService(BatteryStats.SERVICE_NAME)); 415 for (int i = 0; i < STANDARD_CONCURRENCY_LIMIT; i++) { 416 mIdleContexts.add( 417 new JobServiceContext(mService, this, batteryStats, 418 mService.mJobPackageTracker, mContext.getMainLooper())); 419 } 420 } 421 422 @GuardedBy("mLock") onAppRemovedLocked(String pkgName, int uid)423 void onAppRemovedLocked(String pkgName, int uid) { 424 final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName); 425 if (packageStats != null) { 426 if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) { 427 // Don't delete the object just yet. We'll remove it in onJobCompleted() when the 428 // jobs officially stop running. 429 Slog.w(TAG, 430 pkgName + "(" + uid + ") marked as removed before jobs stopped running"); 431 } else { 432 mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName); 433 } 434 } 435 } 436 onUserRemoved(int userId)437 void onUserRemoved(int userId) { 438 mGracePeriodObserver.onUserRemoved(userId); 439 } 440 441 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 442 @Override 443 public void onReceive(Context context, Intent intent) { 444 switch (intent.getAction()) { 445 case Intent.ACTION_SCREEN_ON: 446 onInteractiveStateChanged(true); 447 break; 448 case Intent.ACTION_SCREEN_OFF: 449 onInteractiveStateChanged(false); 450 break; 451 case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: 452 if (mPowerManager != null && mPowerManager.isDeviceIdleMode()) { 453 synchronized (mLock) { 454 stopUnexemptedJobsForDoze(); 455 stopLongRunningJobsLocked("deep doze"); 456 } 457 } 458 break; 459 case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED: 460 if (mPowerManager != null && mPowerManager.isPowerSaveMode()) { 461 synchronized (mLock) { 462 stopLongRunningJobsLocked("battery saver"); 463 } 464 } 465 break; 466 } 467 } 468 }; 469 470 /** 471 * Called when the screen turns on / off. 472 */ onInteractiveStateChanged(boolean interactive)473 private void onInteractiveStateChanged(boolean interactive) { 474 synchronized (mLock) { 475 if (mCurrentInteractiveState == interactive) { 476 return; 477 } 478 mCurrentInteractiveState = interactive; 479 if (DEBUG) { 480 Slog.d(TAG, "Interactive: " + interactive); 481 } 482 483 final long nowRealtime = sElapsedRealtimeClock.millis(); 484 if (interactive) { 485 mLastScreenOnRealtime = nowRealtime; 486 mEffectiveInteractiveState = true; 487 488 mHandler.removeCallbacks(mRampUpForScreenOff); 489 } else { 490 mLastScreenOffRealtime = nowRealtime; 491 492 // Set mEffectiveInteractiveState to false after the delay, when we may increase 493 // the concurrency. 494 // We don't need a wakeup alarm here. When there's a pending job, there should 495 // also be jobs running too, meaning the device should be awake. 496 497 // Note: we can't directly do postDelayed(this::rampUpForScreenOn), because 498 // we need the exact same instance for removeCallbacks(). 499 mHandler.postDelayed(mRampUpForScreenOff, mScreenOffAdjustmentDelayMs); 500 } 501 } 502 } 503 504 private final Runnable mRampUpForScreenOff = this::rampUpForScreenOff; 505 506 /** 507 * Called in {@link #mScreenOffAdjustmentDelayMs} after 508 * the screen turns off, in order to increase concurrency. 509 */ rampUpForScreenOff()510 private void rampUpForScreenOff() { 511 synchronized (mLock) { 512 // Make sure the screen has really been off for the configured duration. 513 // (There could be a race.) 514 if (!mEffectiveInteractiveState) { 515 return; 516 } 517 if (mLastScreenOnRealtime > mLastScreenOffRealtime) { 518 return; 519 } 520 final long now = sElapsedRealtimeClock.millis(); 521 if ((mLastScreenOffRealtime + mScreenOffAdjustmentDelayMs) > now) { 522 return; 523 } 524 525 mEffectiveInteractiveState = false; 526 527 if (DEBUG) { 528 Slog.d(TAG, "Ramping up concurrency"); 529 } 530 531 mService.maybeRunPendingJobsLocked(); 532 } 533 } 534 535 @GuardedBy("mLock") getRunningJobsLocked()536 ArraySet<JobStatus> getRunningJobsLocked() { 537 return mRunningJobs; 538 } 539 540 @GuardedBy("mLock") isJobRunningLocked(JobStatus job)541 boolean isJobRunningLocked(JobStatus job) { 542 return mRunningJobs.contains(job); 543 } 544 545 /** 546 * Returns true if a job that is "similar" to the provided job is currently running. 547 * "Similar" in this context means any job that the {@link JobStore} would consider equivalent 548 * and replace one with the other. 549 */ 550 @GuardedBy("mLock") isSimilarJobRunningLocked(JobStatus job)551 private boolean isSimilarJobRunningLocked(JobStatus job) { 552 for (int i = mRunningJobs.size() - 1; i >= 0; --i) { 553 JobStatus js = mRunningJobs.valueAt(i); 554 if (job.getUid() == js.getUid() && job.getJobId() == js.getJobId()) { 555 return true; 556 } 557 } 558 return false; 559 } 560 561 /** Return {@code true} if the state was updated. */ 562 @GuardedBy("mLock") refreshSystemStateLocked()563 private boolean refreshSystemStateLocked() { 564 final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis(); 565 566 // Only refresh the information every so often. 567 if (nowUptime < mNextSystemStateRefreshTime) { 568 return false; 569 } 570 571 final long start = mStatLogger.getTime(); 572 mNextSystemStateRefreshTime = nowUptime + SYSTEM_STATE_REFRESH_MIN_INTERVAL; 573 574 mLastMemoryTrimLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL; 575 try { 576 mLastMemoryTrimLevel = ActivityManager.getService().getMemoryTrimLevel(); 577 } catch (RemoteException e) { 578 } 579 580 mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start); 581 return true; 582 } 583 584 @GuardedBy("mLock") updateCounterConfigLocked()585 private void updateCounterConfigLocked() { 586 if (!refreshSystemStateLocked()) { 587 return; 588 } 589 590 final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState 591 ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF; 592 593 switch (mLastMemoryTrimLevel) { 594 case ProcessStats.ADJ_MEM_FACTOR_MODERATE: 595 mWorkTypeConfig = workConfigs.moderate; 596 break; 597 case ProcessStats.ADJ_MEM_FACTOR_LOW: 598 mWorkTypeConfig = workConfigs.low; 599 break; 600 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: 601 mWorkTypeConfig = workConfigs.critical; 602 break; 603 default: 604 mWorkTypeConfig = workConfigs.normal; 605 break; 606 } 607 608 mWorkCountTracker.setConfig(mWorkTypeConfig); 609 } 610 611 /** 612 * Takes jobs from pending queue and runs them on available contexts. 613 * If no contexts are available, preempts lower bias jobs to run higher bias ones. 614 * Lock on mLock before calling this function. 615 */ 616 @GuardedBy("mLock") assignJobsToContextsLocked()617 void assignJobsToContextsLocked() { 618 final long start = mStatLogger.getTime(); 619 620 assignJobsToContextsInternalLocked(); 621 622 mStatLogger.logDurationStat(Stats.ASSIGN_JOBS_TO_CONTEXTS, start); 623 } 624 625 @GuardedBy("mLock") assignJobsToContextsInternalLocked()626 private void assignJobsToContextsInternalLocked() { 627 if (DEBUG) { 628 Slog.d(TAG, printPendingQueueLocked()); 629 } 630 631 if (mService.getPendingJobQueue().size() == 0) { 632 // Nothing to do. 633 return; 634 } 635 636 final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue(); 637 final List<JobServiceContext> activeServices = mActiveServices; 638 639 // To avoid GC churn, we recycle the arrays. 640 final ArraySet<ContextAssignment> changed = mRecycledChanged; 641 final ArraySet<ContextAssignment> idle = mRecycledIdle; 642 final ArrayList<ContextAssignment> preferredUidOnly = mRecycledPreferredUidOnly; 643 final ArrayList<ContextAssignment> stoppable = mRecycledStoppable; 644 645 updateCounterConfigLocked(); 646 // Reset everything since we'll re-evaluate the current state. 647 mWorkCountTracker.resetCounts(); 648 649 // Update the priorities of jobs that aren't running, and also count the pending work types. 650 // Do this before the following loop to hopefully reduce the cost of 651 // shouldStopRunningJobLocked(). 652 updateNonRunningPrioritiesLocked(pendingJobQueue, true); 653 654 final int numRunningJobs = activeServices.size(); 655 for (int i = 0; i < numRunningJobs; ++i) { 656 final JobServiceContext jsc = activeServices.get(i); 657 final JobStatus js = jsc.getRunningJobLocked(); 658 659 ContextAssignment assignment = mContextAssignmentPool.acquire(); 660 if (assignment == null) { 661 assignment = new ContextAssignment(); 662 } 663 664 assignment.context = jsc; 665 666 if (js != null) { 667 mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType()); 668 assignment.workType = jsc.getRunningJobWorkType(); 669 } 670 671 assignment.preferredUid = jsc.getPreferredUid(); 672 if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) { 673 stoppable.add(assignment); 674 } else { 675 preferredUidOnly.add(assignment); 676 } 677 } 678 preferredUidOnly.sort(sDeterminationComparator); 679 stoppable.sort(sDeterminationComparator); 680 for (int i = numRunningJobs; i < STANDARD_CONCURRENCY_LIMIT; ++i) { 681 final JobServiceContext jsc; 682 final int numIdleContexts = mIdleContexts.size(); 683 if (numIdleContexts > 0) { 684 jsc = mIdleContexts.removeAt(numIdleContexts - 1); 685 } else { 686 Slog.wtf(TAG, "Had fewer than " + STANDARD_CONCURRENCY_LIMIT + " in existence"); 687 jsc = createNewJobServiceContext(); 688 } 689 690 ContextAssignment assignment = mContextAssignmentPool.acquire(); 691 if (assignment == null) { 692 assignment = new ContextAssignment(); 693 } 694 695 assignment.context = jsc; 696 idle.add(assignment); 697 } 698 if (DEBUG) { 699 Slog.d(TAG, printAssignments("running jobs initial", stoppable, preferredUidOnly)); 700 } 701 702 mWorkCountTracker.onCountDone(); 703 704 JobStatus nextPending; 705 pendingJobQueue.resetIterator(); 706 int projectedRunningCount = numRunningJobs; 707 while ((nextPending = pendingJobQueue.next()) != null) { 708 if (mRunningJobs.contains(nextPending)) { 709 // Should never happen. 710 Slog.wtf(TAG, "Pending queue contained a running job"); 711 if (DEBUG) { 712 Slog.e(TAG, "Pending+running job: " + nextPending); 713 } 714 pendingJobQueue.remove(nextPending); 715 continue; 716 } 717 718 final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob() 719 && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP; 720 if (DEBUG && isSimilarJobRunningLocked(nextPending)) { 721 Slog.w(TAG, "Already running similar " + (isTopEj ? "TOP-EJ" : "job") 722 + " to: " + nextPending); 723 } 724 725 // Find an available slot for nextPending. The context should be one of the following: 726 // 1. Unused 727 // 2. Its job should have used up its minimum execution guarantee so it 728 // 3. Its job should have the lowest bias among all running jobs (sharing the same UID 729 // as nextPending) 730 ContextAssignment selectedContext = null; 731 final int allWorkTypes = getJobWorkTypes(nextPending); 732 final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending); 733 final boolean isInOverage = projectedRunningCount > STANDARD_CONCURRENCY_LIMIT; 734 boolean startingJob = false; 735 if (idle.size() > 0) { 736 final int idx = idle.size() - 1; 737 final ContextAssignment assignment = idle.valueAt(idx); 738 final boolean preferredUidOkay = (assignment.preferredUid == nextPending.getUid()) 739 || (assignment.preferredUid == JobServiceContext.NO_PREFERRED_UID); 740 int workType = mWorkCountTracker.canJobStart(allWorkTypes); 741 if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) { 742 // This slot is free, and we haven't yet hit the limit on 743 // concurrent jobs... we can just throw the job in to here. 744 selectedContext = assignment; 745 startingJob = true; 746 idle.removeAt(idx); 747 assignment.newJob = nextPending; 748 assignment.newWorkType = workType; 749 } 750 } 751 if (selectedContext == null && stoppable.size() > 0) { 752 int topEjCount = 0; 753 for (int r = mRunningJobs.size() - 1; r >= 0; --r) { 754 JobStatus js = mRunningJobs.valueAt(r); 755 if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { 756 topEjCount++; 757 } 758 } 759 for (int s = stoppable.size() - 1; s >= 0; --s) { 760 final ContextAssignment assignment = stoppable.get(s); 761 final JobStatus runningJob = assignment.context.getRunningJobLocked(); 762 // Maybe stop the job if it has had its day in the sun. Only allow replacing 763 // for one of the following conditions: 764 // 1. We're putting in the current TOP app's EJ 765 // 2. There aren't too many jobs running AND the current job started when the 766 // app was in the background 767 // 3. There aren't too many jobs running AND the current job started when the 768 // app was on TOP, but the app has since left TOP 769 // 4. There aren't too many jobs running AND the current job started when the 770 // app was on TOP, the app is still TOP, but there are too many TOP+EJs 771 // running (because we don't want them to starve out other apps and the 772 // current job has already run for the minimum guaranteed time). 773 boolean canReplace = isTopEj; // Case 1 774 if (!canReplace && !isInOverage) { 775 final int currentJobBias = mService.evaluateJobBiasLocked(runningJob); 776 canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2 777 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3 778 || topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4 779 } 780 if (canReplace) { 781 int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes, 782 assignment.context.getRunningJobWorkType()); 783 if (replaceWorkType != WORK_TYPE_NONE) { 784 // Right now, the way the code is set up, we don't need to explicitly 785 // assign the new job to this context since we'll reassign when the 786 // preempted job finally stops. 787 assignment.preemptReason = assignment.shouldStopJobReason; 788 assignment.preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE; 789 selectedContext = assignment; 790 stoppable.remove(s); 791 assignment.newJob = nextPending; 792 assignment.newWorkType = replaceWorkType; 793 break; 794 } 795 } 796 } 797 } 798 if (selectedContext == null && (!isInOverage || isTopEj)) { 799 int lowestBiasSeen = Integer.MAX_VALUE; 800 for (int p = preferredUidOnly.size() - 1; p >= 0; --p) { 801 final ContextAssignment assignment = preferredUidOnly.get(p); 802 final JobStatus runningJob = assignment.context.getRunningJobLocked(); 803 if (runningJob.getUid() != nextPending.getUid()) { 804 continue; 805 } 806 final int jobBias = mService.evaluateJobBiasLocked(runningJob); 807 if (jobBias >= nextPending.lastEvaluatedBias) { 808 continue; 809 } 810 811 if (selectedContext == null || lowestBiasSeen > jobBias) { 812 // Step down the preemption threshold - wind up replacing 813 // the lowest-bias running job 814 lowestBiasSeen = jobBias; 815 selectedContext = assignment; 816 assignment.preemptReason = "higher bias job found"; 817 assignment.preemptReasonCode = JobParameters.STOP_REASON_PREEMPT; 818 // In this case, we're just going to preempt a low bias job, we're not 819 // actually starting a job, so don't set startingJob to true. 820 } 821 } 822 if (selectedContext != null) { 823 selectedContext.newJob = nextPending; 824 preferredUidOnly.remove(selectedContext); 825 } 826 } 827 // Make sure to run EJs for the TOP app immediately. 828 if (isTopEj) { 829 if (selectedContext != null 830 && selectedContext.context.getRunningJobLocked() != null) { 831 // We're "replacing" a currently running job, but we want TOP EJs to start 832 // immediately, so we'll start the EJ on a fresh available context and 833 // stop this currently running job to replace in two steps. 834 changed.add(selectedContext); 835 projectedRunningCount--; 836 selectedContext.newJob = null; 837 selectedContext.newWorkType = WORK_TYPE_NONE; 838 selectedContext = null; 839 } 840 if (selectedContext == null) { 841 selectedContext = mContextAssignmentPool.acquire(); 842 if (selectedContext == null) { 843 selectedContext = new ContextAssignment(); 844 } 845 selectedContext.context = mIdleContexts.size() > 0 846 ? mIdleContexts.removeAt(mIdleContexts.size() - 1) 847 : createNewJobServiceContext(); 848 selectedContext.newJob = nextPending; 849 final int workType = mWorkCountTracker.canJobStart(allWorkTypes); 850 selectedContext.newWorkType = 851 (workType != WORK_TYPE_NONE) ? workType : WORK_TYPE_TOP; 852 } 853 } 854 final PackageStats packageStats = getPkgStatsLocked( 855 nextPending.getSourceUserId(), nextPending.getSourcePackageName()); 856 if (selectedContext != null) { 857 changed.add(selectedContext); 858 if (selectedContext.context.getRunningJobLocked() != null) { 859 projectedRunningCount--; 860 } 861 if (selectedContext.newJob != null) { 862 projectedRunningCount++; 863 } 864 packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob()); 865 } 866 if (startingJob) { 867 // Increase the counters when we're going to start a job. 868 mWorkCountTracker.stageJob(selectedContext.newWorkType, allWorkTypes); 869 mActivePkgStats.add( 870 nextPending.getSourceUserId(), nextPending.getSourcePackageName(), 871 packageStats); 872 } 873 } 874 if (DEBUG) { 875 Slog.d(TAG, printAssignments("running jobs final", 876 stoppable, preferredUidOnly, changed)); 877 878 Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString()); 879 } 880 881 for (int c = changed.size() - 1; c >= 0; --c) { 882 final ContextAssignment assignment = changed.valueAt(c); 883 final JobStatus js = assignment.context.getRunningJobLocked(); 884 if (js != null) { 885 if (DEBUG) { 886 Slog.d(TAG, "preempting job: " + js); 887 } 888 // preferredUid will be set to uid of currently running job, if appropriate. 889 assignment.context.cancelExecutingJobLocked( 890 assignment.preemptReasonCode, 891 JobParameters.INTERNAL_STOP_REASON_PREEMPT, assignment.preemptReason); 892 } else { 893 final JobStatus pendingJob = assignment.newJob; 894 if (DEBUG) { 895 Slog.d(TAG, "About to run job on context " 896 + assignment.context.getId() + ", job: " + pendingJob); 897 } 898 startJobLocked(assignment.context, pendingJob, assignment.newWorkType); 899 } 900 901 assignment.clear(); 902 mContextAssignmentPool.release(assignment); 903 } 904 for (int s = stoppable.size() - 1; s >= 0; --s) { 905 final ContextAssignment assignment = stoppable.get(s); 906 assignment.clear(); 907 mContextAssignmentPool.release(assignment); 908 } 909 for (int p = preferredUidOnly.size() - 1; p >= 0; --p) { 910 final ContextAssignment assignment = preferredUidOnly.get(p); 911 assignment.clear(); 912 mContextAssignmentPool.release(assignment); 913 } 914 for (int i = idle.size() - 1; i >= 0; --i) { 915 final ContextAssignment assignment = idle.valueAt(i); 916 mIdleContexts.add(assignment.context); 917 assignment.clear(); 918 mContextAssignmentPool.release(assignment); 919 } 920 changed.clear(); 921 idle.clear(); 922 stoppable.clear(); 923 preferredUidOnly.clear(); 924 mWorkCountTracker.resetStagingCount(); 925 mActivePkgStats.forEach(mPackageStatsStagingCountClearer); 926 noteConcurrency(); 927 } 928 929 @GuardedBy("mLock") onUidBiasChangedLocked(int prevBias, int newBias)930 void onUidBiasChangedLocked(int prevBias, int newBias) { 931 if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) { 932 // TOP app didn't change. Nothing to do. 933 return; 934 } 935 if (mService.getPendingJobQueue().size() == 0) { 936 // Nothing waiting for the top app to leave. Nothing to do. 937 return; 938 } 939 // Don't stop the TOP jobs directly. Instead, see if they would be replaced by some 940 // pending job (there may not always be something to replace them). 941 assignJobsToContextsLocked(); 942 } 943 944 @GuardedBy("mLock") stopJobOnServiceContextLocked(JobStatus job, @JobParameters.StopReason int reason, int internalReasonCode, String debugReason)945 boolean stopJobOnServiceContextLocked(JobStatus job, 946 @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) { 947 if (!mRunningJobs.contains(job)) { 948 return false; 949 } 950 951 for (int i = 0; i < mActiveServices.size(); i++) { 952 JobServiceContext jsc = mActiveServices.get(i); 953 final JobStatus executing = jsc.getRunningJobLocked(); 954 if (executing == job) { 955 jsc.cancelExecutingJobLocked(reason, internalReasonCode, debugReason); 956 return true; 957 } 958 } 959 Slog.wtf(TAG, "Couldn't find running job on a context"); 960 mRunningJobs.remove(job); 961 return false; 962 } 963 964 @GuardedBy("mLock") stopUnexemptedJobsForDoze()965 private void stopUnexemptedJobsForDoze() { 966 // When becoming idle, make sure no jobs are actively running, 967 // except those using the idle exemption flag. 968 for (int i = 0; i < mActiveServices.size(); i++) { 969 JobServiceContext jsc = mActiveServices.get(i); 970 final JobStatus executing = jsc.getRunningJobLocked(); 971 if (executing != null && !executing.canRunInDoze()) { 972 jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE, 973 JobParameters.INTERNAL_STOP_REASON_DEVICE_IDLE, 974 "cancelled due to doze"); 975 } 976 } 977 } 978 979 @GuardedBy("mLock") stopLongRunningJobsLocked(@onNull String debugReason)980 private void stopLongRunningJobsLocked(@NonNull String debugReason) { 981 for (int i = 0; i < mActiveServices.size(); ++i) { 982 final JobServiceContext jsc = mActiveServices.get(i); 983 final JobStatus jobStatus = jsc.getRunningJobLocked(); 984 985 if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) { 986 jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE, 987 JobParameters.INTERNAL_STOP_REASON_TIMEOUT, debugReason); 988 } 989 } 990 } 991 992 @GuardedBy("mLock") stopNonReadyActiveJobsLocked()993 void stopNonReadyActiveJobsLocked() { 994 for (int i = 0; i < mActiveServices.size(); i++) { 995 JobServiceContext serviceContext = mActiveServices.get(i); 996 final JobStatus running = serviceContext.getRunningJobLocked(); 997 if (running == null) { 998 continue; 999 } 1000 if (!running.isReady()) { 1001 if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX 1002 && running.getStopReason() == JobParameters.STOP_REASON_APP_STANDBY) { 1003 serviceContext.cancelExecutingJobLocked( 1004 running.getStopReason(), 1005 JobParameters.INTERNAL_STOP_REASON_RESTRICTED_BUCKET, 1006 "cancelled due to restricted bucket"); 1007 } else { 1008 serviceContext.cancelExecutingJobLocked( 1009 running.getStopReason(), 1010 JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED, 1011 "cancelled due to unsatisfied constraints"); 1012 } 1013 } else { 1014 final JobRestriction restriction = mService.checkIfRestricted(running); 1015 if (restriction != null) { 1016 final int internalReasonCode = restriction.getInternalReason(); 1017 serviceContext.cancelExecutingJobLocked(restriction.getReason(), 1018 internalReasonCode, 1019 "restricted due to " 1020 + JobParameters.getInternalReasonCodeDescription( 1021 internalReasonCode)); 1022 } 1023 } 1024 } 1025 } 1026 noteConcurrency()1027 private void noteConcurrency() { 1028 mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(), 1029 // TODO: log per type instead of only TOP 1030 mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP)); 1031 } 1032 1033 @GuardedBy("mLock") updateNonRunningPrioritiesLocked(@onNull final PendingJobQueue jobQueue, boolean updateCounter)1034 private void updateNonRunningPrioritiesLocked(@NonNull final PendingJobQueue jobQueue, 1035 boolean updateCounter) { 1036 JobStatus pending; 1037 jobQueue.resetIterator(); 1038 while ((pending = jobQueue.next()) != null) { 1039 1040 // If job is already running, go to next job. 1041 if (mRunningJobs.contains(pending)) { 1042 continue; 1043 } 1044 1045 pending.lastEvaluatedBias = mService.evaluateJobBiasLocked(pending); 1046 1047 if (updateCounter) { 1048 mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending)); 1049 } 1050 } 1051 } 1052 1053 @GuardedBy("mLock") 1054 @NonNull getPkgStatsLocked(int userId, @NonNull String packageName)1055 private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) { 1056 PackageStats packageStats = mActivePkgStats.get(userId, packageName); 1057 if (packageStats == null) { 1058 packageStats = mPkgStatsPool.acquire(); 1059 if (packageStats == null) { 1060 packageStats = new PackageStats(); 1061 } 1062 packageStats.setPackage(userId, packageName); 1063 } 1064 return packageStats; 1065 } 1066 1067 @GuardedBy("mLock") 1068 @VisibleForTesting isPkgConcurrencyLimitedLocked(@onNull JobStatus jobStatus)1069 boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) { 1070 if (jobStatus.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) { 1071 // Don't restrict top apps' concurrency. The work type limits will make sure 1072 // background jobs have slots to run if the system has resources. 1073 return false; 1074 } 1075 // Use < instead of <= as that gives us a little wiggle room in case a new job comes 1076 // along very shortly. 1077 if (mService.getPendingJobQueue().size() + mRunningJobs.size() 1078 < mWorkTypeConfig.getMaxTotal()) { 1079 // Don't artificially limit a single package if we don't even have enough jobs to use 1080 // the maximum number of slots. We'll preempt the job later if we need the slot. 1081 return false; 1082 } 1083 final PackageStats packageStats = 1084 mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 1085 if (packageStats == null) { 1086 // No currently running jobs. 1087 return false; 1088 } 1089 if (jobStatus.shouldTreatAsExpeditedJob()) { 1090 return packageStats.numRunningEj + packageStats.numStagedEj >= mPkgConcurrencyLimitEj; 1091 } else { 1092 return packageStats.numRunningRegular + packageStats.numStagedRegular 1093 >= mPkgConcurrencyLimitRegular; 1094 } 1095 } 1096 1097 @GuardedBy("mLock") startJobLocked(@onNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType)1098 private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, 1099 @WorkType final int workType) { 1100 final List<StateController> controllers = mService.mControllers; 1101 final int numControllers = controllers.size(); 1102 final PowerManager.WakeLock wl = 1103 mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, jobStatus.getTag()); 1104 wl.setWorkSource(mService.deriveWorkSource( 1105 jobStatus.getSourceUid(), jobStatus.getSourcePackageName())); 1106 wl.setReferenceCounted(false); 1107 // Since the quota controller will start counting from the time prepareForExecutionLocked() 1108 // is called, hold a wakelock to make sure the CPU doesn't suspend between that call and 1109 // when the service actually starts. 1110 wl.acquire(); 1111 try { 1112 for (int ic = 0; ic < numControllers; ic++) { 1113 controllers.get(ic).prepareForExecutionLocked(jobStatus); 1114 } 1115 final PackageStats packageStats = getPkgStatsLocked( 1116 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 1117 packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob()); 1118 if (!worker.executeRunnableJob(jobStatus, workType)) { 1119 Slog.e(TAG, "Error executing " + jobStatus); 1120 mWorkCountTracker.onStagedJobFailed(workType); 1121 for (int ic = 0; ic < numControllers; ic++) { 1122 controllers.get(ic).unprepareFromExecutionLocked(jobStatus); 1123 } 1124 } else { 1125 mRunningJobs.add(jobStatus); 1126 mActiveServices.add(worker); 1127 mIdleContexts.remove(worker); 1128 mWorkCountTracker.onJobStarted(workType); 1129 packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob()); 1130 mActivePkgStats.add( 1131 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), 1132 packageStats); 1133 } 1134 if (mService.getPendingJobQueue().remove(jobStatus)) { 1135 mService.mJobPackageTracker.noteNonpending(jobStatus); 1136 } 1137 } finally { 1138 wl.release(); 1139 } 1140 } 1141 1142 @GuardedBy("mLock") onJobCompletedLocked(@onNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType)1143 void onJobCompletedLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, 1144 @WorkType final int workType) { 1145 mWorkCountTracker.onJobFinished(workType); 1146 mRunningJobs.remove(jobStatus); 1147 mActiveServices.remove(worker); 1148 if (mIdleContexts.size() < MAX_RETAINED_OBJECTS) { 1149 // Don't need to save all new contexts, but keep some extra around in case we need 1150 // extras for another TOP+EJ overage. 1151 mIdleContexts.add(worker); 1152 } else { 1153 mNumDroppedContexts++; 1154 } 1155 final PackageStats packageStats = 1156 mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 1157 if (packageStats == null) { 1158 Slog.wtf(TAG, "Running job didn't have an active PackageStats object"); 1159 } else { 1160 packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob); 1161 if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) { 1162 mActivePkgStats.delete(packageStats.userId, packageStats.packageName); 1163 mPkgStatsPool.release(packageStats); 1164 } 1165 } 1166 1167 final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue(); 1168 if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT || pendingJobQueue.size() == 0) { 1169 worker.clearPreferredUid(); 1170 // We're over the limit (because the TOP app scheduled a lot of EJs). Don't start 1171 // running anything new until we get back below the limit. 1172 noteConcurrency(); 1173 return; 1174 } 1175 1176 if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) { 1177 updateCounterConfigLocked(); 1178 // Preemption case needs special care. 1179 updateNonRunningPrioritiesLocked(pendingJobQueue, false); 1180 1181 JobStatus highestBiasJob = null; 1182 int highBiasWorkType = workType; 1183 int highBiasAllWorkTypes = workType; 1184 JobStatus backupJob = null; 1185 int backupWorkType = WORK_TYPE_NONE; 1186 int backupAllWorkTypes = WORK_TYPE_NONE; 1187 1188 JobStatus nextPending; 1189 pendingJobQueue.resetIterator(); 1190 while ((nextPending = pendingJobQueue.next()) != null) { 1191 if (mRunningJobs.contains(nextPending)) { 1192 // Should never happen. 1193 Slog.wtf(TAG, "Pending queue contained a running job"); 1194 if (DEBUG) { 1195 Slog.e(TAG, "Pending+running job: " + nextPending); 1196 } 1197 pendingJobQueue.remove(nextPending); 1198 continue; 1199 } 1200 1201 if (DEBUG && isSimilarJobRunningLocked(nextPending)) { 1202 Slog.w(TAG, "Already running similar job to: " + nextPending); 1203 } 1204 1205 if (worker.getPreferredUid() != nextPending.getUid()) { 1206 if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) { 1207 int allWorkTypes = getJobWorkTypes(nextPending); 1208 int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); 1209 if (workAsType != WORK_TYPE_NONE) { 1210 backupJob = nextPending; 1211 backupWorkType = workAsType; 1212 backupAllWorkTypes = allWorkTypes; 1213 } 1214 } 1215 continue; 1216 } 1217 1218 // Only bypass the concurrent limit if we had preempted the job due to a higher 1219 // bias job. 1220 if (nextPending.lastEvaluatedBias <= jobStatus.lastEvaluatedBias 1221 && isPkgConcurrencyLimitedLocked(nextPending)) { 1222 continue; 1223 } 1224 1225 if (highestBiasJob == null 1226 || highestBiasJob.lastEvaluatedBias < nextPending.lastEvaluatedBias) { 1227 highestBiasJob = nextPending; 1228 } else { 1229 continue; 1230 } 1231 1232 // In this path, we pre-empted an existing job. We don't fully care about the 1233 // reserved slots. We should just run the highest bias job we can find, 1234 // though it would be ideal to use an available WorkType slot instead of 1235 // overloading slots. 1236 highBiasAllWorkTypes = getJobWorkTypes(nextPending); 1237 final int workAsType = mWorkCountTracker.canJobStart(highBiasAllWorkTypes); 1238 if (workAsType == WORK_TYPE_NONE) { 1239 // Just use the preempted job's work type since this new one is technically 1240 // replacing it anyway. 1241 highBiasWorkType = workType; 1242 } else { 1243 highBiasWorkType = workAsType; 1244 } 1245 } 1246 if (highestBiasJob != null) { 1247 if (DEBUG) { 1248 Slog.d(TAG, "Running job " + highestBiasJob + " as preemption"); 1249 } 1250 mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes); 1251 startJobLocked(worker, highestBiasJob, highBiasWorkType); 1252 } else { 1253 if (DEBUG) { 1254 Slog.d(TAG, "Couldn't find preemption job for uid " + worker.getPreferredUid()); 1255 } 1256 worker.clearPreferredUid(); 1257 if (backupJob != null) { 1258 if (DEBUG) { 1259 Slog.d(TAG, "Running job " + backupJob + " instead"); 1260 } 1261 mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes); 1262 startJobLocked(worker, backupJob, backupWorkType); 1263 } 1264 } 1265 } else if (pendingJobQueue.size() > 0) { 1266 updateCounterConfigLocked(); 1267 updateNonRunningPrioritiesLocked(pendingJobQueue, false); 1268 1269 // This slot is now free and we have pending jobs. Start the highest bias job we find. 1270 JobStatus highestBiasJob = null; 1271 int highBiasWorkType = workType; 1272 int highBiasAllWorkTypes = workType; 1273 1274 JobStatus nextPending; 1275 pendingJobQueue.resetIterator(); 1276 while ((nextPending = pendingJobQueue.next()) != null) { 1277 1278 if (mRunningJobs.contains(nextPending)) { 1279 // Should never happen. 1280 Slog.wtf(TAG, "Pending queue contained a running job"); 1281 if (DEBUG) { 1282 Slog.e(TAG, "Pending+running job: " + nextPending); 1283 } 1284 pendingJobQueue.remove(nextPending); 1285 continue; 1286 } 1287 1288 if (DEBUG && isSimilarJobRunningLocked(nextPending)) { 1289 Slog.w(TAG, "Already running similar job to: " + nextPending); 1290 } 1291 1292 if (isPkgConcurrencyLimitedLocked(nextPending)) { 1293 continue; 1294 } 1295 1296 final int allWorkTypes = getJobWorkTypes(nextPending); 1297 final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); 1298 if (workAsType == WORK_TYPE_NONE) { 1299 continue; 1300 } 1301 if (highestBiasJob == null 1302 || highestBiasJob.lastEvaluatedBias < nextPending.lastEvaluatedBias) { 1303 highestBiasJob = nextPending; 1304 highBiasWorkType = workAsType; 1305 highBiasAllWorkTypes = allWorkTypes; 1306 } 1307 } 1308 1309 if (highestBiasJob != null) { 1310 // This slot is free, and we haven't yet hit the limit on 1311 // concurrent jobs... we can just throw the job in to here. 1312 if (DEBUG) { 1313 Slog.d(TAG, "About to run job: " + highestBiasJob); 1314 } 1315 mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes); 1316 startJobLocked(worker, highestBiasJob, highBiasWorkType); 1317 } 1318 } 1319 1320 noteConcurrency(); 1321 } 1322 1323 /** 1324 * Returns {@code null} if the job can continue running and a non-null String if the job should 1325 * be stopped. The non-null String details the reason for stopping the job. A job will generally 1326 * be stopped if there are similar job types waiting to be run and stopping this job would allow 1327 * another job to run, or if system state suggests the job should stop. 1328 */ 1329 @Nullable 1330 @GuardedBy("mLock") shouldStopRunningJobLocked(@onNull JobServiceContext context)1331 String shouldStopRunningJobLocked(@NonNull JobServiceContext context) { 1332 final JobStatus js = context.getRunningJobLocked(); 1333 if (js == null) { 1334 // This can happen when we try to assign newly found pending jobs to contexts. 1335 return null; 1336 } 1337 1338 if (context.isWithinExecutionGuaranteeTime()) { 1339 return null; 1340 } 1341 1342 // We're over the minimum guaranteed runtime. Stop the job if we're over config limits, 1343 // there are pending jobs that could replace this one, or the device state is not conducive 1344 // to long runs. 1345 1346 if (mPowerManager.isPowerSaveMode()) { 1347 return "battery saver"; 1348 } 1349 if (mPowerManager.isDeviceIdleMode()) { 1350 return "deep doze"; 1351 } 1352 1353 // Update config in case memory usage has changed significantly. 1354 updateCounterConfigLocked(); 1355 1356 @WorkType final int workType = context.getRunningJobWorkType(); 1357 1358 if (mRunningJobs.size() > mWorkTypeConfig.getMaxTotal() 1359 || mWorkCountTracker.isOverTypeLimit(workType)) { 1360 return "too many jobs running"; 1361 } 1362 1363 final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue(); 1364 final int numPending = pendingJobQueue.size(); 1365 if (numPending == 0) { 1366 // All quiet. We can let this job run to completion. 1367 return null; 1368 } 1369 1370 // Only expedited jobs can replace expedited jobs. 1371 if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) { 1372 // Keep fg/bg user distinction. 1373 if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) { 1374 // Let any important bg user job replace a bg user expedited job. 1375 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_BGUSER_IMPORTANT) > 0) { 1376 return "blocking " + workTypeToString(WORK_TYPE_BGUSER_IMPORTANT) + " queue"; 1377 } 1378 // Let a fg user EJ preempt a bg user EJ (if able), but not the other way around. 1379 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0 1380 && mWorkCountTracker.canJobStart(WORK_TYPE_EJ, workType) 1381 != WORK_TYPE_NONE) { 1382 return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue"; 1383 } 1384 } else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) { 1385 return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue"; 1386 } else if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { 1387 // Try not to let TOP + EJ starve out other apps. 1388 int topEjCount = 0; 1389 for (int r = mRunningJobs.size() - 1; r >= 0; --r) { 1390 JobStatus j = mRunningJobs.valueAt(r); 1391 if (j.startedAsExpeditedJob && j.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { 1392 topEjCount++; 1393 } 1394 } 1395 if (topEjCount > .5 * mWorkTypeConfig.getMaxTotal()) { 1396 return "prevent top EJ dominance"; 1397 } 1398 } 1399 // No other pending EJs. Return null so we don't let regular jobs preempt an EJ. 1400 return null; 1401 } 1402 1403 // Easy check. If there are pending jobs of the same work type, then we know that 1404 // something will replace this. 1405 if (mWorkCountTracker.getPendingJobCount(workType) > 0) { 1406 return "blocking " + workTypeToString(workType) + " queue"; 1407 } 1408 1409 // Harder check. We need to see if a different work type can replace this job. 1410 int remainingWorkTypes = ALL_WORK_TYPES; 1411 JobStatus pending; 1412 pendingJobQueue.resetIterator(); 1413 while ((pending = pendingJobQueue.next()) != null) { 1414 final int workTypes = getJobWorkTypes(pending); 1415 if ((workTypes & remainingWorkTypes) > 0 1416 && mWorkCountTracker.canJobStart(workTypes, workType) != WORK_TYPE_NONE) { 1417 return "blocking other pending jobs"; 1418 } 1419 1420 remainingWorkTypes = remainingWorkTypes & ~workTypes; 1421 if (remainingWorkTypes == 0) { 1422 break; 1423 } 1424 } 1425 1426 return null; 1427 } 1428 1429 @GuardedBy("mLock") executeTimeoutCommandLocked(PrintWriter pw, String pkgName, int userId, boolean hasJobId, int jobId)1430 boolean executeTimeoutCommandLocked(PrintWriter pw, String pkgName, int userId, 1431 boolean hasJobId, int jobId) { 1432 boolean foundSome = false; 1433 for (int i = 0; i < mActiveServices.size(); i++) { 1434 final JobServiceContext jc = mActiveServices.get(i); 1435 final JobStatus js = jc.getRunningJobLocked(); 1436 if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) { 1437 foundSome = true; 1438 pw.print("Timing out: "); 1439 js.printUniqueId(pw); 1440 pw.print(" "); 1441 pw.println(js.getServiceComponent().flattenToShortString()); 1442 } 1443 } 1444 return foundSome; 1445 } 1446 1447 @NonNull createNewJobServiceContext()1448 private JobServiceContext createNewJobServiceContext() { 1449 return new JobServiceContext(mService, this, 1450 IBatteryStats.Stub.asInterface( 1451 ServiceManager.getService(BatteryStats.SERVICE_NAME)), 1452 mService.mJobPackageTracker, mContext.getMainLooper()); 1453 } 1454 1455 @GuardedBy("mLock") printPendingQueueLocked()1456 private String printPendingQueueLocked() { 1457 StringBuilder s = new StringBuilder("Pending queue: "); 1458 PendingJobQueue pendingJobQueue = mService.getPendingJobQueue(); 1459 JobStatus js; 1460 pendingJobQueue.resetIterator(); 1461 while ((js = pendingJobQueue.next()) != null) { 1462 s.append("(") 1463 .append(js.getJob().getId()) 1464 .append(", ") 1465 .append(js.getUid()) 1466 .append(") "); 1467 } 1468 return s.toString(); 1469 } 1470 printAssignments(String header, Collection<ContextAssignment>... list)1471 private static String printAssignments(String header, Collection<ContextAssignment>... list) { 1472 final StringBuilder s = new StringBuilder(header + ": "); 1473 for (int l = 0; l < list.length; ++l) { 1474 final Collection<ContextAssignment> assignments = list[l]; 1475 int c = 0; 1476 for (final ContextAssignment assignment : assignments) { 1477 final JobStatus job = assignment.newJob == null 1478 ? assignment.context.getRunningJobLocked() : assignment.newJob; 1479 1480 if (l > 0 || c > 0) { 1481 s.append(" "); 1482 } 1483 s.append("(").append(assignment.context.getId()).append("="); 1484 if (job == null) { 1485 s.append("nothing"); 1486 } else { 1487 s.append(job.getJobId()).append("/").append(job.getUid()); 1488 } 1489 s.append(")"); 1490 c++; 1491 } 1492 } 1493 return s.toString(); 1494 } 1495 1496 @GuardedBy("mLock") updateConfigLocked()1497 void updateConfigLocked() { 1498 DeviceConfig.Properties properties = 1499 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER); 1500 1501 mScreenOffAdjustmentDelayMs = properties.getLong( 1502 KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS); 1503 1504 CONFIG_LIMITS_SCREEN_ON.normal.update(properties); 1505 CONFIG_LIMITS_SCREEN_ON.moderate.update(properties); 1506 CONFIG_LIMITS_SCREEN_ON.low.update(properties); 1507 CONFIG_LIMITS_SCREEN_ON.critical.update(properties); 1508 1509 CONFIG_LIMITS_SCREEN_OFF.normal.update(properties); 1510 CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties); 1511 CONFIG_LIMITS_SCREEN_OFF.low.update(properties); 1512 CONFIG_LIMITS_SCREEN_OFF.critical.update(properties); 1513 1514 // Package concurrency limits must in the range [1, STANDARD_CONCURRENCY_LIMIT]. 1515 mPkgConcurrencyLimitEj = Math.max(1, Math.min(STANDARD_CONCURRENCY_LIMIT, 1516 properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ))); 1517 mPkgConcurrencyLimitRegular = Math.max(1, Math.min(STANDARD_CONCURRENCY_LIMIT, 1518 properties.getInt( 1519 KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR))); 1520 } 1521 1522 @GuardedBy("mLock") dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime)1523 public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) { 1524 pw.println("Concurrency:"); 1525 1526 pw.increaseIndent(); 1527 try { 1528 pw.println("Configuration:"); 1529 pw.increaseIndent(); 1530 pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println(); 1531 pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println(); 1532 pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println(); 1533 pw.println(); 1534 CONFIG_LIMITS_SCREEN_ON.normal.dump(pw); 1535 pw.println(); 1536 CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw); 1537 pw.println(); 1538 CONFIG_LIMITS_SCREEN_ON.low.dump(pw); 1539 pw.println(); 1540 CONFIG_LIMITS_SCREEN_ON.critical.dump(pw); 1541 pw.println(); 1542 CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw); 1543 pw.println(); 1544 CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw); 1545 pw.println(); 1546 CONFIG_LIMITS_SCREEN_OFF.low.dump(pw); 1547 pw.println(); 1548 CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw); 1549 pw.println(); 1550 pw.decreaseIndent(); 1551 1552 pw.print("Screen state: current "); 1553 pw.print(mCurrentInteractiveState ? "ON" : "OFF"); 1554 pw.print(" effective "); 1555 pw.print(mEffectiveInteractiveState ? "ON" : "OFF"); 1556 pw.println(); 1557 1558 pw.print("Last screen ON: "); 1559 TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now); 1560 pw.println(); 1561 1562 pw.print("Last screen OFF: "); 1563 TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now); 1564 pw.println(); 1565 1566 pw.println(); 1567 1568 pw.print("Current work counts: "); 1569 pw.println(mWorkCountTracker); 1570 1571 pw.println(); 1572 1573 pw.print("mLastMemoryTrimLevel: "); 1574 pw.println(mLastMemoryTrimLevel); 1575 pw.println(); 1576 1577 pw.println("Active Package stats:"); 1578 pw.increaseIndent(); 1579 mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw)); 1580 pw.decreaseIndent(); 1581 pw.println(); 1582 1583 pw.print("User Grace Period: "); 1584 pw.println(mGracePeriodObserver.mGracePeriodExpiration); 1585 pw.println(); 1586 1587 mStatLogger.dump(pw); 1588 } finally { 1589 pw.decreaseIndent(); 1590 } 1591 } 1592 1593 @GuardedBy("mLock") dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed, long nowUptime)1594 void dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate, 1595 long nowElapsed, long nowUptime) { 1596 pw.println("Active jobs:"); 1597 pw.increaseIndent(); 1598 if (mActiveServices.size() == 0) { 1599 pw.println("N/A"); 1600 } 1601 for (int i = 0; i < mActiveServices.size(); i++) { 1602 JobServiceContext jsc = mActiveServices.get(i); 1603 final JobStatus job = jsc.getRunningJobLocked(); 1604 1605 if (job != null && !predicate.test(job)) { 1606 continue; 1607 } 1608 1609 pw.print("Slot #"); pw.print(i); 1610 pw.print("(ID="); pw.print(jsc.getId()); pw.print("): "); 1611 jsc.dumpLocked(pw, nowElapsed); 1612 1613 if (job != null) { 1614 pw.increaseIndent(); 1615 1616 pw.increaseIndent(); 1617 job.dump(pw, false, nowElapsed); 1618 pw.decreaseIndent(); 1619 1620 pw.print("Evaluated bias: "); 1621 pw.println(JobInfo.getBiasString(job.lastEvaluatedBias)); 1622 1623 pw.print("Active at "); 1624 TimeUtils.formatDuration(job.madeActive - nowUptime, pw); 1625 pw.print(", pending for "); 1626 TimeUtils.formatDuration(job.madeActive - job.madePending, pw); 1627 pw.decreaseIndent(); 1628 pw.println(); 1629 } 1630 } 1631 pw.decreaseIndent(); 1632 1633 pw.println(); 1634 pw.print("Idle contexts ("); 1635 pw.print(mIdleContexts.size()); 1636 pw.println("):"); 1637 pw.increaseIndent(); 1638 for (int i = 0; i < mIdleContexts.size(); i++) { 1639 JobServiceContext jsc = mIdleContexts.valueAt(i); 1640 1641 pw.print("ID="); pw.print(jsc.getId()); pw.print(": "); 1642 jsc.dumpLocked(pw, nowElapsed); 1643 } 1644 pw.decreaseIndent(); 1645 1646 if (mNumDroppedContexts > 0) { 1647 pw.println(); 1648 pw.print("Dropped "); 1649 pw.print(mNumDroppedContexts); 1650 pw.println(" contexts"); 1651 } 1652 } 1653 dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime)1654 public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) { 1655 final long token = proto.start(tag); 1656 1657 proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE_STATE, mCurrentInteractiveState); 1658 proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE_STATE, 1659 mEffectiveInteractiveState); 1660 1661 proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS, 1662 nowRealtime - mLastScreenOnRealtime); 1663 proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS, 1664 nowRealtime - mLastScreenOffRealtime); 1665 1666 proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel); 1667 1668 mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS); 1669 1670 proto.end(token); 1671 } 1672 1673 /** 1674 * Decides whether a job is from the current foreground user or the equivalent. 1675 */ 1676 @VisibleForTesting shouldRunAsFgUserJob(JobStatus job)1677 boolean shouldRunAsFgUserJob(JobStatus job) { 1678 if (!mShouldRestrictBgUser) return true; 1679 int userId = job.getSourceUserId(); 1680 UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); 1681 UserInfo userInfo = um.getUserInfo(userId); 1682 1683 // If the user has a parent user (e.g. a work profile of another user), the user should be 1684 // treated equivalent as its parent user. 1685 if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID 1686 && userInfo.profileGroupId != userId) { 1687 userId = userInfo.profileGroupId; 1688 userInfo = um.getUserInfo(userId); 1689 } 1690 1691 int currentUser = LocalServices.getService(ActivityManagerInternal.class) 1692 .getCurrentUserId(); 1693 // A user is treated as foreground user if any of the followings is true: 1694 // 1. The user is current user 1695 // 2. The user is primary user 1696 // 3. The user's grace period has not expired 1697 return currentUser == userId || userInfo.isPrimary() 1698 || mGracePeriodObserver.isWithinGracePeriodForUser(userId); 1699 } 1700 getJobWorkTypes(@onNull JobStatus js)1701 int getJobWorkTypes(@NonNull JobStatus js) { 1702 int classification = 0; 1703 1704 if (shouldRunAsFgUserJob(js)) { 1705 if (js.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) { 1706 classification |= WORK_TYPE_TOP; 1707 } else if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE) { 1708 classification |= WORK_TYPE_FGS; 1709 } else { 1710 classification |= WORK_TYPE_BG; 1711 } 1712 1713 if (js.shouldTreatAsExpeditedJob()) { 1714 classification |= WORK_TYPE_EJ; 1715 } 1716 } else { 1717 if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE 1718 || js.shouldTreatAsExpeditedJob()) { 1719 classification |= WORK_TYPE_BGUSER_IMPORTANT; 1720 } 1721 // BGUSER_IMPORTANT jobs can also run as BGUSER jobs, so not an 'else' here. 1722 classification |= WORK_TYPE_BGUSER; 1723 } 1724 1725 return classification; 1726 } 1727 1728 @VisibleForTesting 1729 static class WorkTypeConfig { 1730 @VisibleForTesting 1731 static final String KEY_PREFIX_MAX_TOTAL = CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_"; 1732 private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_"; 1733 private static final String KEY_PREFIX_MAX_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_fgs_"; 1734 private static final String KEY_PREFIX_MAX_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "max_ej_"; 1735 private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_"; 1736 private static final String KEY_PREFIX_MAX_BGUSER = 1737 CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_"; 1738 private static final String KEY_PREFIX_MAX_BGUSER_IMPORTANT = 1739 CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_important_"; 1740 private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_"; 1741 private static final String KEY_PREFIX_MIN_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "min_fgs_"; 1742 private static final String KEY_PREFIX_MIN_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "min_ej_"; 1743 private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_"; 1744 private static final String KEY_PREFIX_MIN_BGUSER = 1745 CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_"; 1746 private static final String KEY_PREFIX_MIN_BGUSER_IMPORTANT = 1747 CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_important_"; 1748 private final String mConfigIdentifier; 1749 1750 private int mMaxTotal; 1751 private final SparseIntArray mMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES); 1752 private final SparseIntArray mMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES); 1753 private final int mDefaultMaxTotal; 1754 private final SparseIntArray mDefaultMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES); 1755 private final SparseIntArray mDefaultMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES); 1756 WorkTypeConfig(@onNull String configIdentifier, int defaultMaxTotal, List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax)1757 WorkTypeConfig(@NonNull String configIdentifier, int defaultMaxTotal, 1758 List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax) { 1759 mConfigIdentifier = configIdentifier; 1760 mDefaultMaxTotal = mMaxTotal = Math.min(defaultMaxTotal, STANDARD_CONCURRENCY_LIMIT); 1761 int numReserved = 0; 1762 for (int i = defaultMin.size() - 1; i >= 0; --i) { 1763 mDefaultMinReservedSlots.put(defaultMin.get(i).first, defaultMin.get(i).second); 1764 numReserved += defaultMin.get(i).second; 1765 } 1766 if (mDefaultMaxTotal < 0 || numReserved > mDefaultMaxTotal) { 1767 // We only create new configs on boot, so this should trigger during development 1768 // (before the code gets checked in), so this makes sure the hard-coded defaults 1769 // make sense. DeviceConfig values will be handled gracefully in update(). 1770 throw new IllegalArgumentException("Invalid default config: t=" + defaultMaxTotal 1771 + " min=" + defaultMin + " max=" + defaultMax); 1772 } 1773 for (int i = defaultMax.size() - 1; i >= 0; --i) { 1774 mDefaultMaxAllowedSlots.put(defaultMax.get(i).first, defaultMax.get(i).second); 1775 } 1776 update(new DeviceConfig.Properties.Builder( 1777 DeviceConfig.NAMESPACE_JOB_SCHEDULER).build()); 1778 } 1779 update(@onNull DeviceConfig.Properties properties)1780 void update(@NonNull DeviceConfig.Properties properties) { 1781 // Ensure total in the range [1, STANDARD_CONCURRENCY_LIMIT]. 1782 mMaxTotal = Math.max(1, Math.min(STANDARD_CONCURRENCY_LIMIT, 1783 properties.getInt(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mDefaultMaxTotal))); 1784 1785 mMaxAllowedSlots.clear(); 1786 // Ensure they're in the range [1, total]. 1787 final int maxTop = Math.max(1, Math.min(mMaxTotal, 1788 properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier, 1789 mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal)))); 1790 mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop); 1791 final int maxFgs = Math.max(1, Math.min(mMaxTotal, 1792 properties.getInt(KEY_PREFIX_MAX_FGS + mConfigIdentifier, 1793 mDefaultMaxAllowedSlots.get(WORK_TYPE_FGS, mMaxTotal)))); 1794 mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs); 1795 final int maxEj = Math.max(1, Math.min(mMaxTotal, 1796 properties.getInt(KEY_PREFIX_MAX_EJ + mConfigIdentifier, 1797 mDefaultMaxAllowedSlots.get(WORK_TYPE_EJ, mMaxTotal)))); 1798 mMaxAllowedSlots.put(WORK_TYPE_EJ, maxEj); 1799 final int maxBg = Math.max(1, Math.min(mMaxTotal, 1800 properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier, 1801 mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal)))); 1802 mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg); 1803 final int maxBgUserImp = Math.max(1, Math.min(mMaxTotal, 1804 properties.getInt(KEY_PREFIX_MAX_BGUSER_IMPORTANT + mConfigIdentifier, 1805 mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT, mMaxTotal)))); 1806 mMaxAllowedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, maxBgUserImp); 1807 final int maxBgUser = Math.max(1, Math.min(mMaxTotal, 1808 properties.getInt(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier, 1809 mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER, mMaxTotal)))); 1810 mMaxAllowedSlots.put(WORK_TYPE_BGUSER, maxBgUser); 1811 1812 int remaining = mMaxTotal; 1813 mMinReservedSlots.clear(); 1814 // Ensure top is in the range [1, min(maxTop, total)] 1815 final int minTop = Math.max(1, Math.min(Math.min(maxTop, mMaxTotal), 1816 properties.getInt(KEY_PREFIX_MIN_TOP + mConfigIdentifier, 1817 mDefaultMinReservedSlots.get(WORK_TYPE_TOP)))); 1818 mMinReservedSlots.put(WORK_TYPE_TOP, minTop); 1819 remaining -= minTop; 1820 // Ensure fgs is in the range [0, min(maxFgs, remaining)] 1821 final int minFgs = Math.max(0, Math.min(Math.min(maxFgs, remaining), 1822 properties.getInt(KEY_PREFIX_MIN_FGS + mConfigIdentifier, 1823 mDefaultMinReservedSlots.get(WORK_TYPE_FGS)))); 1824 mMinReservedSlots.put(WORK_TYPE_FGS, minFgs); 1825 remaining -= minFgs; 1826 // Ensure ej is in the range [0, min(maxEj, remaining)] 1827 final int minEj = Math.max(0, Math.min(Math.min(maxEj, remaining), 1828 properties.getInt(KEY_PREFIX_MIN_EJ + mConfigIdentifier, 1829 mDefaultMinReservedSlots.get(WORK_TYPE_EJ)))); 1830 mMinReservedSlots.put(WORK_TYPE_EJ, minEj); 1831 remaining -= minEj; 1832 // Ensure bg is in the range [0, min(maxBg, remaining)] 1833 final int minBg = Math.max(0, Math.min(Math.min(maxBg, remaining), 1834 properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier, 1835 mDefaultMinReservedSlots.get(WORK_TYPE_BG)))); 1836 mMinReservedSlots.put(WORK_TYPE_BG, minBg); 1837 remaining -= minBg; 1838 // Ensure bg user imp is in the range [0, min(maxBgUserImp, remaining)] 1839 final int minBgUserImp = Math.max(0, Math.min(Math.min(maxBgUserImp, remaining), 1840 properties.getInt(KEY_PREFIX_MIN_BGUSER_IMPORTANT + mConfigIdentifier, 1841 mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT, 0)))); 1842 mMinReservedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, minBgUserImp); 1843 // Ensure bg user is in the range [0, min(maxBgUser, remaining)] 1844 final int minBgUser = Math.max(0, Math.min(Math.min(maxBgUser, remaining), 1845 properties.getInt(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier, 1846 mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER, 0)))); 1847 mMinReservedSlots.put(WORK_TYPE_BGUSER, minBgUser); 1848 } 1849 getMaxTotal()1850 int getMaxTotal() { 1851 return mMaxTotal; 1852 } 1853 getMax(@orkType int workType)1854 int getMax(@WorkType int workType) { 1855 return mMaxAllowedSlots.get(workType, mMaxTotal); 1856 } 1857 getMinReserved(@orkType int workType)1858 int getMinReserved(@WorkType int workType) { 1859 return mMinReservedSlots.get(workType); 1860 } 1861 dump(IndentingPrintWriter pw)1862 void dump(IndentingPrintWriter pw) { 1863 pw.print(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mMaxTotal).println(); 1864 pw.print(KEY_PREFIX_MIN_TOP + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_TOP)) 1865 .println(); 1866 pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP)) 1867 .println(); 1868 pw.print(KEY_PREFIX_MIN_FGS + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_FGS)) 1869 .println(); 1870 pw.print(KEY_PREFIX_MAX_FGS + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_FGS)) 1871 .println(); 1872 pw.print(KEY_PREFIX_MIN_EJ + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_EJ)) 1873 .println(); 1874 pw.print(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_EJ)) 1875 .println(); 1876 pw.print(KEY_PREFIX_MIN_BG + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_BG)) 1877 .println(); 1878 pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG)) 1879 .println(); 1880 pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier, 1881 mMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println(); 1882 pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier, 1883 mMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println(); 1884 pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier, 1885 mMinReservedSlots.get(WORK_TYPE_BGUSER)).println(); 1886 pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier, 1887 mMaxAllowedSlots.get(WORK_TYPE_BGUSER)).println(); 1888 } 1889 } 1890 1891 /** {@link WorkTypeConfig} for each memory trim level. */ 1892 static class WorkConfigLimitsPerMemoryTrimLevel { 1893 public final WorkTypeConfig normal; 1894 public final WorkTypeConfig moderate; 1895 public final WorkTypeConfig low; 1896 public final WorkTypeConfig critical; 1897 WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate, WorkTypeConfig low, WorkTypeConfig critical)1898 WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate, 1899 WorkTypeConfig low, WorkTypeConfig critical) { 1900 this.normal = normal; 1901 this.moderate = moderate; 1902 this.low = low; 1903 this.critical = critical; 1904 } 1905 } 1906 1907 /** 1908 * This class keeps the track of when a user's grace period expires. 1909 */ 1910 @VisibleForTesting 1911 static class GracePeriodObserver extends UserSwitchObserver { 1912 // Key is UserId and Value is the time when grace period expires 1913 @VisibleForTesting 1914 final SparseLongArray mGracePeriodExpiration = new SparseLongArray(); 1915 private int mCurrentUserId; 1916 @VisibleForTesting 1917 int mGracePeriod; 1918 private final UserManagerInternal mUserManagerInternal; 1919 final Object mLock = new Object(); 1920 1921 GracePeriodObserver(Context context)1922 GracePeriodObserver(Context context) { 1923 mCurrentUserId = LocalServices.getService(ActivityManagerInternal.class) 1924 .getCurrentUserId(); 1925 mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); 1926 mGracePeriod = Math.max(0, context.getResources().getInteger( 1927 R.integer.config_jobSchedulerUserGracePeriod)); 1928 } 1929 1930 @Override onUserSwitchComplete(int newUserId)1931 public void onUserSwitchComplete(int newUserId) { 1932 final long expiration = sElapsedRealtimeClock.millis() + mGracePeriod; 1933 synchronized (mLock) { 1934 if (mCurrentUserId != UserHandle.USER_NULL 1935 && mUserManagerInternal.exists(mCurrentUserId)) { 1936 mGracePeriodExpiration.append(mCurrentUserId, expiration); 1937 } 1938 mGracePeriodExpiration.delete(newUserId); 1939 mCurrentUserId = newUserId; 1940 } 1941 } 1942 onUserRemoved(int userId)1943 void onUserRemoved(int userId) { 1944 synchronized (mLock) { 1945 mGracePeriodExpiration.delete(userId); 1946 } 1947 } 1948 1949 @VisibleForTesting isWithinGracePeriodForUser(int userId)1950 public boolean isWithinGracePeriodForUser(int userId) { 1951 synchronized (mLock) { 1952 return userId == mCurrentUserId 1953 || sElapsedRealtimeClock.millis() 1954 < mGracePeriodExpiration.get(userId, Long.MAX_VALUE); 1955 } 1956 } 1957 } 1958 1959 /** 1960 * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs 1961 * are running/pending, how many more job can start. 1962 * 1963 * Extracted for testing and logging. 1964 */ 1965 @VisibleForTesting 1966 static class WorkCountTracker { 1967 private int mConfigMaxTotal; 1968 private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES); 1969 private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES); 1970 private final SparseIntArray mRecycledReserved = new SparseIntArray(NUM_WORK_TYPES); 1971 1972 /** 1973 * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't 1974 * enough ready jobs of a type to take up all of the desired reserved slots. 1975 */ 1976 private final SparseIntArray mNumActuallyReservedSlots = new SparseIntArray(NUM_WORK_TYPES); 1977 private final SparseIntArray mNumPendingJobs = new SparseIntArray(NUM_WORK_TYPES); 1978 private final SparseIntArray mNumRunningJobs = new SparseIntArray(NUM_WORK_TYPES); 1979 private final SparseIntArray mNumStartingJobs = new SparseIntArray(NUM_WORK_TYPES); 1980 private int mNumUnspecializedRemaining = 0; 1981 setConfig(@onNull WorkTypeConfig workTypeConfig)1982 void setConfig(@NonNull WorkTypeConfig workTypeConfig) { 1983 mConfigMaxTotal = workTypeConfig.getMaxTotal(); 1984 for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { 1985 mConfigNumReservedSlots.put(workType, workTypeConfig.getMinReserved(workType)); 1986 mConfigAbsoluteMaxSlots.put(workType, workTypeConfig.getMax(workType)); 1987 } 1988 1989 mNumUnspecializedRemaining = mConfigMaxTotal; 1990 for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) { 1991 mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i), 1992 mConfigNumReservedSlots.get(mNumRunningJobs.keyAt(i))); 1993 } 1994 } 1995 resetCounts()1996 void resetCounts() { 1997 mNumActuallyReservedSlots.clear(); 1998 mNumPendingJobs.clear(); 1999 mNumRunningJobs.clear(); 2000 resetStagingCount(); 2001 } 2002 resetStagingCount()2003 void resetStagingCount() { 2004 mNumStartingJobs.clear(); 2005 } 2006 incrementRunningJobCount(@orkType int workType)2007 void incrementRunningJobCount(@WorkType int workType) { 2008 mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1); 2009 } 2010 incrementPendingJobCount(int workTypes)2011 void incrementPendingJobCount(int workTypes) { 2012 adjustPendingJobCount(workTypes, true); 2013 } 2014 decrementPendingJobCount(int workTypes)2015 void decrementPendingJobCount(int workTypes) { 2016 if (adjustPendingJobCount(workTypes, false) > 1) { 2017 // We don't need to adjust reservations if only one work type was modified 2018 // because that work type is the one we're using. 2019 2020 for (int workType = 1; workType <= workTypes; workType <<= 1) { 2021 if ((workType & workTypes) == workType) { 2022 maybeAdjustReservations(workType); 2023 } 2024 } 2025 } 2026 } 2027 2028 /** Returns the number of WorkTypes that were modified. */ adjustPendingJobCount(int workTypes, boolean add)2029 private int adjustPendingJobCount(int workTypes, boolean add) { 2030 final int adj = add ? 1 : -1; 2031 2032 int numAdj = 0; 2033 // We don't know which type we'll classify the job as when we run it yet, so make sure 2034 // we have space in all applicable slots. 2035 for (int workType = 1; workType <= workTypes; workType <<= 1) { 2036 if ((workTypes & workType) == workType) { 2037 mNumPendingJobs.put(workType, mNumPendingJobs.get(workType) + adj); 2038 numAdj++; 2039 } 2040 } 2041 2042 return numAdj; 2043 } 2044 stageJob(@orkType int workType, int allWorkTypes)2045 void stageJob(@WorkType int workType, int allWorkTypes) { 2046 final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1; 2047 mNumStartingJobs.put(workType, newNumStartingJobs); 2048 decrementPendingJobCount(allWorkTypes); 2049 if (newNumStartingJobs + mNumRunningJobs.get(workType) 2050 > mNumActuallyReservedSlots.get(workType)) { 2051 mNumUnspecializedRemaining--; 2052 } 2053 } 2054 onStagedJobFailed(@orkType int workType)2055 void onStagedJobFailed(@WorkType int workType) { 2056 final int oldNumStartingJobs = mNumStartingJobs.get(workType); 2057 if (oldNumStartingJobs == 0) { 2058 Slog.e(TAG, "# staged jobs for " + workType + " went negative."); 2059 // We are in a bad state. We will eventually recover when the pending list is 2060 // regenerated. 2061 return; 2062 } 2063 mNumStartingJobs.put(workType, oldNumStartingJobs - 1); 2064 maybeAdjustReservations(workType); 2065 } 2066 maybeAdjustReservations(@orkType int workType)2067 private void maybeAdjustReservations(@WorkType int workType) { 2068 // Always make sure we reserve the minimum number of slots in case new jobs become ready 2069 // soon. 2070 final int numRemainingForType = Math.max(mConfigNumReservedSlots.get(workType), 2071 mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType) 2072 + mNumPendingJobs.get(workType)); 2073 if (numRemainingForType < mNumActuallyReservedSlots.get(workType)) { 2074 // We've run all jobs for this type. Let another type use it now. 2075 mNumActuallyReservedSlots.put(workType, numRemainingForType); 2076 int assignWorkType = WORK_TYPE_NONE; 2077 for (int i = 0; i < mNumActuallyReservedSlots.size(); ++i) { 2078 int wt = mNumActuallyReservedSlots.keyAt(i); 2079 if (assignWorkType == WORK_TYPE_NONE || wt < assignWorkType) { 2080 // Try to give this slot to the highest bias one within its limits. 2081 int total = mNumRunningJobs.get(wt) + mNumStartingJobs.get(wt) 2082 + mNumPendingJobs.get(wt); 2083 if (mNumActuallyReservedSlots.valueAt(i) < mConfigAbsoluteMaxSlots.get(wt) 2084 && total > mNumActuallyReservedSlots.valueAt(i)) { 2085 assignWorkType = wt; 2086 } 2087 } 2088 } 2089 if (assignWorkType != WORK_TYPE_NONE) { 2090 mNumActuallyReservedSlots.put(assignWorkType, 2091 mNumActuallyReservedSlots.get(assignWorkType) + 1); 2092 } else { 2093 mNumUnspecializedRemaining++; 2094 } 2095 } 2096 } 2097 onJobStarted(@orkType int workType)2098 void onJobStarted(@WorkType int workType) { 2099 mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1); 2100 final int oldNumStartingJobs = mNumStartingJobs.get(workType); 2101 if (oldNumStartingJobs == 0) { 2102 Slog.e(TAG, "# stated jobs for " + workType + " went negative."); 2103 // We are in a bad state. We will eventually recover when the pending list is 2104 // regenerated. For now, only modify the running count. 2105 } else { 2106 mNumStartingJobs.put(workType, oldNumStartingJobs - 1); 2107 } 2108 } 2109 onJobFinished(@orkType int workType)2110 void onJobFinished(@WorkType int workType) { 2111 final int newNumRunningJobs = mNumRunningJobs.get(workType) - 1; 2112 if (newNumRunningJobs < 0) { 2113 // We are in a bad state. We will eventually recover when the pending list is 2114 // regenerated. 2115 Slog.e(TAG, "# running jobs for " + workType + " went negative."); 2116 return; 2117 } 2118 mNumRunningJobs.put(workType, newNumRunningJobs); 2119 maybeAdjustReservations(workType); 2120 } 2121 onCountDone()2122 void onCountDone() { 2123 // Calculate how many slots to reserve for each work type. "Unspecialized" slots will 2124 // be reserved for higher importance types first (ie. top before ej before bg). 2125 // Steps: 2126 // 1. Account for slots for already running jobs 2127 // 2. Use remaining unaccounted slots to try and ensure minimum reserved slots 2128 // 3. Allocate remaining up to max, based on importance 2129 2130 mNumUnspecializedRemaining = mConfigMaxTotal; 2131 2132 // Step 1 2133 for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { 2134 int run = mNumRunningJobs.get(workType); 2135 mRecycledReserved.put(workType, run); 2136 mNumUnspecializedRemaining -= run; 2137 } 2138 2139 // Step 2 2140 for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { 2141 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType); 2142 int res = mRecycledReserved.get(workType); 2143 int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining, 2144 Math.min(num, mConfigNumReservedSlots.get(workType) - res))); 2145 res += fillUp; 2146 mRecycledReserved.put(workType, res); 2147 mNumUnspecializedRemaining -= fillUp; 2148 } 2149 2150 // Step 3 2151 for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) { 2152 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType); 2153 int res = mRecycledReserved.get(workType); 2154 int unspecializedAssigned = Math.max(0, 2155 Math.min(mNumUnspecializedRemaining, 2156 Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res)); 2157 mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned); 2158 mNumUnspecializedRemaining -= unspecializedAssigned; 2159 } 2160 } 2161 canJobStart(int workTypes)2162 int canJobStart(int workTypes) { 2163 for (int workType = 1; workType <= workTypes; workType <<= 1) { 2164 if ((workTypes & workType) == workType) { 2165 final int maxAllowed = Math.min( 2166 mConfigAbsoluteMaxSlots.get(workType), 2167 mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining); 2168 if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType) 2169 < maxAllowed) { 2170 return workType; 2171 } 2172 } 2173 } 2174 return WORK_TYPE_NONE; 2175 } 2176 canJobStart(int workTypes, @WorkType int replacingWorkType)2177 int canJobStart(int workTypes, @WorkType int replacingWorkType) { 2178 final boolean changedNums; 2179 int oldNumRunning = mNumRunningJobs.get(replacingWorkType); 2180 if (replacingWorkType != WORK_TYPE_NONE && oldNumRunning > 0) { 2181 mNumRunningJobs.put(replacingWorkType, oldNumRunning - 1); 2182 // Lazy implementation to avoid lots of processing. Best way would be to go 2183 // through the whole process of adjusting reservations, but the processing cost 2184 // is likely not worth it. 2185 mNumUnspecializedRemaining++; 2186 changedNums = true; 2187 } else { 2188 changedNums = false; 2189 } 2190 2191 final int ret = canJobStart(workTypes); 2192 if (changedNums) { 2193 mNumRunningJobs.put(replacingWorkType, oldNumRunning); 2194 mNumUnspecializedRemaining--; 2195 } 2196 return ret; 2197 } 2198 getPendingJobCount(@orkType final int workType)2199 int getPendingJobCount(@WorkType final int workType) { 2200 return mNumPendingJobs.get(workType, 0); 2201 } 2202 getRunningJobCount(@orkType final int workType)2203 int getRunningJobCount(@WorkType final int workType) { 2204 return mNumRunningJobs.get(workType, 0); 2205 } 2206 isOverTypeLimit(@orkType final int workType)2207 boolean isOverTypeLimit(@WorkType final int workType) { 2208 return getRunningJobCount(workType) > mConfigAbsoluteMaxSlots.get(workType); 2209 } 2210 toString()2211 public String toString() { 2212 StringBuilder sb = new StringBuilder(); 2213 2214 sb.append("Config={"); 2215 sb.append("tot=").append(mConfigMaxTotal); 2216 sb.append(" mins="); 2217 sb.append(mConfigNumReservedSlots); 2218 sb.append(" maxs="); 2219 sb.append(mConfigAbsoluteMaxSlots); 2220 sb.append("}"); 2221 2222 sb.append(", act res=").append(mNumActuallyReservedSlots); 2223 sb.append(", Pending=").append(mNumPendingJobs); 2224 sb.append(", Running=").append(mNumRunningJobs); 2225 sb.append(", Staged=").append(mNumStartingJobs); 2226 sb.append(", # unspecialized remaining=").append(mNumUnspecializedRemaining); 2227 2228 return sb.toString(); 2229 } 2230 } 2231 2232 @VisibleForTesting 2233 static class PackageStats { 2234 public int userId; 2235 public String packageName; 2236 public int numRunningEj; 2237 public int numRunningRegular; 2238 public int numStagedEj; 2239 public int numStagedRegular; 2240 setPackage(int userId, @NonNull String packageName)2241 private void setPackage(int userId, @NonNull String packageName) { 2242 this.userId = userId; 2243 this.packageName = packageName; 2244 numRunningEj = numRunningRegular = 0; 2245 resetStagedCount(); 2246 } 2247 resetStagedCount()2248 private void resetStagedCount() { 2249 numStagedEj = numStagedRegular = 0; 2250 } 2251 adjustRunningCount(boolean add, boolean forEj)2252 private void adjustRunningCount(boolean add, boolean forEj) { 2253 if (forEj) { 2254 numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1)); 2255 } else { 2256 numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1)); 2257 } 2258 } 2259 adjustStagedCount(boolean add, boolean forEj)2260 private void adjustStagedCount(boolean add, boolean forEj) { 2261 if (forEj) { 2262 numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1)); 2263 } else { 2264 numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1)); 2265 } 2266 } 2267 2268 @GuardedBy("mLock") dumpLocked(IndentingPrintWriter pw)2269 private void dumpLocked(IndentingPrintWriter pw) { 2270 pw.print("PackageStats{"); 2271 pw.print(userId); 2272 pw.print("-"); 2273 pw.print(packageName); 2274 pw.print("#runEJ", numRunningEj); 2275 pw.print("#runReg", numRunningRegular); 2276 pw.print("#stagedEJ", numStagedEj); 2277 pw.print("#stagedReg", numStagedRegular); 2278 pw.println("}"); 2279 } 2280 } 2281 2282 private static final class ContextAssignment { 2283 public JobServiceContext context; 2284 public int preferredUid = JobServiceContext.NO_PREFERRED_UID; 2285 public int workType = WORK_TYPE_NONE; 2286 public String preemptReason; 2287 public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; 2288 public String shouldStopJobReason; 2289 public JobStatus newJob; 2290 public int newWorkType = WORK_TYPE_NONE; 2291 clear()2292 void clear() { 2293 context = null; 2294 preferredUid = JobServiceContext.NO_PREFERRED_UID; 2295 workType = WORK_TYPE_NONE; 2296 preemptReason = null; 2297 preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; 2298 shouldStopJobReason = null; 2299 newJob = null; 2300 newWorkType = WORK_TYPE_NONE; 2301 } 2302 } 2303 2304 // TESTING HELPERS 2305 2306 @VisibleForTesting addRunningJobForTesting(@onNull JobStatus job)2307 void addRunningJobForTesting(@NonNull JobStatus job) { 2308 mRunningJobs.add(job); 2309 final PackageStats packageStats = 2310 getPackageStatsForTesting(job.getSourceUserId(), job.getSourcePackageName()); 2311 packageStats.adjustRunningCount(true, job.shouldTreatAsExpeditedJob()); 2312 } 2313 2314 @VisibleForTesting getPackageConcurrencyLimitEj()2315 int getPackageConcurrencyLimitEj() { 2316 return mPkgConcurrencyLimitEj; 2317 } 2318 getPackageConcurrencyLimitRegular()2319 int getPackageConcurrencyLimitRegular() { 2320 return mPkgConcurrencyLimitRegular; 2321 } 2322 2323 /** Gets the {@link PackageStats} object for the app and saves it for testing use. */ 2324 @NonNull 2325 @VisibleForTesting getPackageStatsForTesting(int userId, @NonNull String packageName)2326 PackageStats getPackageStatsForTesting(int userId, @NonNull String packageName) { 2327 final PackageStats packageStats = getPkgStatsLocked(userId, packageName); 2328 mActivePkgStats.add(userId, packageName, packageStats); 2329 return packageStats; 2330 } 2331 } 2332