ParallelPackageParser.java revision fd6f4fb264d56726cd0e2fed731ad60bbe4aa06f
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 volatile String mInterruptedInThread; 51 52 private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); 53 54 private final ExecutorService mService = Executors.newFixedThreadPool(MAX_THREADS, 55 new ThreadFactory() { 56 private final AtomicInteger threadNum = new AtomicInteger(0); 57 58 @Override 59 public Thread newThread(final Runnable r) { 60 return new Thread("package-parsing-thread" + threadNum.incrementAndGet()) { 61 @Override 62 public void run() { 63 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); 64 r.run(); 65 } 66 }; 67 } 68 }); 69 70 ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps, 71 DisplayMetrics metrics) { 72 mSeparateProcesses = separateProcesses; 73 mOnlyCore = onlyCoreApps; 74 mMetrics = metrics; 75 } 76 77 static class ParseResult { 78 79 PackageParser.Package pkg; // Parsed package 80 File scanFile; // File that was parsed 81 Throwable throwable; // Set if an error occurs during parsing 82 83 @Override 84 public String toString() { 85 return "ParseResult{" + 86 "pkg=" + pkg + 87 ", scanFile=" + scanFile + 88 ", throwable=" + throwable + 89 '}'; 90 } 91 } 92 93 /** 94 * Take the parsed package from the parsing queue, waiting if necessary until the element 95 * appears in the queue. 96 * @return parsed package 97 */ 98 public ParseResult take() { 99 try { 100 if (mInterruptedInThread != null) { 101 throw new InterruptedException("Interrupted in " + mInterruptedInThread); 102 } 103 return mQueue.take(); 104 } catch (InterruptedException e) { 105 // We cannot recover from interrupt here 106 Thread.currentThread().interrupt(); 107 throw new IllegalStateException(e); 108 } 109 } 110 111 /** 112 * Submits the file for parsing 113 * @param scanFile file to scan 114 * @param parseFlags parse falgs 115 */ 116 public void submit(File scanFile, int parseFlags) { 117 mService.submit(() -> { 118 ParseResult pr = new ParseResult(); 119 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]"); 120 try { 121 PackageParser pp = new PackageParser(); 122 pp.setSeparateProcesses(mSeparateProcesses); 123 pp.setOnlyCoreApps(mOnlyCore); 124 pp.setDisplayMetrics(mMetrics); 125 pr.scanFile = scanFile; 126 pr.pkg = parsePackage(pp, scanFile, parseFlags); 127 } catch (Throwable e) { 128 pr.throwable = e; 129 } finally { 130 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 131 } 132 try { 133 mQueue.put(pr); 134 } catch (InterruptedException e) { 135 Thread.currentThread().interrupt(); 136 // Propagate result to callers of take(). 137 // This is helpful to prevent main thread from getting stuck waiting on 138 // ParallelPackageParser to finish in case of interruption 139 mInterruptedInThread = Thread.currentThread().getName(); 140 } 141 }); 142 } 143 144 @VisibleForTesting 145 protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile, 146 int parseFlags) throws PackageParser.PackageParserException { 147 return packageParser.parsePackage(scanFile, parseFlags); 148 } 149 150 @Override 151 public void close() { 152 List<Runnable> unfinishedTasks = mService.shutdownNow(); 153 if (!unfinishedTasks.isEmpty()) { 154 throw new IllegalStateException("Not all tasks finished before calling close: " 155 + unfinishedTasks); 156 } 157 } 158} 159