Loading services/core/java/com/android/server/pm/PackageManagerService.java +39 −12 Original line number Diff line number Diff line Loading @@ -6670,7 +6670,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) { private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) { final File[] files = dir.listFiles(); if (ArrayUtils.isEmpty(files)) { Log.d(TAG, "No files in app dir " + dir); Loading @@ -6681,7 +6681,11 @@ public class PackageManagerService extends IPackageManager.Stub { Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags + " flags=0x" + Integer.toHexString(parseFlags)); } ParallelPackageParser parallelPackageParser = new ParallelPackageParser( mSeparateProcesses, mOnlyCore, mMetrics); // Submit files for parsing in parallel int fileCount = 0; for (File file : files) { final boolean isPackage = (isApkFile(file) || file.isDirectory()) && !PackageInstallerService.isStageName(file.getName()); Loading @@ -6689,20 +6693,43 @@ public class PackageManagerService extends IPackageManager.Stub { // Ignore entries which are not packages continue; } parallelPackageParser.submit(file, parseFlags); fileCount++; } // Process results one by one for (; fileCount > 0; fileCount--) { ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); Throwable throwable = parseResult.throwable; int errorCode = PackageManager.INSTALL_SUCCEEDED; if (throwable == null) { try { scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, scanFlags, currentTime, null); scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags, currentTime, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage()); errorCode = e.error; Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage()); } } else if (throwable instanceof PackageParser.PackageParserException) { PackageParser.PackageParserException e = (PackageParser.PackageParserException) throwable; errorCode = e.error; Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage()); } else { throw new IllegalStateException("Unexpected exception occurred while parsing " + parseResult.scanFile, throwable); } // Delete invalid userdata apps if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 && e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { logCriticalInfo(Log.WARN, "Deleting invalid package at " + file); removeCodePathLI(file); } errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) { logCriticalInfo(Log.WARN, "Deleting invalid package at " + parseResult.scanFile); removeCodePathLI(parseResult.scanFile); } } parallelPackageParser.close(); } private static File getSettingsProblemFile() { Loading services/core/java/com/android/server/pm/ParallelPackageParser.java 0 → 100644 +158 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.pm; import android.content.pm.PackageParser; import android.os.Process; import android.os.Trace; import android.util.DisplayMetrics; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; /** * Helper class for parallel parsing of packages using {@link PackageParser}. * <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 { private static final int QUEUE_CAPACITY = 10; private static final int MAX_THREADS = 4; private final String[] mSeparateProcesses; private final boolean mOnlyCore; private final DisplayMetrics mMetrics; private volatile String mInterruptedInThread; private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); private final ExecutorService mService = Executors.newFixedThreadPool(MAX_THREADS, new ThreadFactory() { private final AtomicInteger threadNum = new AtomicInteger(0); @Override public Thread newThread(final Runnable r) { return new Thread("package-parsing-thread" + threadNum.incrementAndGet()) { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); r.run(); } }; } }); ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps, DisplayMetrics metrics) { mSeparateProcesses = separateProcesses; mOnlyCore = onlyCoreApps; mMetrics = metrics; } static class ParseResult { PackageParser.Package pkg; // Parsed package File scanFile; // File that was parsed Throwable throwable; // Set if an error occurs during parsing @Override public String toString() { return "ParseResult{" + "pkg=" + pkg + ", scanFile=" + scanFile + ", throwable=" + throwable + '}'; } } /** * Take the parsed package from the parsing queue, waiting if necessary until the element * appears in the queue. * @return parsed package */ public ParseResult take() { try { if (mInterruptedInThread != null) { throw new InterruptedException("Interrupted in " + mInterruptedInThread); } return mQueue.take(); } catch (InterruptedException e) { // We cannot recover from interrupt here Thread.currentThread().interrupt(); throw new IllegalStateException(e); } } /** * Submits the file for parsing * @param scanFile file to scan * @param parseFlags parse falgs */ public void submit(File scanFile, int parseFlags) { mService.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); pr.scanFile = scanFile; pr.pkg = parsePackage(pp, scanFile, parseFlags); } catch (Throwable e) { pr.throwable = e; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } try { mQueue.put(pr); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // Propagate result to callers of take(). // This is helpful to prevent main thread from getting stuck waiting on // ParallelPackageParser to finish in case of interruption mInterruptedInThread = Thread.currentThread().getName(); } }); } @VisibleForTesting protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile, int parseFlags) throws PackageParser.PackageParserException { return packageParser.parsePackage(scanFile, parseFlags); } @Override public void close() { List<Runnable> unfinishedTasks = mService.shutdownNow(); if (!unfinishedTasks.isEmpty()) { throw new IllegalStateException("Not all tasks finished before calling close: " + unfinishedTasks); } } } services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.pm; import android.content.pm.PackageParser; import android.support.test.runner.AndroidJUnit4; import android.util.Log; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.util.HashSet; import java.util.Set; /** * Tests for {@link ParallelPackageParser} */ @RunWith(AndroidJUnit4.class) public class ParallelPackageParserTest { private static final String TAG = ParallelPackageParserTest.class.getSimpleName(); private ParallelPackageParser mParser; @Before public void setUp() { mParser = new TestParallelPackageParser(); } @Test(timeout = 1000) public void test() { Set<File> submittedFiles = new HashSet<>(); int fileCount = 15; for (int i = 0; i < fileCount; i++) { File file = new File("f" + i); mParser.submit(file, 0); submittedFiles.add(file); Log.d(TAG, "submitting " + file); } for (int i = 0; i < fileCount; i++) { ParallelPackageParser.ParseResult result = mParser.take(); Assert.assertNotNull(result); File parsedFile = result.scanFile; Log.d(TAG, "took " + parsedFile); Assert.assertNotNull(parsedFile); boolean removeSuccessful = submittedFiles.remove(parsedFile); Assert.assertTrue("Unexpected file " + parsedFile + ". Expected submitted files: " + submittedFiles, removeSuccessful); } } class TestParallelPackageParser extends ParallelPackageParser { TestParallelPackageParser() { super(null, false, null); } @Override protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile, int parseFlags) throws PackageParser.PackageParserException { // Do not actually parse the package for testing return null; } } } Loading
services/core/java/com/android/server/pm/PackageManagerService.java +39 −12 Original line number Diff line number Diff line Loading @@ -6670,7 +6670,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) { private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) { final File[] files = dir.listFiles(); if (ArrayUtils.isEmpty(files)) { Log.d(TAG, "No files in app dir " + dir); Loading @@ -6681,7 +6681,11 @@ public class PackageManagerService extends IPackageManager.Stub { Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags + " flags=0x" + Integer.toHexString(parseFlags)); } ParallelPackageParser parallelPackageParser = new ParallelPackageParser( mSeparateProcesses, mOnlyCore, mMetrics); // Submit files for parsing in parallel int fileCount = 0; for (File file : files) { final boolean isPackage = (isApkFile(file) || file.isDirectory()) && !PackageInstallerService.isStageName(file.getName()); Loading @@ -6689,20 +6693,43 @@ public class PackageManagerService extends IPackageManager.Stub { // Ignore entries which are not packages continue; } parallelPackageParser.submit(file, parseFlags); fileCount++; } // Process results one by one for (; fileCount > 0; fileCount--) { ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); Throwable throwable = parseResult.throwable; int errorCode = PackageManager.INSTALL_SUCCEEDED; if (throwable == null) { try { scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, scanFlags, currentTime, null); scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags, currentTime, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage()); errorCode = e.error; Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage()); } } else if (throwable instanceof PackageParser.PackageParserException) { PackageParser.PackageParserException e = (PackageParser.PackageParserException) throwable; errorCode = e.error; Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage()); } else { throw new IllegalStateException("Unexpected exception occurred while parsing " + parseResult.scanFile, throwable); } // Delete invalid userdata apps if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 && e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { logCriticalInfo(Log.WARN, "Deleting invalid package at " + file); removeCodePathLI(file); } errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) { logCriticalInfo(Log.WARN, "Deleting invalid package at " + parseResult.scanFile); removeCodePathLI(parseResult.scanFile); } } parallelPackageParser.close(); } private static File getSettingsProblemFile() { Loading
services/core/java/com/android/server/pm/ParallelPackageParser.java 0 → 100644 +158 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.pm; import android.content.pm.PackageParser; import android.os.Process; import android.os.Trace; import android.util.DisplayMetrics; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; /** * Helper class for parallel parsing of packages using {@link PackageParser}. * <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 { private static final int QUEUE_CAPACITY = 10; private static final int MAX_THREADS = 4; private final String[] mSeparateProcesses; private final boolean mOnlyCore; private final DisplayMetrics mMetrics; private volatile String mInterruptedInThread; private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); private final ExecutorService mService = Executors.newFixedThreadPool(MAX_THREADS, new ThreadFactory() { private final AtomicInteger threadNum = new AtomicInteger(0); @Override public Thread newThread(final Runnable r) { return new Thread("package-parsing-thread" + threadNum.incrementAndGet()) { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); r.run(); } }; } }); ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps, DisplayMetrics metrics) { mSeparateProcesses = separateProcesses; mOnlyCore = onlyCoreApps; mMetrics = metrics; } static class ParseResult { PackageParser.Package pkg; // Parsed package File scanFile; // File that was parsed Throwable throwable; // Set if an error occurs during parsing @Override public String toString() { return "ParseResult{" + "pkg=" + pkg + ", scanFile=" + scanFile + ", throwable=" + throwable + '}'; } } /** * Take the parsed package from the parsing queue, waiting if necessary until the element * appears in the queue. * @return parsed package */ public ParseResult take() { try { if (mInterruptedInThread != null) { throw new InterruptedException("Interrupted in " + mInterruptedInThread); } return mQueue.take(); } catch (InterruptedException e) { // We cannot recover from interrupt here Thread.currentThread().interrupt(); throw new IllegalStateException(e); } } /** * Submits the file for parsing * @param scanFile file to scan * @param parseFlags parse falgs */ public void submit(File scanFile, int parseFlags) { mService.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); pr.scanFile = scanFile; pr.pkg = parsePackage(pp, scanFile, parseFlags); } catch (Throwable e) { pr.throwable = e; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } try { mQueue.put(pr); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // Propagate result to callers of take(). // This is helpful to prevent main thread from getting stuck waiting on // ParallelPackageParser to finish in case of interruption mInterruptedInThread = Thread.currentThread().getName(); } }); } @VisibleForTesting protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile, int parseFlags) throws PackageParser.PackageParserException { return packageParser.parsePackage(scanFile, parseFlags); } @Override public void close() { List<Runnable> unfinishedTasks = mService.shutdownNow(); if (!unfinishedTasks.isEmpty()) { throw new IllegalStateException("Not all tasks finished before calling close: " + unfinishedTasks); } } }
services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.pm; import android.content.pm.PackageParser; import android.support.test.runner.AndroidJUnit4; import android.util.Log; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.util.HashSet; import java.util.Set; /** * Tests for {@link ParallelPackageParser} */ @RunWith(AndroidJUnit4.class) public class ParallelPackageParserTest { private static final String TAG = ParallelPackageParserTest.class.getSimpleName(); private ParallelPackageParser mParser; @Before public void setUp() { mParser = new TestParallelPackageParser(); } @Test(timeout = 1000) public void test() { Set<File> submittedFiles = new HashSet<>(); int fileCount = 15; for (int i = 0; i < fileCount; i++) { File file = new File("f" + i); mParser.submit(file, 0); submittedFiles.add(file); Log.d(TAG, "submitting " + file); } for (int i = 0; i < fileCount; i++) { ParallelPackageParser.ParseResult result = mParser.take(); Assert.assertNotNull(result); File parsedFile = result.scanFile; Log.d(TAG, "took " + parsedFile); Assert.assertNotNull(parsedFile); boolean removeSuccessful = submittedFiles.remove(parsedFile); Assert.assertTrue("Unexpected file " + parsedFile + ". Expected submitted files: " + submittedFiles, removeSuccessful); } } class TestParallelPackageParser extends ParallelPackageParser { TestParallelPackageParser() { super(null, false, null); } @Override protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile, int parseFlags) throws PackageParser.PackageParserException { // Do not actually parse the package for testing return null; } } }