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

Commit 574994af authored by Calin Juravle's avatar Calin Juravle
Browse files

Compile secondary dex files during background dexopt job

Extend the backgound dexopt job to compile secondary dex files when
"dalvik.vm.deopt.secondary" system property is set to true.

Test: adb shell cmd package bg-dexopt-job
Bug: 32871170

(cherry picked from commit 51f521c3)

Change-Id: I220ff4ea26c4eee030ab1a9f4c957a09aa9f2f4c

Merged-In: If3a4b77f785e92c7f2f7869e25ff9b99904f165a
parent f7edab63
Loading
Loading
Loading
Loading
+86 −34
Original line number Diff line number Diff line
@@ -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<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();

@@ -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<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;
    }

    /**
@@ -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
+6 −0
Original line number Diff line number Diff line
@@ -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.
     */
+7 −0
Original line number Diff line number Diff line
@@ -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.
     */
+15 −0
Original line number Diff line number Diff line
@@ -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();