content: /*
   * 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 com.android.server.am.ActivityManagerService;
  
  import java.util.ArrayList;
  import java.util.List;
  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(
              Runtime.getRuntime().availableProcessors(),
              "system-server-init-thread", Process.THREAD_PRIORITY_FOREGROUND);
  
      private List<String> mPendingTasks = new ArrayList<>();
  
      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) {
          synchronized (mPendingTasks) {
              mPendingTasks.add(description);
          }
          return mService.submit(() -> {
              if (IS_DEBUGGABLE) {
                  Slog.d(TAG, "Started executing " + description);
              }
              try {
                  runnable.run();
              } catch (RuntimeException e) {
                  Slog.e(TAG, "Failure in " + description + ": " + e, e);
                  throw e;
              }
              synchronized (mPendingTasks) {
                  mPendingTasks.remove(description);
              }
              if (IS_DEBUGGABLE) {
                  Slog.d(TAG, "Finished executing " + description);
              }
          });
      }
  
      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();
                  dumpStackTraces();
                  throw new IllegalStateException(TAG + " init interrupted");
              }
              if (!terminated) {
                  // dump stack must be called before shutdownNow() to collect stacktrace of threads
                  // in the thread pool.
                  dumpStackTraces();
              }
              List<Runnable> unstartedRunnables = sInstance.mService.shutdownNow();
              if (!terminated) {
                  final List<String> copy = new ArrayList<>();
                  synchronized (sInstance.mPendingTasks) {
                      copy.addAll(sInstance.mPendingTasks);
                  }
                  throw new IllegalStateException("Cannot shutdown. Unstarted tasks "
                          + unstartedRunnables + " Unfinished tasks " + copy);
              }
              sInstance.mService = null; // Make mService eligible for GC
              sInstance.mPendingTasks = null;
              Slog.d(TAG, "Shutdown successful");
          }
      }
  
      /**
       * A helper function to call ActivityManagerService.dumpStackTraces().
       */
      private static void dumpStackTraces() {
          final ArrayList<Integer> pids = new ArrayList<>();
          pids.add(Process.myPid());
          ActivityManagerService.dumpStackTraces(pids, null, null,
                  Watchdog.getInterestingNativePids());
      }
  }
  