1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
73
74
75
76
77
78
79
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
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
182 Thread.interrupted();
183
184 if (eventChannel.checkError()) {
185 DumpErrorSingleton.getSingleton().dumpText(PROCESS_PIPES_ERROR);
186 logger.error(PROCESS_PIPES_ERROR);
187 }
188
189
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
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
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
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
483
484
485
486
487 public static void main(String[] args) {
488 ForkedBooter booter = new ForkedBooter();
489 run(booter, args);
490 }
491
492
493
494
495
496
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
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 }