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