Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 18ebda54 authored by Keun young Park's avatar Keun young Park
Browse files

update dexopt to support cancellation

- Installer.dexopt now returns true (when completed) or false (when
  cancelled): reflect IInstalld change
- added Installer.controlDexOptBlocking to block or unblock dexopt
  : already added to IInstalld
- add PackageDexOptimizer.DEX_OPT_CANCELLED code with
  PackageDexOptimizer.DexOptResult annotation
- Change mInstaller's lock access:
  controlDexOptBlocking should access it without mInstallLock
  all other existing mInstaller access should be done under
    mInstallLock

Bug: 179094324
Bug: 156537504

Test: build only. Following CLs will include unit tests and manual test
      instruction.
Change-Id: I188bc04f76776e8117418ef1fd7c5311c9492758
parent b1402bb5
Loading
Loading
Loading
Loading
+55 −5
Original line number Diff line number Diff line
@@ -487,9 +487,42 @@ public class Installer extends SystemService {
        }
    }

    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,16 +530,33 @@ public class Installer extends SystemService {
        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);
        }
    }

    /**
     * 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
+4 −1
Original line number Diff line number Diff line
@@ -288,7 +288,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
             *       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 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
                encodeParameter(builder, dexoptCompilationReason);

                commands.add(builder.toString());

                // Cancellation cannot happen for OtaDexOpt. Returns true always.
                return true;
            }

            /**
+109 −30
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import static com.android.server.pm.PackageManagerServiceCompilerMapping.getReas
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 dalvik.system.DexFile;

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 @@ public class PackageDexOptimizer {
    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 @@ public class PackageDexOptimizer {
     * <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) {
@@ -169,11 +197,21 @@ public class PackageDexOptimizer {
        }
    }

    /**
     * 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 @@ public class PackageDexOptimizer {
                        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 @@ public class PackageDexOptimizer {
                    }
                }

                // 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 @@ public class PackageDexOptimizer {
     *      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 @@ public class PackageDexOptimizer {
            // 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 @@ public class PackageDexOptimizer {
    /**
     * 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 @@ public class PackageDexOptimizer {
                continue;
            }
            try {
                mInstaller.dexopt(
                synchronized (mInstallLock) {
                    boolean completed = getInstallerLI().dexopt(
                            dexPath,
                            android.os.Process.SYSTEM_UID,
                        /* packageName= */ "android",
                            /* pkgName= */ "android",
                            isa,
                            dexoptNeeded,
                        /* oatDir= */ null,
                            /* outputPath= */ null,
                            dexoptFlags,
                            options.getCompilerFilter(),
                            StorageManager.UUID_PRIVATE_INTERNAL,
                            dexUseInfo.getClassLoaderContext(),
                            /* seInfo= */ null,
                            /* downgrade= */ false,
                        /* targetSdk= */ 0,
                        /* profileName */ null,
                        /* dexMetadataPath */ null,
                            /* 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 @@ public class PackageDexOptimizer {
     * 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 @@ public class PackageDexOptimizer {
    }

    @GuardedBy("mInstallLock")
    @DexOptResult
    private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path,
            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
        if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) {
@@ -523,11 +579,15 @@ public class PackageDexOptimizer {
                // 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 @@ public class PackageDexOptimizer {
        }
        // 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 @@ public class PackageDexOptimizer {
            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;
    }
}
+9 −2
Original line number Diff line number Diff line
@@ -11029,6 +11029,9 @@ public class PackageManagerService extends IPackageManager.Stub
                case PackageDexOptimizer.DEX_OPT_SKIPPED:
                    numberOfPackagesSkipped++;
                    break;
                case PackageDexOptimizer.DEX_OPT_CANCELLED:
                    // ignore this case
                    break;
                case PackageDexOptimizer.DEX_OPT_FAILED:
                    numberOfPackagesFailed++;
                    break;
@@ -11174,12 +11177,18 @@ public class PackageManagerService extends IPackageManager.Stub
        }
    }
    /*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);
    }
@@ -11304,8 +11313,6 @@ 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;
    }