Added SystemServerInitThreadPool
System services can use it during the boot to submit tasks that can be run in
parallel with the main thread.
Switched PersistentDataBlockService and FingerprintService from FgThread to
the new thread pool.
UiModeManagerService: update initial configurations on init thread. They run
while holding the mLock so no extra synchronization barriers are needed at a
later stage.
Test: manual - device boots without errors
Test: ParallelPackageParserTest passes
Change-Id: I548f34b0a18f61924e09a39afb12e085cde35442
diff --git a/core/java/com/android/internal/util/ConcurrentUtils.java b/core/java/com/android/internal/util/ConcurrentUtils.java
new file mode 100644
index 0000000..e35f9f4
--- /dev/null
+++ b/core/java/com/android/internal/util/ConcurrentUtils.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.util;
+
+import android.os.Process;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Utility methods for common functionality using java.util.concurrent package
+ *
+ * @hide
+ */
+public class ConcurrentUtils {
+
+ private ConcurrentUtils() {
+ }
+
+ /**
+ * Creates a thread pool using
+ * {@link java.util.concurrent.Executors#newFixedThreadPool(int, ThreadFactory)}
+ *
+ * @param nThreads the number of threads in the pool
+ * @param poolName base name of the threads in the pool
+ * @param linuxThreadPriority a Linux priority level. see {@link Process#setThreadPriority(int)}
+ * @return the newly created thread pool
+ */
+ public static ExecutorService newFixedThreadPool(int nThreads, String poolName,
+ int linuxThreadPriority) {
+ return Executors.newFixedThreadPool(nThreads,
+ new ThreadFactory() {
+ private final AtomicInteger threadNum = new AtomicInteger(0);
+
+ @Override
+ public Thread newThread(final Runnable r) {
+ return new Thread(poolName + threadNum.incrementAndGet()) {
+ @Override
+ public void run() {
+ Process.setThreadPriority(linuxThreadPriority);
+ r.run();
+ }
+ };
+ }
+ });
+ }
+
+ /**
+ * Waits if necessary for the computation to complete, and then retrieves its result.
+ * <p>If {@code InterruptedException} occurs, this method will interrupt the current thread
+ * and throw {@code IllegalStateException}</p>
+ *
+ * @param future future to wait for result
+ * @param description short description of the operation
+ * @return the computed result
+ * @throws IllegalStateException if interrupted during wait
+ * @throws RuntimeException if an error occurs while waiting for {@link Future#get()}
+ * @see Future#get()
+ */
+ public static <T> T waitForFutureNoInterrupt(Future<T> future, String description) {
+ try {
+ return future.get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException(description + " interrupted");
+ } catch (ExecutionException e) {
+ throw new RuntimeException(description + " failed", e);
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 8a0d4df..417d375 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -116,12 +116,12 @@
@Override
public void onStart() {
// Do init on a separate thread, will join in PHASE_ACTIVITY_MANAGER_READY
- FgThread.getHandler().post(() -> {
+ SystemServerInitThreadPool.get().submit(() -> {
enforceChecksumValidity();
formatIfOemUnlockEnabled();
publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
mInitDoneSignal.countDown();
- });
+ }, TAG + ".onStart");
}
@Override
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
new file mode 100644
index 0000000..d196850
--- /dev/null
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import android.os.Build;
+import android.os.Process;
+import android.util.Slog;
+
+import com.android.internal.util.ConcurrentUtils;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Thread pool used during initialization of system server.
+ * <p>System services can {@link #submit(Runnable)} tasks for execution during boot.
+ * The pool will be shut down after {@link SystemService#PHASE_BOOT_COMPLETED}.
+ * New tasks <em>should not</em> be submitted afterwards.
+ *
+ * @hide
+ */
+public class SystemServerInitThreadPool {
+ private static final String TAG = SystemServerInitThreadPool.class.getSimpleName();
+ private static final int SHUTDOWN_TIMEOUT_MILLIS = 20000;
+ private static final boolean IS_DEBUGGABLE = Build.IS_DEBUGGABLE;
+
+ private static SystemServerInitThreadPool sInstance;
+
+ private ExecutorService mService = ConcurrentUtils.newFixedThreadPool(2,
+ "system-server-init-thread", Process.THREAD_PRIORITY_FOREGROUND);
+
+ public static synchronized SystemServerInitThreadPool get() {
+ if (sInstance == null) {
+ sInstance = new SystemServerInitThreadPool();
+ }
+ Preconditions.checkState(sInstance.mService != null, "Cannot get " + TAG
+ + " - it has been shut down");
+ return sInstance;
+ }
+
+ public Future<?> submit(Runnable runnable, String description) {
+ if (IS_DEBUGGABLE) {
+ return mService.submit(() -> {
+ Slog.d(TAG, "Started executing " + description);
+ try {
+ runnable.run();
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Failure in " + description + ": " + e, e);
+ throw e;
+ }
+ Slog.d(TAG, "Finished executing " + description);
+ });
+ }
+ return mService.submit(runnable);
+ }
+
+ static synchronized void shutdown() {
+ if (sInstance != null && sInstance.mService != null) {
+ sInstance.mService.shutdown();
+ boolean terminated;
+ try {
+ terminated = sInstance.mService.awaitTermination(SHUTDOWN_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException(TAG + " init interrupted");
+ }
+ List<Runnable> unstartedRunnables = sInstance.mService.shutdownNow();
+ if (!terminated) {
+ throw new IllegalStateException("Cannot shutdown. Unstarted tasks "
+ + unstartedRunnables);
+ }
+ sInstance.mService = null; // Make mService eligible for GC
+ Slog.d(TAG, "Shutdown successful");
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 6ea6fb7..5e1e1e0 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -208,11 +208,13 @@
Settings.Secure.UI_NIGHT_MODE, defaultNightMode);
// Update the initial, static configurations.
- synchronized (this) {
- updateConfigurationLocked();
- sendConfigurationLocked();
- }
+ SystemServerInitThreadPool.get().submit(() -> {
+ synchronized (mLock) {
+ updateConfigurationLocked();
+ sendConfigurationLocked();
+ }
+ }, TAG + ".onStart");
publishBinderService(Context.UI_MODE_SERVICE, mService);
}
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 3f0ebf2..b0f67a8 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -54,7 +54,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
-import com.android.server.FgThread;
+import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import org.json.JSONArray;
@@ -1077,7 +1077,7 @@
@Override
public void onStart() {
publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
- FgThread.getHandler().post(() -> getFingerprintDaemon());
+ SystemServerInitThreadPool.get().submit(this::getFingerprintDaemon, TAG + ".onStart");
listenForUserSwitches();
}
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 158cfc94..7312547 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -22,15 +22,13 @@
import android.util.DisplayMetrics;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ConcurrentUtils;
import java.io.File;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
@@ -51,21 +49,8 @@
private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
- private final ExecutorService mService = Executors.newFixedThreadPool(MAX_THREADS,
- new ThreadFactory() {
- private final AtomicInteger threadNum = new AtomicInteger(0);
-
- @Override
- public Thread newThread(final Runnable r) {
- return new Thread("package-parsing-thread" + threadNum.incrementAndGet()) {
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
- r.run();
- }
- };
- }
- });
+ private final ExecutorService mService = ConcurrentUtils.newFixedThreadPool(MAX_THREADS,
+ "package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND);
ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps,
DisplayMetrics metrics) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1d550d2..5b46f51 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -345,6 +345,8 @@
// Create the system service manager.
mSystemServiceManager = new SystemServiceManager(mSystemContext);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
+ // Prepare the thread pool for init tasks that can be parallelized
+ SystemServerInitThreadPool.get();
} finally {
traceEnd(); // InitBeforeStartServices
}
@@ -362,6 +364,7 @@
} finally {
traceEnd();
}
+ SystemServerInitThreadPool.shutdown();
// For debug builds, log event loop stalls to dropbox for analysis.
if (StrictMode.conditionallyEnableDebugLogging()) {