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