Home | History | Annotate | Download | only in pm
      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