diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index a2f29c9e48fa1..66977d66e0a5a 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -30,10 +30,13 @@ import android.content.IntentFilter; import android.os.BatteryManager; import android.os.Environment; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.storage.StorageManager; import android.util.ArraySet; import android.util.Log; +import com.android.server.pm.dex.DexManager; + import java.io.File; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.TimeUnit; @@ -59,21 +62,33 @@ public class BackgroundDexOptService extends JobService { "android", BackgroundDexOptService.class.getName()); + // Possible return codes of individual optimization steps. + + // Optimizations finished. All packages were processed. + private static final int OPTIMIZE_PROCESSED = 0; + // Optimizations should continue. Issued after checking the scheduler, disk space or battery. + private static final int OPTIMIZE_CONTINUE = 1; + // Optimizations should be aborted. Job scheduler requested it. + private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2; + // Optimizations should be aborted. No space left on device. + private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3; + /** * Set of failed packages remembered across job runs. */ - static final ArraySet sFailedPackageNames = new ArraySet(); + static final ArraySet sFailedPackageNamesPrimary = new ArraySet(); + static final ArraySet sFailedPackageNamesSecondary = new ArraySet(); /** * Atomics set to true if the JobScheduler requests an abort. */ - final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false); - final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false); + private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false); + private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false); /** * Atomic set to true if one job should exit early because another job was started. */ - final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); + private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); private final File mDataDir = Environment.getDataDirectory(); @@ -104,8 +119,11 @@ public class BackgroundDexOptService extends JobService { // The idle maintanance job skips packages which previously failed to // compile. The given package has changed and may successfully compile // now. Remove it from the list of known failing packages. - synchronized (sFailedPackageNames) { - sFailedPackageNames.remove(packageName); + synchronized (sFailedPackageNamesPrimary) { + sFailedPackageNamesPrimary.remove(packageName); + } + synchronized (sFailedPackageNamesSecondary) { + sFailedPackageNamesSecondary.remove(packageName); } } @@ -206,8 +224,9 @@ public class BackgroundDexOptService extends JobService { new Thread("BackgroundDexOptService_IdleOptimization") { @Override public void run() { - idleOptimization(pm, pkgs, BackgroundDexOptService.this); - if (!mAbortIdleOptimization.get()) { + int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this); + if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { + Log.w(TAG, "Idle optimizations aborted because of space constraints."); // If we didn't abort we ran to completion (or stopped because of space). // Abandon our timeslice and do not reschedule. jobFinished(jobParams, /* reschedule */ false); @@ -217,67 +236,99 @@ public class BackgroundDexOptService extends JobService { return true; } - // Optimize the given packages and return true if the process was not aborted. - // The abort can happen either because of job scheduler or because of lack of space. - private boolean idleOptimization(PackageManagerService pm, ArraySet pkgs, - Context context) { + // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes). + private int idleOptimization(PackageManagerService pm, ArraySet pkgs, Context context) { Log.i(TAG, "Performing idle optimizations"); // If post-boot update is still running, request that it exits early. mExitPostBootUpdate.set(true); mAbortIdleOptimization.set(false); long lowStorageThreshold = getLowStorageThreshold(context); - return optimizePackages(pm, pkgs, lowStorageThreshold); - } + // Optimize primary apks. + int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true, + sFailedPackageNamesPrimary); - private boolean optimizePackages(PackageManagerService pm, ArraySet pkgs, - long lowStorageThreshold) { - for (String pkg : pkgs) { - if (abortIdleOptimizations(lowStorageThreshold)) { - return false; + if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { + return result; + } + + if (SystemProperties.getBoolean("dalvik.vm.deopt.secondary", false)) { + result = reconcileSecondaryDexFiles(pm.getDexManager()); + if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { + return result; } - synchronized (sFailedPackageNames) { - if (sFailedPackageNames.contains(pkg)) { + result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false, + sFailedPackageNamesSecondary); + } + return result; + } + + private int optimizePackages(PackageManagerService pm, ArraySet pkgs, + long lowStorageThreshold, boolean is_for_primary_dex, + ArraySet failedPackageNames) { + for (String pkg : pkgs) { + int abort_code = abortIdleOptimizations(lowStorageThreshold); + if (abort_code != OPTIMIZE_CONTINUE) { + return abort_code; + } + + synchronized (failedPackageNames) { + if (failedPackageNames.contains(pkg)) { // Skip previously failing package continue; } else { // Conservatively add package to the list of failing ones in case performDexOpt // never returns. - sFailedPackageNames.add(pkg); + failedPackageNames.add(pkg); } } // Optimize package if needed. Note that there can be no race between // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. - if (pm.performDexOpt(pkg, - /* checkProfiles */ true, - PackageManagerService.REASON_BACKGROUND_DEXOPT, - /* force */ false)) { + boolean success = is_for_primary_dex + ? pm.performDexOpt(pkg, + /* checkProfiles */ true, + PackageManagerService.REASON_BACKGROUND_DEXOPT, + /* force */ false) + : pm.performDexOptSecondary(pkg, + PackageManagerServiceCompilerMapping.getFullCompilerFilter(), + /* force */ true); + if (success) { // Dexopt succeeded, remove package from the list of failing ones. - synchronized (sFailedPackageNames) { - sFailedPackageNames.remove(pkg); + synchronized (failedPackageNames) { + failedPackageNames.remove(pkg); } } } - return true; + return OPTIMIZE_PROCESSED; } - // Return true if the idle optimizations should be aborted because of a space constraints - // or because the JobScheduler requested so. - private boolean abortIdleOptimizations(long lowStorageThreshold) { + private int reconcileSecondaryDexFiles(DexManager dm) { + // TODO(calin): should we blacklist packages for which we fail to reconcile? + for (String p : dm.getAllPackagesWithSecondaryDexFiles()) { + if (mAbortIdleOptimization.get()) { + return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; + } + dm.reconcileSecondaryDexFiles(p); + } + return OPTIMIZE_PROCESSED; + } + + // Evaluate whether or not idle optimizations should continue. + private int abortIdleOptimizations(long lowStorageThreshold) { if (mAbortIdleOptimization.get()) { // JobScheduler requested an early abort. - return true; + return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; } long usableSpace = mDataDir.getUsableSpace(); if (usableSpace < lowStorageThreshold) { // Rather bail than completely fill up the disk. Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace); - return true; + return OPTIMIZE_ABORT_NO_SPACE_LEFT; } - return false; + return OPTIMIZE_CONTINUE; } /** @@ -288,7 +339,8 @@ public class BackgroundDexOptService extends JobService { // Note that this may still run at the same time with the job scheduled by the // JobScheduler but the scheduler will not be able to cancel it. BackgroundDexOptService bdos = new BackgroundDexOptService(); - return bdos.idleOptimization(pm, pm.getOptimizablePackages(), context); + int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context); + return result == OPTIMIZE_PROCESSED; } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 84cb1bcef202a..8ab76a78ed7d7 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -7527,6 +7527,12 @@ public class PackageManagerService extends IPackageManager.Stub { mDexManager.reconcileSecondaryDexFiles(packageName); } + // TODO(calin): this is only needed for BackgroundDexOptService. Find a cleaner way to inject + // a reference there. + /*package*/ DexManager getDexManager() { + return mDexManager; + } + /** * Execute the background dexopt job immediately. */ diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 28bf58fdfbef8..30b949901d74d 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -343,6 +343,13 @@ public class DexManager { } } + /** + * Return all packages that contain records of secondary dex files. + */ + public Set getAllPackagesWithSecondaryDexFiles() { + return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles(); + } + /** * Retrieves the package which owns the given dexPath. */ diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 253d0e97f51e4..26134163a222d 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -437,6 +437,21 @@ public class PackageDexUsage extends AbstractStatsBase { } } + /** + * Return all packages that contain records of secondary dex files. + */ + public Set getAllPackagesWithSecondaryDexFiles() { + Set packages = new HashSet<>(); + synchronized (mPackageUseInfoMap) { + for (Map.Entry entry : mPackageUseInfoMap.entrySet()) { + if (!entry.getValue().mDexUseInfoMap.isEmpty()) { + packages.add(entry.getKey()); + } + } + } + return packages; + } + public void clear() { synchronized (mPackageUseInfoMap) { mPackageUseInfoMap.clear();