ParallelPackageParser.java revision 5c50e8630164d7d9a1a097f70d2f8bcbf1bd854f
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.server.pm; 18 19import android.content.pm.PackageParser; 20import android.os.Process; 21import android.os.Trace; 22import android.util.DisplayMetrics; 23 24import com.android.internal.annotations.VisibleForTesting; 25 26import java.io.File; 27import java.util.List; 28import java.util.concurrent.ArrayBlockingQueue; 29import java.util.concurrent.BlockingQueue; 30import java.util.concurrent.ExecutorService; 31import java.util.concurrent.Executors; 32import java.util.concurrent.ThreadFactory; 33import java.util.concurrent.atomic.AtomicInteger; 34 35import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; 36 37/** 38 * Helper class for parallel parsing of packages using {@link PackageParser}. 39 * <p>Parsing requests are processed by a thread-pool of {@link #MAX_THREADS}. 40 * At any time, at most {@link #QUEUE_CAPACITY} results are kept in RAM</p> 41 */ 42class ParallelPackageParser implements AutoCloseable { 43 44 private static final int QUEUE_CAPACITY = 10; 45 private static final int MAX_THREADS = 4; 46 47 private final String[] mSeparateProcesses; 48 private final boolean mOnlyCore; 49 private final DisplayMetrics mMetrics; 50 private final File mCacheDir; 51 private volatile String mInterruptedInThread; 52 53 private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); 54 55 private final ExecutorService mService = Executors.newFixedThreadPool(MAX_THREADS, 56 new ThreadFactory() { 57 private final AtomicInteger threadNum = new AtomicInteger(0); 58 59 @Override 60 public Thread newThread(final Runnable r) { 61 return new Thread("package-parsing-thread" + threadNum.incrementAndGet()) { 62 @Override 63 public void run() { 64 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); 65 r.run(); 66 } 67 }; 68 } 69 }); 70 71 ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps, 72 DisplayMetrics metrics, File cacheDir) { 73 mSeparateProcesses = separateProcesses; 74 mOnlyCore = onlyCoreApps; 75 mMetrics = metrics; 76 mCacheDir = cacheDir; 77 } 78 79 static class ParseResult { 80 81 PackageParser.Package pkg; // Parsed package 82 File scanFile; // File that was parsed 83 Throwable throwable; // Set if an error occurs during parsing 84 85 @Override 86 public String toString() { 87 return "ParseResult{" + 88 "pkg=" + pkg + 89 ", scanFile=" + scanFile + 90 ", throwable=" + throwable + 91 '}'; 92 } 93 } 94 95 /** 96 * Take the parsed package from the parsing queue, waiting if necessary until the element 97 * appears in the queue. 98 * @return parsed package 99 */ 100 public ParseResult take() { 101 try { 102 if (mInterruptedInThread != null) { 103 throw new InterruptedException("Interrupted in " + mInterruptedInThread); 104 } 105 return mQueue.take(); 106 } catch (InterruptedException e) { 107 // We cannot recover from interrupt here 108 Thread.currentThread().interrupt(); 109 throw new IllegalStateException(e); 110 } 111 } 112 113 /** 114 * Submits the file for parsing 115 * @param scanFile file to scan 116 * @param parseFlags parse falgs 117 */ 118 public void submit(File scanFile, int parseFlags) { 119 mService.submit(() -> { 120 ParseResult pr = new ParseResult(); 121 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]"); 122 try { 123 PackageParser pp = new PackageParser(); 124 pp.setSeparateProcesses(mSeparateProcesses); 125 pp.setOnlyCoreApps(mOnlyCore); 126 pp.setDisplayMetrics(mMetrics); 127 pp.setCacheDir(mCacheDir); 128 pr.scanFile = scanFile; 129 pr.pkg = parsePackage(pp, scanFile, parseFlags); 130 } catch (Throwable e) { 131 pr.throwable = e; 132 } finally { 133 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 134 } 135 try { 136 mQueue.put(pr); 137 } catch (InterruptedException e) { 138 Thread.currentThread().interrupt(); 139 // Propagate result to callers of take(). 140 // This is helpful to prevent main thread from getting stuck waiting on 141 // ParallelPackageParser to finish in case of interruption 142 mInterruptedInThread = Thread.currentThread().getName(); 143 } 144 }); 145 } 146 147 @VisibleForTesting 148 protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile, 149 int parseFlags) throws PackageParser.PackageParserException { 150 return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */); 151 } 152 153 @Override 154 public void close() { 155 List<Runnable> unfinishedTasks = mService.shutdownNow(); 156 if (!unfinishedTasks.isEmpty()) { 157 throw new IllegalStateException("Not all tasks finished before calling close: " 158 + unfinishedTasks); 159 } 160 } 161} 162