Loading services/core/java/com/android/server/pm/BackgroundDexOptService.java +86 −34 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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<String> sFailedPackageNames = new ArraySet<String>(); static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>(); static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>(); /** * 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(); Loading Loading @@ -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); } } Loading Loading @@ -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); Loading @@ -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<String> pkgs, Context context) { // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes). private int idleOptimization(PackageManagerService pm, ArraySet<String> 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); 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; } result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false, sFailedPackageNamesSecondary); } return result; } private boolean optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, long lowStorageThreshold) { private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, long lowStorageThreshold, boolean is_for_primary_dex, ArraySet<String> failedPackageNames) { for (String pkg : pkgs) { if (abortIdleOptimizations(lowStorageThreshold)) { return false; int abort_code = abortIdleOptimizations(lowStorageThreshold); if (abort_code != OPTIMIZE_CONTINUE) { return abort_code; } synchronized (sFailedPackageNames) { if (sFailedPackageNames.contains(pkg)) { 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, boolean success = is_for_primary_dex ? pm.performDexOpt(pkg, /* checkProfiles */ true, PackageManagerService.REASON_BACKGROUND_DEXOPT, /* force */ false)) { /* 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; } 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; } // 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) { // 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; } /** Loading @@ -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 Loading services/core/java/com/android/server/pm/PackageManagerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -7559,6 +7559,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. */ Loading services/core/java/com/android/server/pm/dex/DexManager.java +7 −0 Original line number Diff line number Diff line Loading @@ -343,6 +343,13 @@ public class DexManager { } } /** * Return all packages that contain records of secondary dex files. */ public Set<String> getAllPackagesWithSecondaryDexFiles() { return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles(); } /** * Retrieves the package which owns the given dexPath. */ Loading services/core/java/com/android/server/pm/dex/PackageDexUsage.java +15 −0 Original line number Diff line number Diff line Loading @@ -437,6 +437,21 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } } /** * Return all packages that contain records of secondary dex files. */ public Set<String> getAllPackagesWithSecondaryDexFiles() { Set<String> packages = new HashSet<>(); synchronized (mPackageUseInfoMap) { for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) { if (!entry.getValue().mDexUseInfoMap.isEmpty()) { packages.add(entry.getKey()); } } } return packages; } public void clear() { synchronized (mPackageUseInfoMap) { mPackageUseInfoMap.clear(); Loading Loading
services/core/java/com/android/server/pm/BackgroundDexOptService.java +86 −34 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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<String> sFailedPackageNames = new ArraySet<String>(); static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>(); static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>(); /** * 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(); Loading Loading @@ -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); } } Loading Loading @@ -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); Loading @@ -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<String> pkgs, Context context) { // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes). private int idleOptimization(PackageManagerService pm, ArraySet<String> 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); 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; } result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false, sFailedPackageNamesSecondary); } return result; } private boolean optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, long lowStorageThreshold) { private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, long lowStorageThreshold, boolean is_for_primary_dex, ArraySet<String> failedPackageNames) { for (String pkg : pkgs) { if (abortIdleOptimizations(lowStorageThreshold)) { return false; int abort_code = abortIdleOptimizations(lowStorageThreshold); if (abort_code != OPTIMIZE_CONTINUE) { return abort_code; } synchronized (sFailedPackageNames) { if (sFailedPackageNames.contains(pkg)) { 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, boolean success = is_for_primary_dex ? pm.performDexOpt(pkg, /* checkProfiles */ true, PackageManagerService.REASON_BACKGROUND_DEXOPT, /* force */ false)) { /* 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; } 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; } // 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) { // 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; } /** Loading @@ -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 Loading
services/core/java/com/android/server/pm/PackageManagerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -7559,6 +7559,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. */ Loading
services/core/java/com/android/server/pm/dex/DexManager.java +7 −0 Original line number Diff line number Diff line Loading @@ -343,6 +343,13 @@ public class DexManager { } } /** * Return all packages that contain records of secondary dex files. */ public Set<String> getAllPackagesWithSecondaryDexFiles() { return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles(); } /** * Retrieves the package which owns the given dexPath. */ Loading
services/core/java/com/android/server/pm/dex/PackageDexUsage.java +15 −0 Original line number Diff line number Diff line Loading @@ -437,6 +437,21 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } } /** * Return all packages that contain records of secondary dex files. */ public Set<String> getAllPackagesWithSecondaryDexFiles() { Set<String> packages = new HashSet<>(); synchronized (mPackageUseInfoMap) { for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) { if (!entry.getValue().mDexUseInfoMap.isEmpty()) { packages.add(entry.getKey()); } } } return packages; } public void clear() { synchronized (mPackageUseInfoMap) { mPackageUseInfoMap.clear(); Loading