From e29a5a11529dc7df82911b48b9f95461383cbcc2 Mon Sep 17 00:00:00 2001 From: Fyodor Kupolov Date: Fri, 16 Dec 2016 16:14:17 -0800 Subject: [PATCH] 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 --- .../internal/util/ConcurrentUtils.java | 89 +++++++++++++++++ .../server/PersistentDataBlockService.java | 4 +- .../server/SystemServerInitThreadPool.java | 96 +++++++++++++++++++ .../android/server/UiModeManagerService.java | 10 +- .../fingerprint/FingerprintService.java | 4 +- .../server/pm/ParallelPackageParser.java | 21 +--- .../java/com/android/server/SystemServer.java | 3 + 7 files changed, 201 insertions(+), 26 deletions(-) create mode 100644 core/java/com/android/internal/util/ConcurrentUtils.java create mode 100644 services/core/java/com/android/server/SystemServerInitThreadPool.java 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 0000000000000..e35f9f45acfe7 --- /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. + *

If {@code InterruptedException} occurs, this method will interrupt the current thread + * and throw {@code IllegalStateException}

+ * + * @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 waitForFutureNoInterrupt(Future 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 8a0d4df8ec276..417d375f302a8 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -116,12 +116,12 @@ public class PersistentDataBlockService extends SystemService { @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 0000000000000..d1968507508ae --- /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. + *

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 should not 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 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 6ea6fb7c9c037..5e1e1e0a59258 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -208,11 +208,13 @@ final class UiModeManagerService extends SystemService { 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 3f0ebf28bbfdf..b0f67a81db280 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 android.util.proto.ProtoOutputStream; 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 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe @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 158cfc946b732..73125473df03d 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.os.Trace; 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 @@ class ParallelPackageParser implements AutoCloseable { private final BlockingQueue 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 1d550d294560f..5b46f5144fd57 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -345,6 +345,8 @@ public final class SystemServer { // 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 @@ public final class SystemServer { } finally { traceEnd(); } + SystemServerInitThreadPool.shutdown(); // For debug builds, log event loop stalls to dropbox for analysis. if (StrictMode.conditionallyEnableDebugLogging()) {