Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 57899a3e authored by Kevin Jeon's avatar Kevin Jeon
Browse files

Move getResources() off of boot critical path

When starting VoiceInteractionManagerService, there is a getResources()
call that blocks boot for 150ms. This happens when the service's
RoleObserver is instantiated. This change updates this work to happen in
a separate thread, with results being returned in a future.

This change also updates SystemServerInitThreadPool to allow submitted
tasks to return results.

Test: verify in traces that resources are loaded in a separate thread
Bug: 406841419
Flag: android.server.voiceinteractionmanagerservice_get_resources_in_init_thread
Change-Id: I029a09e7d1bb6af1aeebcd65bfefa3a9a46540fe
parent 34ae379c
Loading
Loading
Loading
Loading
+28 −11
Original line number Diff line number Diff line
@@ -32,8 +32,10 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

@@ -72,26 +74,39 @@ public final class SystemServerInitThreadPool implements Dumpable {
                "system-server-init-thread", Process.THREAD_PRIORITY_FOREGROUND);
    }

    private static SystemServerInitThreadPool getInstance() {
        SystemServerInitThreadPool instance;
        synchronized (LOCK) {
            Preconditions.checkState(sInstance != null, "Cannot get " + TAG
                    + " - it has been shut down");
            instance = sInstance;
        }
        return instance;
    }

    /**
     * Submits a task for execution.
     * Submits a task for execution and returns a Future that returns null on success.
     *
     * @throws IllegalStateException if it hasn't been started or has been shut down already.
     */
    public static @NonNull Future<?> submit(@NonNull Runnable runnable,
            @NonNull String description) {
        Objects.requireNonNull(description, "description cannot be null");

        SystemServerInitThreadPool instance;
        synchronized (LOCK) {
            Preconditions.checkState(sInstance != null, "Cannot get " + TAG
                    + " - it has been shut down");
            instance = sInstance;
        return getInstance().submitTask(Executors.callable(runnable), description);
    }

        return instance.submitTask(runnable, description);
    /**
     * Submits a task for execution and returns a Future containing the Callable's result.
     *
     * @throws IllegalStateException if it hasn't been started or has been shut down already.
     */
    public static <T> @NonNull Future<T> submit(@NonNull Callable<T> callable,
            @NonNull String description) {
        Objects.requireNonNull(description, "description cannot be null");
        return getInstance().submitTask(callable, description);
    }

    private @NonNull Future<?> submitTask(@NonNull Runnable runnable,
    private <T> @NonNull Future<T> submitTask(@NonNull Callable<T> callable,
            @NonNull String description) {
        synchronized (mPendingTasks) {
            Preconditions.checkState(!mShutDown, TAG + " already shut down");
@@ -103,8 +118,9 @@ public final class SystemServerInitThreadPool implements Dumpable {
            if (IS_DEBUGGABLE) {
                Slog.d(TAG, "Started executing " + description);
            }
            T result = null;
            try {
                runnable.run();
                result = callable.call();
            } catch (RuntimeException e) {
                Slog.e(TAG, "Failure in " + description + ": " + e, e);
                traceLog.traceEnd();
@@ -117,6 +133,7 @@ public final class SystemServerInitThreadPool implements Dumpable {
                Slog.d(TAG, "Finished executing " + description);
            }
            traceLog.traceEnd();
            return result;
        });
    }

+14 −3
Original line number Diff line number Diff line
@@ -2018,6 +2018,13 @@ public final class SystemServer implements Dumpable {
            dpms = mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
            t.traceEnd();

            // If this flag is disabled, this service is started later.
            if (android.server.Flags.voiceinteractionmanagerserviceGetResourcesInInitThread()) {
                t.traceBegin("StartVoiceRecognitionManager");
                mSystemServiceManager.startService(VoiceInteractionManagerService.class);
                t.traceEnd();
            }

            t.traceBegin("StartStatusBarManagerService");
            try {
                statusBar = new StatusBarManagerService(context);
@@ -2529,9 +2536,13 @@ public final class SystemServer implements Dumpable {
            // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
            // of initializing various settings.  It will internally modify its behavior
            // based on that feature.
            //
            // If this flag is enabled, this service will have begun initializing earlier.
            if (!android.server.Flags.voiceinteractionmanagerserviceGetResourcesInInitThread()) {
                t.traceBegin("StartVoiceRecognitionManager");
                mSystemServiceManager.startService(VoiceInteractionManagerService.class);
                t.traceEnd();
            }

            if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
                t.traceBegin("StartGestureLauncher");
+24 −3
Original line number Diff line number Diff line
@@ -110,6 +110,7 @@ import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SoundTriggerInternal;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.pm.UserManagerInternal;
@@ -127,8 +128,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;

/**
 * SystemService that publishes an IVoiceInteractionManagerService.
@@ -346,6 +348,13 @@ public class VoiceInteractionManagerService extends SystemService {
        public void onPreCreatedUserConversion(int userId) {
            Slogf.d(TAG, "onPreCreatedUserConversion(%d): calling onRoleHoldersChanged() again",
                    userId);
            if (mServiceStub.mRoleObserver == null) {
                try {
                    mServiceStub.mRoleObserver = mServiceStub.mRoleObserverFuture.get();
                } catch (ExecutionException | InterruptedException e) {
                    Slogf.wtf(TAG, "Unable to get role observer for user %d", userId);
                }
            }
            mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
                                                UserHandle.of(userId));
        }
@@ -416,11 +425,23 @@ public class VoiceInteractionManagerService extends SystemService {

        private final boolean mEnableService;
        // TODO(b/226201975): remove reference once RoleService supports pre-created users
        private final RoleObserver mRoleObserver;
        private final Future<RoleObserver> mRoleObserverFuture;
        private RoleObserver mRoleObserver;

        VoiceInteractionManagerServiceStub() {
            mEnableService = shouldEnableService(mContext);

            // If this flag is enabled, initialize in SystemServerInitThreadPool. This is intended
            // to avoid blocking system_server start on loading resources.
            if (android.server.Flags.voiceinteractionmanagerserviceGetResourcesInInitThread()) {
                mRoleObserver = null;
                mRoleObserverFuture = SystemServerInitThreadPool.submit(() -> {
                    return new RoleObserver(mContext.getMainExecutor());
                }, "RoleObserver");
            } else {
                mRoleObserver = new RoleObserver(mContext.getMainExecutor());
                mRoleObserverFuture = null;
            }
        }

        void handleUserStop(String packageName, int userHandle) {