Merge "update dexopt to support cancellation"
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 8fd545f..7e002bf 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -487,9 +487,42 @@
}
}
- public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
+ /**
+ * Runs dex optimization.
+ *
+ * @param apkPath Path of target APK
+ * @param uid UID of the package
+ * @param pkgName Name of the package
+ * @param instructionSet Target instruction set to run dex optimization.
+ * @param dexoptNeeded Necessary dex optimization for this request. Check
+ * {@link dalvik.system.DexFile#NO_DEXOPT_NEEDED},
+ * {@link dalvik.system.DexFile#DEX2OAT_FROM_SCRATCH},
+ * {@link dalvik.system.DexFile#DEX2OAT_FOR_BOOT_IMAGE}, and
+ * {@link dalvik.system.DexFile#DEX2OAT_FOR_FILTER}.
+ * @param outputPath Output path of generated dex optimization.
+ * @param dexFlags Check {@code DEXOPT_*} for allowed flags.
+ * @param compilerFilter Compiler filter like "verify", "speed-profile". Check
+ * {@code art/libartbase/base/compiler_filter.cc} for full list.
+ * @param volumeUuid UUID of the volume where the package data is stored. {@code null}
+ * represents internal storage.
+ * @param classLoaderContext This encodes the class loader chain (class loader type + class
+ * path) in a format compatible to dex2oat. Check
+ * {@code DexoptUtils.processContextForDexLoad} for further details.
+ * @param seInfo Selinux context to set for generated outputs.
+ * @param downgrade If set, allows downgrading {@code compilerFilter}. If downgrading is not
+ * allowed and requested {@code compilerFilter} is considered as downgrade,
+ * the request will be ignored.
+ * @param targetSdkVersion Target SDK version of the package.
+ * @param profileName Name of reference profile file.
+ * @param dexMetadataPath Specifies the location of dex metadata file.
+ * @param compilationReason Specifies the reason for the compilation like "install".
+ * @return {@code true} if {@code dexopt} is completed. {@code false} if it was cancelled.
+ *
+ * @throws InstallerException if {@code dexopt} fails.
+ */
+ public boolean dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
int dexoptNeeded, @Nullable String outputPath, int dexFlags,
- String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries,
+ String compilerFilter, @Nullable String volumeUuid, @Nullable String classLoaderContext,
@Nullable String seInfo, boolean downgrade, int targetSdkVersion,
@Nullable String profileName, @Nullable String dexMetadataPath,
@Nullable String compilationReason) throws InstallerException {
@@ -497,10 +530,10 @@
BlockGuard.getVmPolicy().onPathAccess(apkPath);
BlockGuard.getVmPolicy().onPathAccess(outputPath);
BlockGuard.getVmPolicy().onPathAccess(dexMetadataPath);
- if (!checkBeforeRemote()) return;
+ if (!checkBeforeRemote()) return false;
try {
- mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
- dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade,
+ return mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
+ dexFlags, compilerFilter, volumeUuid, classLoaderContext, seInfo, downgrade,
targetSdkVersion, profileName, dexMetadataPath, compilationReason);
} catch (Exception e) {
throw InstallerException.from(e);
@@ -508,6 +541,23 @@
}
/**
+ * Enables or disables dex optimization blocking.
+ *
+ * <p> Enabling blocking will also involve cancelling pending dexopt call and killing child
+ * processes forked from installd to run dexopt. The pending dexopt call will return false
+ * when it is cancelled.
+ *
+ * @param block set to true to enable blocking / false to disable blocking.
+ */
+ public void controlDexOptBlocking(boolean block) {
+ try {
+ mInstalld.controlDexOptBlocking(block);
+ } catch (Exception e) {
+ Slog.w(TAG, "blockDexOpt failed", e);
+ }
+ }
+
+ /**
* Analyzes the ART profiles of the given package, possibly merging the information
* into the reference profile. Returns whether or not we should optimize the package
* based on how much information is in the profile.
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index f968daf..862cb6f 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -288,7 +288,7 @@
* frameworks/native/cmds/installd/otapreopt.cpp.
*/
@Override
- public void dexopt(String apkPath, int uid, @Nullable String pkgName,
+ public boolean dexopt(String apkPath, int uid, @Nullable String pkgName,
String instructionSet, int dexoptNeeded, @Nullable String outputPath,
int dexFlags, String compilerFilter, @Nullable String volumeUuid,
@Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade,
@@ -320,6 +320,9 @@
encodeParameter(builder, dexoptCompilationReason);
commands.add(builder.toString());
+
+ // Cancellation cannot happen for OtaDexOpt. Returns true always.
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index dd22fd6..7739f2f 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -43,6 +43,7 @@
import static dalvik.system.DexFile.getSafeModeCompilerFilter;
import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -79,6 +80,8 @@
import java.io.File;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -92,16 +95,40 @@
private static final String TAG = "PackageDexOptimizer";
static final String OAT_DIR_NAME = "oat";
// TODO b/19550105 Remove error codes and use exceptions
+ /** No need to run dexopt and it was skipped */
public static final int DEX_OPT_SKIPPED = 0;
+ /** Dexopt was completed */
public static final int DEX_OPT_PERFORMED = 1;
+ /**
+ * Cancelled while running it. This is not an error case as cancel was requested
+ * from the client.
+ */
+ public static final int DEX_OPT_CANCELLED = 2;
+ /** Failed to run dexopt */
public static final int DEX_OPT_FAILED = -1;
+
+ @IntDef(prefix = {"DEX_OPT_"}, value = {
+ DEX_OPT_SKIPPED,
+ DEX_OPT_PERFORMED,
+ DEX_OPT_CANCELLED,
+ DEX_OPT_FAILED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DexOptResult {
+ }
+
// One minute over PM WATCHDOG_TIMEOUT
private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60;
- @GuardedBy("mInstallLock")
- private final Installer mInstaller;
private final Object mInstallLock;
+ /**
+ * This should be accessed only through {@link #getInstallerLI()} with {@link #mInstallLock}
+ * or {@link #getInstallerWithoutLock()} without the lock. Check both methods for further
+ * details on when to use each of them.
+ */
+ private final Installer mInstaller;
+
@GuardedBy("mInstallLock")
private final PowerManager.WakeLock mDexoptWakeLock;
private volatile boolean mSystemReady;
@@ -144,6 +171,7 @@
* <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
* synchronized on {@link #mInstallLock}.
*/
+ @DexOptResult
int performDexOpt(AndroidPackage pkg, @NonNull PackageSetting pkgSetting,
String[] instructionSets, CompilerStats.PackageStats packageStats,
PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) {
@@ -170,10 +198,20 @@
}
/**
+ * Cancels currently running dex optimization.
+ */
+ void controlDexOptBlocking(boolean block) {
+ // This method should not hold mInstallLock as cancelling should be possible while
+ // the lock is held by other thread running performDexOpt.
+ getInstallerWithoutLock().controlDexOptBlocking(block);
+ }
+
+ /**
* Performs dexopt on all code paths of the given package.
* It assumes the install lock is held.
*/
@GuardedBy("mInstallLock")
+ @DexOptResult
private int performDexOptLI(AndroidPackage pkg, @NonNull PackageSetting pkgSetting,
String[] targetInstructionSets, CompilerStats.PackageStats packageStats,
PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) {
@@ -269,7 +307,6 @@
profileAnalysisResult, classLoaderContexts[i], dexoptFlags, sharedGid,
packageStats, options.isDowngrade(), profileName, dexMetadataPath,
options.getCompilationReason());
-
// OTAPreopt doesn't have stats so don't report in that case.
if (packageStats != null) {
Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "dex2oat-metrics");
@@ -293,6 +330,14 @@
}
}
+ // Should stop the operation immediately.
+ if (newResult == DEX_OPT_CANCELLED) {
+ // Even for the cancellation, return failed if has failed.
+ if (result == DEX_OPT_FAILED) {
+ return result;
+ }
+ return newResult;
+ }
// The end result is:
// - FAILED if any path failed,
// - PERFORMED if at least one path needed compilation,
@@ -314,6 +359,7 @@
* DEX_OPT_SKIPPED if the path does not need to be deopt-ed.
*/
@GuardedBy("mInstallLock")
+ @DexOptResult
private int dexOptPath(AndroidPackage pkg, @NonNull PackageSetting pkgSetting, String path,
String isa, String compilerFilter, int profileAnalysisResult, String classLoaderContext,
int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade,
@@ -340,12 +386,14 @@
// installd only uses downgrade flag for secondary dex files and ignores it for
// primary dex files.
String seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
- mInstaller.dexopt(path, uid, pkg.getPackageName(), isa, dexoptNeeded, oatDir,
- dexoptFlags, compilerFilter, pkg.getVolumeUuid(), classLoaderContext,
- seInfo, false /* downgrade*/, pkg.getTargetSdkVersion(),
- profileName, dexMetadataPath,
+ boolean completed = getInstallerLI().dexopt(path, uid, pkg.getPackageName(), isa,
+ dexoptNeeded, oatDir, dexoptFlags, compilerFilter, pkg.getVolumeUuid(),
+ classLoaderContext, seInfo, /* downgrade= */ false ,
+ pkg.getTargetSdkVersion(), profileName, dexMetadataPath,
getAugmentedReasonName(compilationReason, dexMetadataPath != null));
-
+ if (!completed) {
+ return DEX_OPT_CANCELLED;
+ }
if (packageStats != null) {
long endTime = System.currentTimeMillis();
packageStats.setCompileTime(path, (int)(endTime - startTime));
@@ -360,6 +408,7 @@
/**
* Perform dexopt (if needed) on a system server code path).
*/
+ @DexOptResult
public int dexoptSystemServerPath(
String dexPath, PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
int dexoptFlags = DEXOPT_PUBLIC
@@ -380,23 +429,28 @@
continue;
}
try {
- mInstaller.dexopt(
- dexPath,
- android.os.Process.SYSTEM_UID,
- /* packageName= */ "android",
- isa,
- dexoptNeeded,
- /* oatDir= */ null,
- dexoptFlags,
- options.getCompilerFilter(),
- StorageManager.UUID_PRIVATE_INTERNAL,
- dexUseInfo.getClassLoaderContext(),
- /* seInfo= */ null,
- /* downgrade= */ false ,
- /* targetSdk= */ 0,
- /* profileName */ null,
- /* dexMetadataPath */ null,
- getReasonName(options.getCompilationReason()));
+ synchronized (mInstallLock) {
+ boolean completed = getInstallerLI().dexopt(
+ dexPath,
+ android.os.Process.SYSTEM_UID,
+ /* pkgName= */ "android",
+ isa,
+ dexoptNeeded,
+ /* outputPath= */ null,
+ dexoptFlags,
+ options.getCompilerFilter(),
+ StorageManager.UUID_PRIVATE_INTERNAL,
+ dexUseInfo.getClassLoaderContext(),
+ /* seInfo= */ null,
+ /* downgrade= */ false,
+ /* targetSdkVersion= */ 0,
+ /* profileName= */ null,
+ /* dexMetadataPath= */ null,
+ getReasonName(options.getCompilationReason()));
+ if (!completed) {
+ return DEX_OPT_CANCELLED;
+ }
+ }
} catch (InstallerException e) {
Slog.w(TAG, "Failed to dexopt", e);
return DEX_OPT_FAILED;
@@ -426,6 +480,7 @@
* throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
* that seems wasteful.
*/
+ @DexOptResult
public int dexOptSecondaryDexPath(ApplicationInfo info, String path,
PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
if (info.uid == -1) {
@@ -475,6 +530,7 @@
}
@GuardedBy("mInstallLock")
+ @DexOptResult
private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path,
PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) {
@@ -523,11 +579,15 @@
// arguments as some (dexopNeeded and oatDir) will be computed by installd because
// system server cannot read untrusted app content.
// TODO(calin): maybe add a separate call.
- mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
- /*oatDir*/ null, dexoptFlags,
+ boolean completed = getInstallerLI().dexopt(path, info.uid, info.packageName,
+ isa, /* dexoptNeeded= */ 0,
+ /* outputPath= */ null, dexoptFlags,
compilerFilter, info.volumeUuid, classLoaderContext, info.seInfo,
- options.isDowngrade(), info.targetSdkVersion, /*profileName*/ null,
- /*dexMetadataPath*/ null, getReasonName(reason));
+ options.isDowngrade(), info.targetSdkVersion, /* profileName= */ null,
+ /* dexMetadataPath= */ null, getReasonName(reason));
+ if (!completed) {
+ return DEX_OPT_CANCELLED;
+ }
}
return DEX_OPT_PERFORMED;
@@ -810,7 +870,9 @@
}
// Merge profiles. It returns whether or not there was an updated in the profile info.
try {
- return mInstaller.mergeProfiles(uid, pkg.getPackageName(), profileName);
+ synchronized (mInstallLock) {
+ return getInstallerLI().mergeProfiles(uid, pkg.getPackageName(), profileName);
+ }
} catch (InstallerException e) {
Slog.w(TAG, "Failed to merge profiles", e);
// We don't need to optimize if we failed to merge.
@@ -921,4 +983,21 @@
return flags | DEXOPT_FORCE;
}
}
+
+ /**
+ * Returns {@link #mInstaller} with {@link #mInstallLock}. This should be used for all
+ * {@link #mInstaller} access unless {@link #getInstallerWithoutLock()} is allowed.
+ */
+ @GuardedBy("mInstallLock")
+ private Installer getInstallerLI() {
+ return mInstaller;
+ }
+
+ /**
+ * Returns {@link #mInstaller} without lock. This should be used only inside
+ * {@link #controlDexOptBlocking(boolean)}.
+ */
+ private Installer getInstallerWithoutLock() {
+ return mInstaller;
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 89cb7fe..b2035b8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11032,6 +11032,9 @@
case PackageDexOptimizer.DEX_OPT_SKIPPED:
numberOfPackagesSkipped++;
break;
+ case PackageDexOptimizer.DEX_OPT_CANCELLED:
+ // ignore this case
+ break;
case PackageDexOptimizer.DEX_OPT_FAILED:
numberOfPackagesFailed++;
break;
@@ -11177,12 +11180,18 @@
}
}
+ /*package*/ void controlDexOptBlocking(boolean block) {
+ mPackageDexOptimizer.controlDexOptBlocking(block);
+ }
+
/**
* Perform dexopt on the given package and return one of following result:
* {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
* {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
+ * {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
* {@link PackageDexOptimizer#DEX_OPT_FAILED}
*/
+ @PackageDexOptimizer.DexOptResult
/* package */ int performDexOptWithStatus(DexoptOptions options) {
return performDexOptTraced(options);
}
@@ -11307,8 +11316,6 @@
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;
}