ParallelPackageParser.java revision e29a5a11529dc7df82911b48b9f95461383cbcc2
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; 25import com.android.internal.util.ConcurrentUtils; 26 27import java.io.File; 28import java.util.List; 29import java.util.concurrent.ArrayBlockingQueue; 30import java.util.concurrent.BlockingQueue; 31import java.util.concurrent.ExecutorService; 32 33import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; 34 35/** 36 * Helper class for parallel parsing of packages using {@link PackageParser}. 37 * <p>Parsing requests are processed by a thread-pool of {@link #MAX_THREADS}. 38 * At any time, at most {@link #QUEUE_CAPACITY} results are kept in RAM</p> 39 */ 40class ParallelPackageParser implements AutoCloseable { 41 42 private static final int QUEUE_CAPACITY = 10; 43 private static final int MAX_THREADS = 4; 44 45 private final String[] mSeparateProcesses; 46 private final boolean mOnlyCore; 47 private final DisplayMetrics mMetrics; 48 private volatile String mInterruptedInThread; 49 50 private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); 51 52 private final ExecutorService mService = ConcurrentUtils.newFixedThreadPool(MAX_THREADS, 53 "package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND); 54 55 ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps, 56 DisplayMetrics metrics) { 57 mSeparateProcesses = separateProcesses; 58 mOnlyCore = onlyCoreApps; 59 mMetrics = metrics; 60 } 61 62 static class ParseResult { 63 64 PackageParser.Package pkg; // Parsed package 65 File scanFile; // File that was parsed 66 Throwable throwable; // Set if an error occurs during parsing 67 68 @Override 69 public String toString() { 70 return "ParseResult{" + 71 "pkg=" + pkg + 72 ", scanFile=" + scanFile + 73 ", throwable=" + throwable + 74 '}'; 75 } 76 } 77 78 /** 79 * Take the parsed package from the parsing queue, waiting if necessary until the element 80 * appears in the queue. 81 * @return parsed package 82 */ 83 public ParseResult take() { 84 try { 85 if (mInterruptedInThread != null) { 86 throw new InterruptedException("Interrupted in " + mInterruptedInThread); 87 } 88 return mQueue.take(); 89 } catch (InterruptedException e) { 90 // We cannot recover from interrupt here 91 Thread.currentThread().interrupt(); 92 throw new IllegalStateException(e); 93 } 94 } 95 96 /** 97 * Submits the file for parsing 98 * @param scanFile file to scan 99 * @param parseFlags parse falgs 100 */ 101 public void submit(File scanFile, int parseFlags) { 102 mService.submit(() -> { 103 ParseResult pr = new ParseResult(); 104 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]"); 105 try { 106 PackageParser pp = new PackageParser(); 107 pp.setSeparateProcesses(mSeparateProcesses); 108 pp.setOnlyCoreApps(mOnlyCore); 109 pp.setDisplayMetrics(mMetrics); 110 pr.scanFile = scanFile; 111 pr.pkg = parsePackage(pp, scanFile, parseFlags); 112 } catch (Throwable e) { 113 pr.throwable = e; 114 } finally { 115 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 116 } 117 try { 118 mQueue.put(pr); 119 } catch (InterruptedException e) { 120 Thread.currentThread().interrupt(); 121 // Propagate result to callers of take(). 122 // This is helpful to prevent main thread from getting stuck waiting on 123 // ParallelPackageParser to finish in case of interruption 124 mInterruptedInThread = Thread.currentThread().getName(); 125 } 126 }); 127 } 128 129 @VisibleForTesting 130 protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile, 131 int parseFlags) throws PackageParser.PackageParserException { 132 return packageParser.parsePackage(scanFile, parseFlags); 133 } 134 135 @Override 136 public void close() { 137 List<Runnable> unfinishedTasks = mService.shutdownNow(); 138 if (!unfinishedTasks.isEmpty()) { 139 throw new IllegalStateException("Not all tasks finished before calling close: " 140 + unfinishedTasks); 141 } 142 } 143} 144