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

Commit 2997442a authored by Winson's avatar Winson
Browse files

Combine package parsing thread pools

A thread pool was being spawned for every partition,
creating potentially 20 different threads. This can be
constrained to MAX_THREADS (4) by passing the ExecutorService
through the scan calls and only destroying it at the end.

It's unclear how much of a benefit this has, but cleaning up
these threads can't be a bad thing.

Test: manual device scans/boots properly
Test: atest ParallelPackageParserTest

Bug: 146081131

Change-Id: If1f0cd4b2809000b5bb19a6f64a329619f4d45f4
parent 17b76c62
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;

import android.annotation.AnyThread;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -1081,6 +1082,7 @@ public class PackageParser {
     *
     * @see #parsePackage(File, int, boolean)
     */
    @AnyThread
    public ParsedPackage parseParsedPackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
        ParsedPackage parsed = useCaches ? getCachedResult(packageFile, flags) : null;
+82 −56
Original line number Diff line number Diff line
@@ -384,6 +384,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -2864,23 +2865,34 @@ public class PackageManagerService extends IPackageManager.Stub
                scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
            }
            final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR;
            final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
            PackageParser packageParser = new PackageParser();
            packageParser.setSeparateProcesses(mSeparateProcesses);
            packageParser.setOnlyCoreApps(mOnlyCore);
            packageParser.setDisplayMetrics(mMetrics);
            packageParser.setCacheDir(mCacheDir);
            packageParser.setCallback(mPackageParserCallback);
            ExecutorService executorService = ParallelPackageParser.makeExecutorService();
            // Collect vendor/product/system_ext overlay packages. (Do this before scanning
            // any apps.)
            // For security and version matching reason, only consider overlay packages if they
            // reside in the right directory.
            final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR;
            final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
            for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
                final SystemPartition partition = mDirsToScanAsSystem.get(i);
                if (partition.overlayFolder == null) {
                    continue;
                }
                scanDirTracedLI(partition.overlayFolder, systemParseFlags,
                        systemScanFlags | partition.scanFlag, 0);
                        systemScanFlags | partition.scanFlag, 0,
                        packageParser, executorService);
            }
            scanDirTracedLI(frameworkDir, systemParseFlags,
                    systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0);
                    systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
                    packageParser, executorService);
            if (!mPackages.containsKey("android")) {
                throw new IllegalStateException(
                        "Failed to load frameworks package; check log for warnings");
@@ -2889,10 +2901,12 @@ public class PackageManagerService extends IPackageManager.Stub
                final SystemPartition partition = mDirsToScanAsSystem.get(i);
                if (partition.privAppFolder != null) {
                    scanDirTracedLI(partition.privAppFolder, systemParseFlags,
                            systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0);
                            systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
                            packageParser, executorService);
                }
                scanDirTracedLI(partition.appFolder, systemParseFlags,
                        systemScanFlags | partition.scanFlag, 0);
                        systemScanFlags | partition.scanFlag, 0,
                        packageParser, executorService);
            }
@@ -2996,8 +3010,18 @@ public class PackageManagerService extends IPackageManager.Stub
            if (!mOnlyCore) {
                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                        SystemClock.uptimeMillis());
                scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
                scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0,
                        packageParser, executorService);
            }
            List<Runnable> unfinishedTasks = executorService.shutdownNow();
            if (!unfinishedTasks.isEmpty()) {
                throw new IllegalStateException("Not all tasks finished before calling close: "
                        + unfinishedTasks);
            }
            if (!mOnlyCore) {
                // Remove disable package settings for updated system apps that were
                // removed via an OTA. If the update is no longer present, remove the
                // app completely. Otherwise, revoke their system privileges.
@@ -8570,16 +8594,18 @@ public class PackageManagerService extends IPackageManager.Stub
        return finalList;
    }
    private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
    private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
            long currentTime, PackageParser packageParser, ExecutorService executorService) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
        try {
            scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
            scanDirLI(scanDir, parseFlags, scanFlags, currentTime, packageParser, executorService);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }
    private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
    private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime,
            PackageParser packageParser, ExecutorService executorService) {
        final File[] files = scanDir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            Log.d(TAG, "No files in app dir " + scanDir);
@@ -8590,9 +8616,10 @@ public class PackageManagerService extends IPackageManager.Stub
            Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
                    + " flags=0x" + Integer.toHexString(parseFlags));
        }
        try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
                mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
                mPackageParserCallback)) {
        ParallelPackageParser parallelPackageParser =
                new ParallelPackageParser(packageParser, executorService);
        // Submit files for parsing in parallel
        int fileCount = 0;
        for (File file : files) {
@@ -8636,15 +8663,14 @@ public class PackageManagerService extends IPackageManager.Stub
            }
            // Delete invalid userdata apps
                if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
                        errorCode != PackageManager.INSTALL_SUCCEEDED) {
            if ((scanFlags & SCAN_AS_SYSTEM) == 0
                    && errorCode != PackageManager.INSTALL_SUCCEEDED) {
                logCriticalInfo(Log.WARN,
                        "Deleting invalid package at " + parseResult.scanFile);
                removeCodePathLI(parseResult.scanFile);
            }
        }
    }
    }
    public static void reportSettingsProblem(int priority, String msg) {
        logCriticalInfo(priority, msg);
+18 −38
Original line number Diff line number Diff line
@@ -22,13 +22,11 @@ import android.content.pm.PackageParser;
import android.content.pm.parsing.ParsedPackage;
import android.os.Process;
import android.os.Trace;
import android.util.DisplayMetrics;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ConcurrentUtils;

import java.io.File;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
@@ -38,30 +36,27 @@ import java.util.concurrent.ExecutorService;
 * <p>Parsing requests are processed by a thread-pool of {@link #MAX_THREADS}.
 * At any time, at most {@link #QUEUE_CAPACITY} results are kept in RAM</p>
 */
class ParallelPackageParser implements AutoCloseable {
class ParallelPackageParser {

    private static final int QUEUE_CAPACITY = 10;
    private static final int QUEUE_CAPACITY = 30;
    private static final int MAX_THREADS = 4;

    private final String[] mSeparateProcesses;
    private final boolean mOnlyCore;
    private final DisplayMetrics mMetrics;
    private final File mCacheDir;
    private final PackageParser.Callback mPackageParserCallback;
    private volatile String mInterruptedInThread;

    private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);

    private final ExecutorService mService = ConcurrentUtils.newFixedThreadPool(MAX_THREADS,
            "package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND);
    static ExecutorService makeExecutorService() {
        return ConcurrentUtils.newFixedThreadPool(MAX_THREADS, "package-parsing-thread",
                Process.THREAD_PRIORITY_FOREGROUND);
    }

    private final PackageParser mPackageParser;

    ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps,
            DisplayMetrics metrics, File cacheDir, PackageParser.Callback callback) {
        mSeparateProcesses = separateProcesses;
        mOnlyCore = onlyCoreApps;
        mMetrics = metrics;
        mCacheDir = cacheDir;
        mPackageParserCallback = callback;
    private final ExecutorService mExecutorService;

    ParallelPackageParser(PackageParser packageParser, ExecutorService executorService) {
        mPackageParser = packageParser;
        mExecutorService = executorService;
    }

    static class ParseResult {
@@ -104,18 +99,12 @@ class ParallelPackageParser implements AutoCloseable {
     * @param parseFlags parse flags
     */
    public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
        mExecutorService.submit(() -> {
            ParseResult pr = new ParseResult();
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
            try {
                PackageParser pp = new PackageParser();
                pp.setSeparateProcesses(mSeparateProcesses);
                pp.setOnlyCoreApps(mOnlyCore);
                pp.setDisplayMetrics(mMetrics);
                pp.setCacheDir(mCacheDir);
                pp.setCallback(mPackageParserCallback);
                pr.scanFile = scanFile;
                pr.parsedPackage = parsePackage(pp, scanFile, parseFlags);
                pr.parsedPackage = parsePackage(scanFile, parseFlags);
            } catch (Throwable e) {
                pr.throwable = e;
            } finally {
@@ -134,17 +123,8 @@ class ParallelPackageParser implements AutoCloseable {
    }

    @VisibleForTesting
    protected ParsedPackage parsePackage(PackageParser packageParser, File scanFile,
            int parseFlags) throws PackageParser.PackageParserException {
        return packageParser.parseParsedPackage(scanFile, parseFlags, true);
    }

    @Override
    public void close() {
        List<Runnable> unfinishedTasks = mService.shutdownNow();
        if (!unfinishedTasks.isEmpty()) {
            throw new IllegalStateException("Not all tasks finished before calling close: "
                    + unfinishedTasks);
        }
    protected ParsedPackage parsePackage(File scanFile, int parseFlags)
            throws PackageParser.PackageParserException {
        return mPackageParser.parseParsedPackage(scanFile, parseFlags, true);
    }
}
+7 −6
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import org.junit.runner.RunWith;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;

/**
 * Tests for {@link ParallelPackageParser}
@@ -43,7 +44,8 @@ public class ParallelPackageParserTest {

    @Before
    public void setUp() {
        mParser = new TestParallelPackageParser();
        mParser = new TestParallelPackageParser(new PackageParser(),
                ParallelPackageParser.makeExecutorService());
    }

    @Test(timeout = 1000)
@@ -68,15 +70,14 @@ public class ParallelPackageParserTest {
        }
    }

    class TestParallelPackageParser extends ParallelPackageParser {
    private class TestParallelPackageParser extends ParallelPackageParser {

        TestParallelPackageParser() {
            super(null, false, null, null, null);
        TestParallelPackageParser(PackageParser packageParser, ExecutorService executorService) {
            super(packageParser, executorService);
        }

        @Override
        protected ParsedPackage parsePackage(PackageParser packageParser, File scanFile,
                int parseFlags) throws PackageParser.PackageParserException {
        protected ParsedPackage parsePackage(File scanFile, int parseFlags) {
            // Do not actually parse the package for testing
            return null;
        }