View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.util;
20  
21  import java.io.OutputStreamWriter;
22  import java.io.PrintStream;
23  import java.io.PrintWriter;
24  import java.lang.Thread.UncaughtExceptionHandler;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.nio.charset.StandardCharsets;
28  import java.util.Set;
29  import java.util.concurrent.LinkedBlockingQueue;
30  import java.util.concurrent.ThreadFactory;
31  import java.util.concurrent.ThreadPoolExecutor;
32  import java.util.concurrent.TimeUnit;
33  import java.util.concurrent.atomic.AtomicInteger;
34  
35  import org.apache.hadoop.hbase.classification.InterfaceAudience;
36  import org.apache.hadoop.util.ReflectionUtils;
37  import org.apache.hadoop.util.StringUtils;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * Thread Utility
43   */
44  @InterfaceAudience.Private
45  public class Threads {
46    private static final Logger LOG = LoggerFactory.getLogger(Threads.class);
47    private static final AtomicInteger poolNumber = new AtomicInteger(1);
48  
49    public static final UncaughtExceptionHandler LOGGING_EXCEPTION_HANDLER =
50      new UncaughtExceptionHandler() {
51      @Override
52      public void uncaughtException(Thread t, Throwable e) {
53        LOG.warn("Thread:" + t + " exited with Exception:"
54            + StringUtils.stringifyException(e));
55      }
56    };
57  
58    /**
59     * Utility method that sets name, daemon status and starts passed thread.
60     * @param t thread to run
61     * @return Returns the passed Thread <code>t</code>.
62     */
63    public static Thread setDaemonThreadRunning(final Thread t) {
64      return setDaemonThreadRunning(t, t.getName());
65    }
66  
67    /**
68     * Utility method that sets name, daemon status and starts passed thread.
69     * @param t thread to frob
70     * @param name new name
71     * @return Returns the passed Thread <code>t</code>.
72     */
73    public static Thread setDaemonThreadRunning(final Thread t,
74      final String name) {
75      return setDaemonThreadRunning(t, name, null);
76    }
77  
78    /**
79     * Utility method that sets name, daemon status and starts passed thread.
80     * @param t thread to frob
81     * @param name new name
82     * @param handler A handler to set on the thread.  Pass null if want to
83     * use default handler.
84     * @return Returns the passed Thread <code>t</code>.
85     */
86    public static Thread setDaemonThreadRunning(final Thread t,
87      final String name, final UncaughtExceptionHandler handler) {
88      t.setName(name);
89      if (handler != null) {
90        t.setUncaughtExceptionHandler(handler);
91      }
92      t.setDaemon(true);
93      t.start();
94      return t;
95    }
96  
97    /**
98     * Shutdown passed thread using isAlive and join.
99     * @param t Thread to shutdown
100    */
101   public static void shutdown(final Thread t) {
102     shutdown(t, 0);
103   }
104 
105   /**
106    * Shutdown passed thread using isAlive and join.
107    * @param joinwait Pass 0 if we're to wait forever.
108    * @param t Thread to shutdown
109    */
110   public static void shutdown(final Thread t, final long joinwait) {
111     if (t == null) return;
112     while (t.isAlive()) {
113       try {
114         t.join(joinwait);
115       } catch (InterruptedException e) {
116         LOG.warn(t.getName() + "; joinwait=" + joinwait, e);
117       }
118     }
119   }
120 
121 
122   /**
123    * @param t Waits on the passed thread to die dumping a threaddump every
124    * minute while its up.
125    * @throws InterruptedException
126    */
127   public static void threadDumpingIsAlive(final Thread t)
128   throws InterruptedException {
129     if (t == null) {
130       return;
131     }
132 
133     while (t.isAlive()) {
134       t.join(60 * 1000);
135       if (t.isAlive()) {
136         printThreadInfo(System.out,
137             "Automatic Stack Trace every 60 seconds waiting on " +
138             t.getName());
139       }
140     }
141   }
142 
143   /**
144    * If interrupted, just prints out the interrupt on STDOUT, resets interrupt and returns
145    * @param millis How long to sleep for in milliseconds.
146    */
147   public static void sleep(long millis) {
148     try {
149       Thread.sleep(millis);
150     } catch (InterruptedException e) {
151       e.printStackTrace();
152       Thread.currentThread().interrupt();
153     }
154   }
155 
156   /**
157    * Sleeps for the given amount of time even if interrupted. Preserves
158    * the interrupt status.
159    * @param msToWait the amount of time to sleep in milliseconds
160    */
161   public static void sleepWithoutInterrupt(final long msToWait) {
162     long timeMillis = System.currentTimeMillis();
163     long endTime = timeMillis + msToWait;
164     boolean interrupted = false;
165     while (timeMillis < endTime) {
166       try {
167         Thread.sleep(endTime - timeMillis);
168       } catch (InterruptedException ex) {
169         interrupted = true;
170       }
171       timeMillis = System.currentTimeMillis();
172     }
173 
174     if (interrupted) {
175       Thread.currentThread().interrupt();
176     }
177   }
178 
179   /**
180    * Create a new CachedThreadPool with a bounded number as the maximum
181    * thread size in the pool.
182    *
183    * @param maxCachedThread the maximum thread could be created in the pool
184    * @param timeout the maximum time to wait
185    * @param unit the time unit of the timeout argument
186    * @param threadFactory the factory to use when creating new threads
187    * @return threadPoolExecutor the cachedThreadPool with a bounded number
188    * as the maximum thread size in the pool.
189    */
190   public static ThreadPoolExecutor getBoundedCachedThreadPool(
191       int maxCachedThread, long timeout, TimeUnit unit,
192       ThreadFactory threadFactory) {
193     ThreadPoolExecutor boundedCachedThreadPool =
194       new ThreadPoolExecutor(maxCachedThread, maxCachedThread, timeout,
195         unit, new LinkedBlockingQueue<Runnable>(), threadFactory);
196     // allow the core pool threads timeout and terminate
197     boundedCachedThreadPool.allowCoreThreadTimeOut(true);
198     return boundedCachedThreadPool;
199   }
200 
201 
202   /**
203    * Returns a {@link java.util.concurrent.ThreadFactory} that names each created thread uniquely,
204    * with a common prefix.
205    * @param prefix The prefix of every created Thread's name
206    * @return a {@link java.util.concurrent.ThreadFactory} that names threads
207    */
208   public static ThreadFactory getNamedThreadFactory(final String prefix) {
209     SecurityManager s = System.getSecurityManager();
210     final ThreadGroup threadGroup = (s != null) ? s.getThreadGroup() : Thread.currentThread()
211         .getThreadGroup();
212 
213     return new ThreadFactory() {
214       final AtomicInteger threadNumber = new AtomicInteger(1);
215       private final int poolNumber = Threads.poolNumber.getAndIncrement();
216       final ThreadGroup group = threadGroup;
217 
218       @Override
219       public Thread newThread(Runnable r) {
220         final String name = prefix + "-pool" + poolNumber + "-t" + threadNumber.getAndIncrement();
221         return new Thread(group, r, name);
222       }
223     };
224   }
225 
226   /**
227    * Same as {#newDaemonThreadFactory(String, UncaughtExceptionHandler)},
228    * without setting the exception handler.
229    */
230   public static ThreadFactory newDaemonThreadFactory(final String prefix) {
231     return newDaemonThreadFactory(prefix, null);
232   }
233 
234   /**
235    * Get a named {@link ThreadFactory} that just builds daemon threads.
236    * @param prefix name prefix for all threads created from the factory
237    * @param handler unhandles exception handler to set for all threads
238    * @return a thread factory that creates named, daemon threads with
239    *         the supplied exception handler and normal priority
240    */
241   public static ThreadFactory newDaemonThreadFactory(final String prefix,
242       final UncaughtExceptionHandler handler) {
243     final ThreadFactory namedFactory = getNamedThreadFactory(prefix);
244     return new ThreadFactory() {
245       @Override
246       public Thread newThread(Runnable r) {
247         Thread t = namedFactory.newThread(r);
248         if (handler != null) {
249           t.setUncaughtExceptionHandler(handler);
250         } else {
251           t.setUncaughtExceptionHandler(LOGGING_EXCEPTION_HANDLER);
252         }
253         if (!t.isDaemon()) {
254           t.setDaemon(true);
255         }
256         if (t.getPriority() != Thread.NORM_PRIORITY) {
257           t.setPriority(Thread.NORM_PRIORITY);
258         }
259         return t;
260       }
261 
262     };
263   }
264 
265   /** Sets an UncaughtExceptionHandler for the thread which logs the
266    * Exception stack if the thread dies.
267    */
268   public static void setLoggingUncaughtExceptionHandler(Thread t) {
269     t.setUncaughtExceptionHandler(LOGGING_EXCEPTION_HANDLER);
270   }
271 
272   private static Method PRINT_THREAD_INFO_METHOD = null;
273   private static boolean PRINT_THREAD_INFO_METHOD_WITH_PRINTSTREAM = true;
274 
275   /**
276    * Print all of the thread's information and stack traces. Wrapper around Hadoop's method.
277    *
278    * @param stream the stream to
279    * @param title a string title for the stack trace
280    */
281   public static synchronized void printThreadInfo(PrintStream stream, String title) {
282     if (PRINT_THREAD_INFO_METHOD == null) {
283       try {
284         // Hadoop 2.7+ declares printThreadInfo(PrintStream, String)
285         PRINT_THREAD_INFO_METHOD = ReflectionUtils.class.getMethod("printThreadInfo",
286           PrintStream.class, String.class);
287       } catch (NoSuchMethodException e) {
288         // Hadoop 2.6 and earlier declares printThreadInfo(PrintWriter, String)
289         PRINT_THREAD_INFO_METHOD_WITH_PRINTSTREAM = false;
290         try {
291           PRINT_THREAD_INFO_METHOD = ReflectionUtils.class.getMethod("printThreadInfo",
292             PrintWriter.class, String.class);
293         } catch (NoSuchMethodException e1) {
294           throw new RuntimeException("Cannot find method. Check hadoop jars linked", e1);
295         }
296       }
297       PRINT_THREAD_INFO_METHOD.setAccessible(true);
298     }
299 
300     try {
301       if (PRINT_THREAD_INFO_METHOD_WITH_PRINTSTREAM) {
302         PRINT_THREAD_INFO_METHOD.invoke(null, stream, title);
303       } else {
304         PRINT_THREAD_INFO_METHOD.invoke(null,
305           new PrintWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8)), title);
306       }
307     } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
308       throw new RuntimeException(e.getCause());
309     }
310   }
311 
312   /**
313    * Checks whether any non-daemon thread is running.
314    * @return true if there are non daemon threads running, otherwise false
315    */
316   public static boolean isNonDaemonThreadRunning() {
317     AtomicInteger nonDaemonThreadCount = new AtomicInteger();
318     Set<Thread> threads =  Thread.getAllStackTraces().keySet();
319     for (Thread t: threads) {
320       // Exclude current thread
321       if (t.getId() != Thread.currentThread().getId() && !t.isDaemon()) {
322         nonDaemonThreadCount.getAndIncrement();
323         LOG.info("Non daemon thread {} is still alive", t.getName());
324         LOG.info(printStackTrace(t));
325       }
326     }
327     return nonDaemonThreadCount.get() > 0;
328   }
329 
330   /*
331     Print stack trace of the passed thread
332    */
333   public static String printStackTrace(Thread t) {
334     StringBuilder sb = new StringBuilder();
335     for (StackTraceElement frame: t.getStackTrace()) {
336       sb.append("\n").append("    ").append(frame.toString());
337     }
338     return sb.toString();
339   }
340 }