From 37a87698f5b13e8d24475cb05d48a3da7339192c Mon Sep 17 00:00:00 2001 From: David Brazdil Date: Thu, 7 Apr 2016 10:43:18 +0100 Subject: [PATCH 1/3] Update packages in post-boot background job Adds a post-boot job which scans all optimizable packages and updates those whose OAT files are out of date. This is meant to offset the fact that on OTA we only update the most used packages. Bug: 27901338 Change-Id: Ia4d4362ecead1ca63d08d62c6814dad4b810f7cc --- .../server/pm/BackgroundDexOptService.java | 184 ++++++++++++++---- 1 file changed, 151 insertions(+), 33 deletions(-) diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index eae2eaaaecad7..2ce2dfe407f57 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; + import android.app.AlarmManager; import android.app.job.JobInfo; import android.app.job.JobParameters; @@ -23,6 +25,9 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; import android.os.ServiceManager; import android.util.ArraySet; import android.util.Log; @@ -38,7 +43,9 @@ public class BackgroundDexOptService extends JobService { static final long RETRY_LATENCY = 4 * AlarmManager.INTERVAL_HOUR; - static final int BACKGROUND_DEXOPT_JOB = 800; + static final int JOB_IDLE_OPTIMIZE = 800; + static final int JOB_POST_BOOT_UPDATE = 801; + private static ComponentName sDexoptServiceName = new ComponentName( "android", BackgroundDexOptService.class.getName()); @@ -48,66 +55,177 @@ public class BackgroundDexOptService extends JobService { */ static final ArraySet sFailedPackageNames = new ArraySet(); - final AtomicBoolean mIdleTime = new AtomicBoolean(false); + /** + * Atomics set to true if the JobScheduler requests an abort. + */ + final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false); + 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); public static void schedule(Context context) { JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - JobInfo job = new JobInfo.Builder(BACKGROUND_DEXOPT_JOB, sDexoptServiceName) - .setRequiresDeviceIdle(true) - .setRequiresCharging(true) - .setPeriodic(TimeUnit.DAYS.toMillis(1)) - .build(); - js.schedule(job); + + // Schedule a one-off job which scans installed packages and updates + // out-of-date oat files. + js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName) + .setMinimumLatency(TimeUnit.MINUTES.toMillis(1)) + .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1)) + .build()); + + // Schedule a daily job which scans installed packages and compiles + // those with fresh profiling data. + js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName) + .setRequiresDeviceIdle(true) + .setRequiresCharging(true) + .setPeriodic(TimeUnit.DAYS.toMillis(1)) + .build()); + + if (DEBUG_DEXOPT) { + Log.i(TAG, "Jobs scheduled"); + } } - @Override - public boolean onStartJob(JobParameters params) { - Log.i(TAG, "onStartJob"); - final PackageManagerService pm = - (PackageManagerService)ServiceManager.getService("package"); + // Returns the current battery level as a 0-100 integer. + private int getBatteryLevel() { + IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + Intent intent = registerReceiver(null, filter); + int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - if (pm.isStorageLow()) { - Log.i(TAG, "Low storage, skipping this run"); - return false; + if (level < 0 || scale <= 0) { + // Battery data unavailable. This should never happen, so assume the worst. + return 0; } - final ArraySet pkgs = pm.getOptimizablePackages(); - if (pkgs == null || pkgs.isEmpty()) { - Log.i(TAG, "No packages to optimize"); + + return (100 * level / scale); + } + + private boolean runPostBootUpdate(final JobParameters jobParams, + final PackageManagerService pm, final ArraySet pkgs) { + if (mExitPostBootUpdate.get()) { + // This job has already been superseded. Do not start it. return false; } - final JobParameters jobParams = params; - mIdleTime.set(true); - new Thread("BackgroundDexOptService_DexOpter") { + // Load low battery threshold from the system config. This is a 0-100 integer. + final int lowBatteryThreshold = getResources().getInteger( + com.android.internal.R.integer.config_lowBatteryWarningLevel); + + mAbortPostBootUpdate.set(false); + new Thread("BackgroundDexOptService_PostBootUpdate") { @Override public void run() { for (String pkg : pkgs) { - if (!mIdleTime.get()) { - // Out of the idle state. Stop the compilation. + if (mAbortPostBootUpdate.get()) { + // JobScheduler requested an early abort. + return; + } + if (mExitPostBootUpdate.get()) { + // Different job, which supersedes this one, is running. + break; + } + if (getBatteryLevel() < lowBatteryThreshold) { + // Rather bail than completely drain the battery. + break; + } + if (DEBUG_DEXOPT) { + Log.i(TAG, "Updating package " + pkg); + } + // Update package if needed. Note that there can be no race between concurrent + // jobs because PackageDexOptimizer.performDexOpt is synchronized. + pm.performDexOpt(pkg, + /* instruction set */ null, + /* checkProfiles */ false, + PackageManagerService.REASON_BOOT, + /* force */ false); + } + // Ran to completion, so we abandon our timeslice and do not reschedule. + jobFinished(jobParams, /* reschedule */ false); + } + }.start(); + return true; + } + + private boolean runIdleOptimization(final JobParameters jobParams, + final PackageManagerService pm, final ArraySet pkgs) { + // If post-boot update is still running, request that it exits early. + mExitPostBootUpdate.set(true); + + mAbortIdleOptimization.set(false); + new Thread("BackgroundDexOptService_IdleOptimization") { + @Override + public void run() { + for (String pkg : pkgs) { + if (mAbortIdleOptimization.get()) { + // JobScheduler requested an early abort. return; } if (sFailedPackageNames.contains(pkg)) { - // skip previously failing package + // Skip previously failing package continue; } - if (!pm.performDexOpt(pkg, /* instruction set */ null, /* checkProfiles */ true, - PackageManagerService.REASON_BACKGROUND_DEXOPT, /* force */ false)) { - // there was a problem running dexopt, - // remember this so we do not keep retrying. + // Optimize package if needed. Note that there can be no race between + // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. + if (!pm.performDexOpt(pkg, + /* instruction set */ null, + /* checkProfiles */ true, + PackageManagerService.REASON_BACKGROUND_DEXOPT, + /* force */ false)) { + // Dexopt failed, remember this so we do not keep retrying. sFailedPackageNames.add(pkg); } } - // ran to completion, so we abandon our timeslice and do not reschedule - jobFinished(jobParams, false); + // Ran to completion, so we abandon our timeslice and do not reschedule. + jobFinished(jobParams, /* reschedule */ false); } }.start(); return true; } + @Override + public boolean onStartJob(JobParameters params) { + if (DEBUG_DEXOPT) { + Log.i(TAG, "onStartJob"); + } + + PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); + if (pm.isStorageLow()) { + if (DEBUG_DEXOPT) { + Log.i(TAG, "Low storage, skipping this run"); + } + return false; + } + + final ArraySet pkgs = pm.getOptimizablePackages(); + if (pkgs == null || pkgs.isEmpty()) { + if (DEBUG_DEXOPT) { + Log.i(TAG, "No packages to optimize"); + } + return false; + } + + if (params.getJobId() == JOB_POST_BOOT_UPDATE) { + return runPostBootUpdate(params, pm, pkgs); + } else { + return runIdleOptimization(params, pm, pkgs); + } + } + @Override public boolean onStopJob(JobParameters params) { - Log.i(TAG, "onIdleStop"); - mIdleTime.set(false); + if (DEBUG_DEXOPT) { + Log.i(TAG, "onStopJob"); + } + + if (params.getJobId() == JOB_POST_BOOT_UPDATE) { + mAbortPostBootUpdate.set(true); + } else { + mAbortIdleOptimization.set(true); + } return false; } } From 6a3b2d2bc63254a412eb546ed371fce8f4337434 Mon Sep 17 00:00:00 2001 From: David Brazdil Date: Fri, 8 Apr 2016 16:09:06 +0100 Subject: [PATCH 2/3] Refactor return values of performDexOpt PackageDexOptimizer.performDexOpt would return DEX_OPT_PERFORMED if dexopt succeeded on the package and DEX_OPT_SKIPPED otherwise, even if dexopt failed. This patch fixes that and cleans up the code. PackageManagerService.performDexOpt* would return true only if PackageDexOptimizer.performDexOpt returned DEX_OPT_PERFORMED. Consequently, it would return false when dexopt was not needed. This patch refactors the code to return true unless PackageDexOptimizer returns DEX_OPT_FAILED and documents the behaviour. Bug: 28082762 Change-Id: Ica73e67ab02025ef5619746bb8c465c96b72846b --- .../android/server/pm/PackageDexOptimizer.java | 18 ++++++++++++------ .../server/pm/PackageManagerService.java | 4 +++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index d13f47266f25f..b3ac05c2e91db 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -53,7 +53,6 @@ class PackageDexOptimizer { // TODO b/19550105 Remove error codes and use exceptions static final int DEX_OPT_SKIPPED = 0; static final int DEX_OPT_PERFORMED = 1; - static final int DEX_OPT_DEFERRED = 2; static final int DEX_OPT_FAILED = -1; private final Installer mInstaller; @@ -170,6 +169,8 @@ class PackageDexOptimizer { final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; boolean performedDexOpt = false; + boolean successfulDexOpt = true; + final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); for (String dexCodeInstructionSet : dexCodeInstructionSets) { for (String path : paths) { @@ -226,15 +227,20 @@ class PackageDexOptimizer { performedDexOpt = true; } catch (InstallerException e) { Slog.w(TAG, "Failed to dexopt", e); + successfulDexOpt = false; } } } - // If we've gotten here, we're sure that no error occurred and that we haven't - // deferred dex-opt. We've either dex-opted one more paths or instruction sets or - // we've skipped all of them because they are up to date. In both cases this - // package doesn't need dexopt any longer. - return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; + if (successfulDexOpt) { + // If we've gotten here, we're sure that no error occurred. We've either + // dex-opted one or more paths or instruction sets or we've skipped + // all of them because they are up to date. In both cases this package + // doesn't need dexopt any longer. + return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; + } else { + return DEX_OPT_FAILED; + } } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ddccbb3d3c656..c81984ec6816d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -7147,6 +7147,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } + // Run dexopt on a given package. Returns true if dexopt did not fail, i.e. + // if the package can now be considered up to date for the given filter. private boolean performDexOptInternal(String packageName, String instructionSet, boolean checkProfiles, String targetCompilerFilter, boolean force) { PackageParser.Package p; @@ -7167,7 +7169,7 @@ public class PackageManagerService extends IPackageManager.Stub { final String[] instructionSets = new String[] { targetInstructionSet }; int result = performDexOptInternalWithDependenciesLI(p, instructionSets, checkProfiles, targetCompilerFilter, force); - return result == PackageDexOptimizer.DEX_OPT_PERFORMED; + return result != PackageDexOptimizer.DEX_OPT_FAILED; } } finally { Binder.restoreCallingIdentity(callingId); From 80932c1418f22fa2ab49aa1eea8337b8f29b9bcc Mon Sep 17 00:00:00 2001 From: David Brazdil Date: Fri, 8 Apr 2016 16:38:04 +0100 Subject: [PATCH 3/3] Assume package failed to compile unless proven otherwise BackgroundDexOptService keeps a list of packages which failed to compile so that they are not revisited. If compilation takes so long that the background job is killed, the offending package is not recorded. This patch records the package before dexopt is called and removes it from the list if dexopt succeeds. Bug: 28082762 Change-Id: If7388e159b999287b60f19dc99cf4dde61ec64c8 --- .../com/android/server/pm/BackgroundDexOptService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 2ce2dfe407f57..da9cd8b88234c 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -168,15 +168,18 @@ public class BackgroundDexOptService extends JobService { // Skip previously failing package continue; } + // Conservatively add package to the list of failing ones in case performDexOpt + // never returns. + sFailedPackageNames.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, + if (pm.performDexOpt(pkg, /* instruction set */ null, /* checkProfiles */ true, PackageManagerService.REASON_BACKGROUND_DEXOPT, /* force */ false)) { - // Dexopt failed, remember this so we do not keep retrying. - sFailedPackageNames.add(pkg); + // Dexopt succeeded, remove package from the list of failing ones. + sFailedPackageNames.remove(pkg); } } // Ran to completion, so we abandon our timeslice and do not reschedule.