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