View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.surefire.booter;
20  
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileNotFoundException;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.lang.management.ManagementFactory;
27  import java.lang.management.ThreadInfo;
28  import java.lang.management.ThreadMXBean;
29  import java.lang.reflect.InvocationTargetException;
30  import java.security.AccessControlException;
31  import java.security.AccessController;
32  import java.security.PrivilegedAction;
33  import java.util.concurrent.ScheduledExecutorService;
34  import java.util.concurrent.ScheduledThreadPoolExecutor;
35  import java.util.concurrent.Semaphore;
36  import java.util.concurrent.ThreadFactory;
37  import java.util.concurrent.atomic.AtomicBoolean;
38  
39  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
40  import org.apache.maven.surefire.api.booter.BaseProviderFactory;
41  import org.apache.maven.surefire.api.booter.Command;
42  import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
43  import org.apache.maven.surefire.api.booter.ForkingReporterFactory;
44  import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
45  import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
46  import org.apache.maven.surefire.api.booter.Shutdown;
47  import org.apache.maven.surefire.api.fork.ForkNodeArguments;
48  import org.apache.maven.surefire.api.provider.CommandListener;
49  import org.apache.maven.surefire.api.provider.ProviderParameters;
50  import org.apache.maven.surefire.api.provider.SurefireProvider;
51  import org.apache.maven.surefire.api.testset.TestSetFailedException;
52  import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
53  import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
54  import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils;
55  import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
56  
57  import static java.lang.Thread.currentThread;
58  import static java.util.ServiceLoader.load;
59  import static java.util.concurrent.TimeUnit.SECONDS;
60  import static org.apache.maven.surefire.api.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
61  import static org.apache.maven.surefire.api.util.ReflectionUtils.instantiateOneArg;
62  import static org.apache.maven.surefire.api.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
63  import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
64  import static org.apache.maven.surefire.booter.ProcessCheckerType.ALL;
65  import static org.apache.maven.surefire.booter.ProcessCheckerType.NATIVE;
66  import static org.apache.maven.surefire.booter.ProcessCheckerType.PING;
67  import static org.apache.maven.surefire.booter.SystemPropertyManager.setSystemProperties;
68  
69  /**
70   * The part of the booter that is unique to a forked vm.
71   * <br>
72   * Deals with deserialization of the booter wire-level protocol
73   * <br>
74   *
75   * @author Jason van Zyl
76   * @author Emmanuel Venisse
77   * @author Kristian Rosenvold
78   */
79  public final class ForkedBooter {
80      private static final long DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30L;
81      private static final long PING_TIMEOUT_IN_SECONDS = 30L;
82      private static final String LAST_DITCH_SHUTDOWN_THREAD = "surefire-forkedjvm-last-ditch-daemon-shutdown-thread-";
83      private static final String PING_THREAD = "surefire-forkedjvm-ping-";
84      private static final String PROCESS_CHECKER_THREAD = "surefire-process-checker";
85      private static final String PROCESS_PIPES_ERROR =
86              "The channel (std/out or TCP/IP) failed to send a stream from this subprocess.";
87  
88      private final Semaphore exitBarrier = new Semaphore(0);
89  
90      private volatile MasterProcessChannelEncoder eventChannel;
91      private volatile ConsoleLogger logger;
92      private volatile MasterProcessChannelProcessorFactory channelProcessorFactory;
93      private volatile CommandReader commandReader;
94      private volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS;
95      private volatile PingScheduler pingScheduler;
96  
97      private ScheduledThreadPoolExecutor jvmTerminator;
98      private ProviderConfiguration providerConfiguration;
99      private ForkingReporterFactory forkingReporterFactory;
100     private StartupConfiguration startupConfiguration;
101     private Object testSet;
102 
103     private void setupBooter(
104             String tmpDir, String dumpFileName, String surefirePropsFileName, String effectiveSystemPropertiesFileName)
105             throws IOException {
106         BooterDeserializer booterDeserializer =
107                 new BooterDeserializer(createSurefirePropertiesIfFileExists(tmpDir, surefirePropsFileName));
108         setSystemProperties(new File(tmpDir, effectiveSystemPropertiesFileName));
109 
110         providerConfiguration = booterDeserializer.deserialize();
111         DumpErrorSingleton.getSingleton()
112                 .init(providerConfiguration.getReporterConfiguration().getReportsDirectory(), dumpFileName);
113 
114         int forkNumber = booterDeserializer.getForkNumber();
115 
116         if (isDebugging()) {
117             DumpErrorSingleton.getSingleton()
118                     .dumpText("Found Maven process ID " + booterDeserializer.getPluginPid() + " for the fork "
119                             + forkNumber + ".");
120         }
121 
122         startupConfiguration = booterDeserializer.getStartupConfiguration();
123 
124         String channelConfig = booterDeserializer.getConnectionString();
125         channelProcessorFactory = lookupDecoderFactory(channelConfig);
126         channelProcessorFactory.connect(channelConfig);
127         boolean isDebugging = isDebugging();
128         boolean debug = isDebugging || providerConfiguration.getMainCliOptions().contains(LOGGING_LEVEL_DEBUG);
129         ForkNodeArguments args = new ForkedNodeArg(forkNumber, debug);
130         eventChannel = channelProcessorFactory.createEncoder(args);
131         MasterProcessChannelDecoder decoder = channelProcessorFactory.createDecoder(args);
132 
133         flushEventChannelOnExit();
134 
135         forkingReporterFactory = createForkingReporterFactory();
136         logger = forkingReporterFactory.createTestReportListener();
137         commandReader = new CommandReader(decoder, providerConfiguration.getShutdown(), logger);
138 
139         pingScheduler = isDebugging ? null : listenToShutdownCommands(booterDeserializer.getPluginPid());
140 
141         systemExitTimeoutInSeconds = providerConfiguration.systemExitTimeout(DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS);
142 
143         AbstractPathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
144 
145         if (classpathConfiguration.isClassPathConfig()) {
146             if (startupConfiguration.isManifestOnlyJarRequestedAndUsable()) {
147                 classpathConfiguration
148                         .toRealPath(ClasspathConfiguration.class)
149                         .trickClassPathWhenManifestOnlyClasspath();
150             }
151             startupConfiguration.writeSurefireTestClasspathProperty();
152         }
153 
154         ClassLoader classLoader = currentThread().getContextClassLoader();
155         classLoader.setDefaultAssertionStatus(classpathConfiguration.isEnableAssertions());
156         boolean readTestsFromCommandReader = providerConfiguration.isReadTestsFromInStream();
157         testSet = createTestSet(providerConfiguration.getTestForFork(), readTestsFromCommandReader, classLoader);
158     }
159 
160     private void execute() {
161         try {
162             runSuitesInProcess();
163         } catch (Throwable t) {
164             Throwable e =
165                     t instanceof InvocationTargetException ? ((InvocationTargetException) t).getTargetException() : t;
166             DumpErrorSingleton.getSingleton().dumpException(e);
167             logger.error(e.getLocalizedMessage(), e);
168         } finally {
169             //noinspection ResultOfMethodCallIgnored
170             Thread.interrupted();
171 
172             if (eventChannel.checkError()) {
173                 DumpErrorSingleton.getSingleton().dumpText(PROCESS_PIPES_ERROR);
174                 logger.error(PROCESS_PIPES_ERROR);
175             }
176 
177             // process pipes are closed far here
178             acknowledgedExit();
179         }
180     }
181 
182     private Object createTestSet(TypeEncodedValue forkedTestSet, boolean readTestsFromCommandReader, ClassLoader cl) {
183         if (forkedTestSet != null) {
184             return forkedTestSet.getDecodedValue(cl);
185         } else if (readTestsFromCommandReader) {
186             return new LazyTestsToRun(eventChannel, commandReader);
187         }
188         return null;
189     }
190 
191     private void cancelPingScheduler() {
192         if (pingScheduler != null) {
193             try {
194                 AccessController.doPrivileged(new PrivilegedAction<Object>() {
195                     @Override
196                     public Object run() {
197                         pingScheduler.shutdown();
198                         return null;
199                     }
200                 });
201             } catch (AccessControlException e) {
202                 // ignore
203             }
204         }
205     }
206 
207     private void closeForkChannel() {
208         if (channelProcessorFactory != null) {
209             try {
210                 channelProcessorFactory.close();
211             } catch (IOException e) {
212                 e.printStackTrace();
213             }
214         }
215     }
216 
217     private PingScheduler listenToShutdownCommands(String ppid) {
218         PpidChecker ppidChecker = ppid == null ? null : new PpidChecker(ppid);
219         commandReader.addShutdownListener(createExitHandler(ppidChecker));
220         AtomicBoolean pingDone = new AtomicBoolean(true);
221         commandReader.addNoopListener(createPingHandler(pingDone));
222         PingScheduler pingMechanisms = new PingScheduler(
223                 createScheduler(PING_THREAD + PING_TIMEOUT_IN_SECONDS + "s"),
224                 createScheduler(PROCESS_CHECKER_THREAD),
225                 ppidChecker);
226 
227         ProcessCheckerType checkerType = startupConfiguration.getProcessChecker();
228 
229         if ((checkerType == ALL || checkerType == NATIVE) && pingMechanisms.processChecker != null) {
230             logger.debug(pingMechanisms.processChecker.toString());
231             if (pingMechanisms.processChecker.canUse()) {
232                 Runnable checkerJob = processCheckerJob(pingMechanisms);
233                 pingMechanisms.processCheckerScheduler.scheduleWithFixedDelay(checkerJob, 0L, 1L, SECONDS);
234             } else if (!pingMechanisms.processChecker.isStopped()) {
235                 logger.warning(
236                         "Cannot use process checker with configuration " + checkerType + ". Platform not supported.");
237             }
238         }
239 
240         if (checkerType == ALL || checkerType == PING) {
241             Runnable pingJob = createPingJob(pingDone, pingMechanisms.processChecker);
242             pingMechanisms.pingScheduler.scheduleWithFixedDelay(pingJob, 0L, PING_TIMEOUT_IN_SECONDS, SECONDS);
243         }
244 
245         return pingMechanisms;
246     }
247 
248     private Runnable processCheckerJob(final PingScheduler pingMechanism) {
249         return new Runnable() {
250             @Override
251             public void run() {
252                 try {
253                     if (pingMechanism.processChecker.canUse()
254                             && !pingMechanism.processChecker.isProcessAlive()
255                             && !pingMechanism.pingScheduler.isShutdown()) {
256                         logger.error("Surefire is going to kill self fork JVM. Maven process died.");
257                         DumpErrorSingleton.getSingleton()
258                                 .dumpText("Killing self fork JVM. Maven process died."
259                                         + NL
260                                         + "Thread dump before killing the process (" + getProcessName() + "):"
261                                         + NL
262                                         + generateThreadDump());
263 
264                         kill();
265                     }
266                 } catch (RuntimeException e) {
267                     DumpErrorSingleton.getSingleton()
268                             .dumpException(e, "System.exit() or native command error interrupted process checker.");
269                 }
270             }
271         };
272     }
273 
274     private CommandListener createPingHandler(final AtomicBoolean pingDone) {
275         return new CommandListener() {
276             @Override
277             public void update(Command command) {
278                 pingDone.set(true);
279             }
280         };
281     }
282 
283     private CommandListener createExitHandler(final PpidChecker ppidChecker) {
284         return new CommandListener() {
285             @Override
286             public void update(Command command) {
287                 Shutdown shutdown = command.toShutdownData();
288                 if (shutdown.isKill()) {
289                     if (ppidChecker != null) {
290                         ppidChecker.stop();
291                     }
292 
293                     logger.error("Surefire is going to kill self fork JVM. " + "Received SHUTDOWN {" + shutdown
294                             + "} command from Maven shutdown hook.");
295                     DumpErrorSingleton.getSingleton()
296                             .dumpText("Killing self fork JVM. Received SHUTDOWN command from Maven shutdown hook."
297                                     + NL
298                                     + "Thread dump before killing the process (" + getProcessName() + "):"
299                                     + NL
300                                     + generateThreadDump());
301 
302                     kill();
303                 } else if (shutdown.isExit()) {
304                     if (ppidChecker != null) {
305                         ppidChecker.stop();
306                     }
307                     cancelPingScheduler();
308                     logger.error("Surefire is going to exit self fork JVM. " + "Received SHUTDOWN {" + shutdown
309                             + "} command from Maven shutdown hook.");
310                     DumpErrorSingleton.getSingleton()
311                             .dumpText("Exiting self fork JVM. Received SHUTDOWN command from Maven shutdown hook."
312                                     + NL
313                                     + "Thread dump before exiting the process (" + getProcessName() + "):"
314                                     + NL
315                                     + generateThreadDump());
316                     exitBarrier.release();
317                     exit1();
318                 } else {
319                     // else refers to shutdown=testset, but not used now, keeping reader open
320                     DumpErrorSingleton.getSingleton()
321                             .dumpText(
322                                     "Thread dump for process (" + getProcessName() + "):" + NL + generateThreadDump());
323                 }
324             }
325         };
326     }
327 
328     private Runnable createPingJob(final AtomicBoolean pingDone, final PpidChecker pluginProcessChecker) {
329         return new Runnable() {
330             @Override
331             public void run() {
332                 if (!canUseNewPingMechanism(pluginProcessChecker)) {
333                     boolean hasPing = pingDone.getAndSet(false);
334                     if (!hasPing) {
335                         logger.error("Killing self fork JVM. PING timeout elapsed.");
336                         DumpErrorSingleton.getSingleton()
337                                 .dumpText("Killing self fork JVM. PING timeout elapsed."
338                                         + NL
339                                         + "Thread dump before killing the process (" + getProcessName() + "):"
340                                         + NL
341                                         + generateThreadDump());
342 
343                         kill();
344                     }
345                 }
346             }
347         };
348     }
349 
350     private void kill() {
351         kill(1);
352     }
353 
354     private void kill(int returnCode) {
355         commandReader.stop();
356         closeForkChannel();
357         Runtime.getRuntime().halt(returnCode);
358     }
359 
360     private void exit1() {
361         launchLastDitchDaemonShutdownThread(1);
362         System.exit(1);
363     }
364 
365     private void acknowledgedExit() {
366         commandReader.addByeAckListener(new CommandListener() {
367             @Override
368             public void update(Command command) {
369                 exitBarrier.release();
370             }
371         });
372         eventChannel.bye();
373         launchLastDitchDaemonShutdownThread(0);
374         boolean byeAckReceived = acquireOnePermit(exitBarrier);
375         if (!byeAckReceived && !eventChannel.checkError()) {
376             eventChannel.sendExitError(null, false);
377         }
378         cancelPingScheduler();
379         commandReader.stop();
380         closeForkChannel();
381         System.exit(0);
382     }
383 
384     private void runSuitesInProcess() throws TestSetFailedException, InvocationTargetException {
385         createProviderInCurrentClassloader().invoke(testSet);
386     }
387 
388     private ForkingReporterFactory createForkingReporterFactory() {
389         final boolean trimStackTrace =
390                 providerConfiguration.getReporterConfiguration().isTrimStackTrace();
391         return new ForkingReporterFactory(trimStackTrace, eventChannel);
392     }
393 
394     private synchronized ScheduledThreadPoolExecutor getJvmTerminator() {
395         if (jvmTerminator == null) {
396             ThreadFactory threadFactory =
397                     newDaemonThreadFactory(LAST_DITCH_SHUTDOWN_THREAD + systemExitTimeoutInSeconds + "s");
398             jvmTerminator = new ScheduledThreadPoolExecutor(1, threadFactory);
399             jvmTerminator.setMaximumPoolSize(1);
400         }
401         return jvmTerminator;
402     }
403 
404     @SuppressWarnings("checkstyle:emptyblock")
405     private void launchLastDitchDaemonShutdownThread(final int returnCode) {
406         getJvmTerminator()
407                 .schedule(
408                         new Runnable() {
409                             @Override
410                             public void run() {
411                                 if (logger != null) {
412                                     logger.error("Surefire is going to kill self fork JVM. The exit has elapsed "
413                                             + systemExitTimeoutInSeconds + " seconds after System.exit(" + returnCode
414                                             + ").");
415                                 }
416 
417                                 DumpErrorSingleton.getSingleton()
418                                         .dumpText("Thread dump for process ("
419                                                 + getProcessName()
420                                                 + ") after "
421                                                 + systemExitTimeoutInSeconds
422                                                 + " seconds shutdown timeout:"
423                                                 + NL
424                                                 + generateThreadDump());
425 
426                                 kill(returnCode);
427                             }
428                         },
429                         systemExitTimeoutInSeconds,
430                         SECONDS);
431     }
432 
433     private SurefireProvider createProviderInCurrentClassloader() {
434         BaseProviderFactory bpf = new BaseProviderFactory(true);
435         bpf.setReporterFactory(forkingReporterFactory);
436         bpf.setCommandReader(commandReader);
437         bpf.setTestRequest(providerConfiguration.getTestSuiteDefinition());
438         bpf.setReporterConfiguration(providerConfiguration.getReporterConfiguration());
439         ClassLoader classLoader = currentThread().getContextClassLoader();
440         bpf.setClassLoaders(classLoader);
441         bpf.setTestArtifactInfo(providerConfiguration.getTestArtifact());
442         bpf.setProviderProperties(providerConfiguration.getProviderProperties());
443         bpf.setRunOrderParameters(providerConfiguration.getRunOrderParameters());
444         bpf.setDirectoryScannerParameters(providerConfiguration.getDirScannerParams());
445         bpf.setMainCliOptions(providerConfiguration.getMainCliOptions());
446         bpf.setSkipAfterFailureCount(providerConfiguration.getSkipAfterFailureCount());
447         bpf.setSystemExitTimeout(providerConfiguration.getSystemExitTimeout());
448         String providerClass = startupConfiguration.getActualClassName();
449         return instantiateOneArg(classLoader, providerClass, ProviderParameters.class, bpf);
450     }
451 
452     /**
453      * Necessary for the Surefire817SystemExitIT.
454      */
455     private void flushEventChannelOnExit() {
456         Runnable target = new Runnable() {
457             @Override
458             public void run() {
459                 eventChannel.onJvmExit();
460             }
461         };
462         Thread t = new Thread(target);
463         t.setDaemon(true);
464         ShutdownHookUtils.addShutDownHook(t);
465     }
466 
467     private static MasterProcessChannelProcessorFactory lookupDecoderFactory(String channelConfig) {
468         MasterProcessChannelProcessorFactory defaultFactory = null;
469         MasterProcessChannelProcessorFactory customFactory = null;
470         for (MasterProcessChannelProcessorFactory factory : load(MasterProcessChannelProcessorFactory.class)) {
471             Class<?> cls = factory.getClass();
472 
473             boolean isSurefireFactory = cls == LegacyMasterProcessChannelProcessorFactory.class
474                     || cls == SurefireMasterProcessChannelProcessorFactory.class;
475 
476             if (isSurefireFactory) {
477                 if (factory.canUse(channelConfig)) {
478                     defaultFactory = factory;
479                 }
480             } else {
481                 customFactory = factory;
482             }
483         }
484         return customFactory != null ? customFactory : defaultFactory;
485     }
486 
487     /**
488      * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
489      * then calls the Surefire class' run method. <br> The system exit code will be 1 if an exception is thrown.
490      *
491      * @param args Commandline arguments
492      */
493     public static void main(String[] args) {
494         ForkedBooter booter = new ForkedBooter();
495         run(booter, args);
496     }
497 
498     /**
499      * created for testing purposes.
500      *
501      * @param booter booter in JVM
502      * @param args arguments passed to JVM
503      */
504     private static void run(ForkedBooter booter, String[] args) {
505         try {
506             booter.setupBooter(args[0], args[1], args[2], args.length > 3 ? args[3] : null);
507             booter.execute();
508         } catch (Throwable t) {
509             DumpErrorSingleton.getSingleton().dumpException(t);
510             if (booter.logger != null) {
511                 booter.logger.error(t.getLocalizedMessage(), t);
512             }
513             booter.cancelPingScheduler();
514             booter.exit1();
515         }
516     }
517 
518     private static boolean canUseNewPingMechanism(PpidChecker pluginProcessChecker) {
519         return pluginProcessChecker != null && pluginProcessChecker.canUse();
520     }
521 
522     private static boolean acquireOnePermit(Semaphore barrier) {
523         try {
524             return barrier.tryAcquire(Integer.MAX_VALUE, SECONDS);
525         } catch (InterruptedException e) {
526             // cancel schedulers, stop the command reader and exit 0
527             return false;
528         }
529     }
530 
531     private static ScheduledExecutorService createScheduler(String threadName) {
532         ThreadFactory threadFactory = newDaemonThreadFactory(threadName);
533         ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory);
534         executor.setMaximumPoolSize(executor.getCorePoolSize());
535         return executor;
536     }
537 
538     private static InputStream createSurefirePropertiesIfFileExists(String tmpDir, String propFileName)
539             throws FileNotFoundException {
540         File surefirePropertiesFile = new File(tmpDir, propFileName);
541         return surefirePropertiesFile.exists() ? new FileInputStream(surefirePropertiesFile) : null;
542     }
543 
544     private static boolean isDebugging() {
545         for (String argument : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
546             if ("-Xdebug".equals(argument) || argument.startsWith("-agentlib:jdwp")) {
547                 return true;
548             }
549         }
550         return false;
551     }
552 
553     private static class PingScheduler {
554         private final ScheduledExecutorService pingScheduler;
555         private final ScheduledExecutorService processCheckerScheduler;
556         private final PpidChecker processChecker;
557 
558         PingScheduler(
559                 ScheduledExecutorService pingScheduler,
560                 ScheduledExecutorService processCheckerScheduler,
561                 PpidChecker processChecker) {
562             this.pingScheduler = pingScheduler;
563             this.processCheckerScheduler = processCheckerScheduler;
564             this.processChecker = processChecker;
565         }
566 
567         void shutdown() {
568             pingScheduler.shutdown();
569             processCheckerScheduler.shutdown();
570             if (processChecker != null) {
571                 processChecker.destroyActiveCommands();
572             }
573         }
574     }
575 
576     private static String generateThreadDump() {
577         StringBuilder dump = new StringBuilder();
578         ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
579         ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100);
580         for (ThreadInfo threadInfo : threadInfos) {
581             dump.append('"');
582             dump.append(threadInfo.getThreadName());
583             dump.append("\" ");
584             Thread.State state = threadInfo.getThreadState();
585             dump.append("\n   java.lang.Thread.State: ");
586             dump.append(state);
587             StackTraceElement[] stackTraceElements = threadInfo.getStackTrace();
588             for (StackTraceElement stackTraceElement : stackTraceElements) {
589                 dump.append("\n        at ");
590                 dump.append(stackTraceElement);
591             }
592             dump.append("\n\n");
593         }
594         return dump.toString();
595     }
596 
597     private static String getProcessName() {
598         return ManagementFactory.getRuntimeMXBean().getName();
599     }
600 }