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