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 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