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