content: /*
   * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
   * (the "License"). You may not use this work except in compliance with the License, which is
   * available at www.apache.org/licenses/LICENSE-2.0
   *
   * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
   * either express or implied, as more fully set forth in the License.
   *
   * See the NOTICE file distributed with this work for information regarding copyright ownership.
   */
  
  package alluxio.util;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.io.ByteArrayOutputStream;
  import java.io.PrintStream;
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.io.UnsupportedEncodingException;
  import java.lang.management.ManagementFactory;
  import java.lang.management.ThreadInfo;
  import java.lang.management.ThreadMXBean;
  import java.nio.charset.Charset;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.TimeUnit;
  import javax.annotation.concurrent.GuardedBy;
  
  /**
   * Utility method for working with threads.
   */
  public final class ThreadUtils {
    private static final Logger LOG = LoggerFactory.getLogger(ThreadUtils.class);
    private static final ThreadMXBean THREAD_BEAN =
        ManagementFactory.getThreadMXBean();
  
    /**
     * @param thread a thread
     * @return a human-readable representation of the thread's stack trace
     */
    public static String formatStackTrace(Thread thread) {
      Throwable t = new Throwable(String.format("Stack trace for thread %s (State: %s):",
          thread.getName(), thread.getState()));
      t.setStackTrace(thread.getStackTrace());
      return formatStackTrace(t);
    }
  
    /**
     * Return formatted stacktrace of Throwable instance.
     * @param t - a Throwable instance
     * @return a human-readable representation of the Throwable's stack trace
     */
    public static String formatStackTrace(Throwable t) {
      StringWriter sw = new StringWriter();
      t.printStackTrace(new PrintWriter(sw));
      return sw.toString();
    }
  
    /**
     * Logs a stack trace for all threads currently running in the JVM, similar to jstack.
     */
    public static void logAllThreads() {
      logThreadInfo(LOG, "Dumping all threads:", 0);
    }
  
    /**
     * From https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html
     *
     * The following method shuts down an ExecutorService in two phases, first by calling shutdown to
     * reject incoming tasks, and then calling shutdownNow, if necessary, to cancel any lingering
     * tasks.
     *
     * @param pool the executor service to shutdown
     * @param timeoutMs how long to wait for the service to shut down
     */
    public static void shutdownAndAwaitTermination(ExecutorService pool, long timeoutMs) {
      pool.shutdown(); // Disable new tasks from being submitted
      try {
        // Wait a while for existing tasks to terminate
        if (!pool.awaitTermination(timeoutMs / 2, TimeUnit.MILLISECONDS)) {
          pool.shutdownNow(); // Cancel currently executing tasks
          // Wait a while for tasks to respond to being cancelled
          if (!pool.awaitTermination(timeoutMs / 2, TimeUnit.MILLISECONDS)) {
            LOG.warn("Pool did not terminate");
          }
        }
      } catch (InterruptedException ie) {
        Thread.currentThread().interrupt();
        // (Re-)Cancel if current thread also interrupted
        pool.shutdownNow();
      }
    }
  
    /**
     * @return a string representation of the current thread
     */
    public static String getCurrentThreadIdentifier() {
      return getThreadIdentifier(Thread.currentThread());
    }
  
    /**
     * @param thread the thread
     * @return a string representation of the given thread
     */
    public static String getThreadIdentifier(Thread thread) {
      return String.format("%d(%s)", thread.getId(), thread.getName());
    }
  
    private static String getTaskName(long id, String name) {
      if (name == null) {
        return Long.toString(id);
      }
      return id + " (" + name + ")";
    }
  
    /**
     * Prints the information and stack traces of all threads.
     * In order not to pause the JVM when there are tons of threads, thread stacks are printed
     * one by one. So the thread stacks are not guaranteed to be based on one consistent
     * snapshot.
     *
     * @param stream the stream to
     * @param title  a string title for the stack trace
     */
    public static synchronized void printThreadInfo(PrintStream stream, String title) {
      stream.println("Process Thread Dump: " + title);
      stream.println(THREAD_BEAN.getThreadCount() + " active threads");
      long[] threadIds = THREAD_BEAN.getAllThreadIds();
      for (long id : threadIds) {
        ThreadInfo info = THREAD_BEAN.getThreadInfo(id, Integer.MAX_VALUE);
        if (info == null) {
          // The thread is no longer active, ignore
          continue;
        }
        stream.print(info.toString());
      }
      stream.flush();
    }
  
    /**
     * The value of the previous log time, unit is ms.
     */
    @GuardedBy("ThreadUtils.class")
    private static long sPreviousLogTime = 0;
  
    /**
     * Log the current thread stacks at INFO level.
     * @param log the logger that logs the stack trace
     * @param title a descriptive title for the call stacks
     * @param minInterval the minimum time from the last, unit is second
     */
    public static void logThreadInfo(Logger log,
        String title,
        long minInterval) {
      boolean dumpStack = false;
      if (log.isInfoEnabled()) {
        synchronized (ThreadUtils.class) {
          long now = System.nanoTime() / 1000000;
          if (now - sPreviousLogTime >= minInterval * 1000) {
            sPreviousLogTime = now;
            dumpStack = true;
          }
        }
        if (dumpStack) {
          try {
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            printThreadInfo(new PrintStream(buffer, false, "UTF-8"), title);
            log.info(buffer.toString(Charset.defaultCharset().name()));
          } catch (UnsupportedEncodingException ignored) {
            // swallow the nit exception to keep silent.
          }
        }
      }
    }
  
    private ThreadUtils() {} // prevent instantiation of utils class
  }
  