You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by kr...@apache.org on 2013/01/09 18:36:11 UTC

git commit: [SUREFIRE-946] prevent hanging main process if forked process was killed (softly)

Updated Branches:
  refs/heads/master 350dd6fd7 -> e54dbd810


[SUREFIRE-946] prevent hanging main process if forked process was killed (softly)

Fixed with extended IT


Project: http://git-wip-us.apache.org/repos/asf/maven-surefire/repo
Commit: http://git-wip-us.apache.org/repos/asf/maven-surefire/commit/e54dbd81
Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/e54dbd81
Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/e54dbd81

Branch: refs/heads/master
Commit: e54dbd810f62fe723800f11279b881bff244b707
Parents: 350dd6f
Author: agudian <an...@gmail.com>
Authored: Sat Jan 5 21:13:12 2013 +0100
Committer: Kristian Rosenvold <kr...@apache.org>
Committed: Wed Jan 9 18:24:38 2013 +0100

----------------------------------------------------------------------
 .../plugin/surefire/booterclient/ForkStarter.java  |  140 +++++++++++----
 .../lazytestprovider/TestProvidingInputStream.java |   29 ++-
 .../surefire/booterclient/output/ForkClient.java   |   22 ++-
 .../maven/surefire/booter/ForkingRunListener.java  |    3 +
 .../apache/maven/surefire/booter/ForkedBooter.java |   74 ++++++--
 .../maven/surefire/its/CrashDetectionIT.java       |    7 +-
 6 files changed, 215 insertions(+), 60 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/e54dbd81/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
index 620dfd4..892c6a9 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
@@ -21,6 +21,8 @@ package org.apache.maven.plugin.surefire.booterclient;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessControlException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -36,6 +38,7 @@ import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+
 import org.apache.maven.plugin.surefire.AbstractSurefireMojo;
 import org.apache.maven.plugin.surefire.CommonReflector;
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
@@ -63,14 +66,13 @@ import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.util.DefaultScanResult;
 
-
 /**
  * Starts the fork or runs in-process.
  * <p/>
  * Lives only on the plugin-side (not present in remote vms)
  * <p/>
  * Knows how to fork new vms and also how to delegate non-forking invocation to SurefireStarter directly
- *
+ * 
  * @author Jason van Zyl
  * @author Emmanuel Venisse
  * @author Brett Porter
@@ -80,6 +82,36 @@ import org.apache.maven.surefire.util.DefaultScanResult;
  */
 public class ForkStarter
 {
+    /**
+     * Closes an InputStream
+     */
+    private final class InputStreamCloser
+        extends Thread
+    {
+        private InputStream testProvidingInputStream;
+
+        public InputStreamCloser( InputStream testProvidingInputStream )
+        {
+            this.testProvidingInputStream = testProvidingInputStream;
+        }
+
+        @Override
+        public void run()
+        {
+            if ( testProvidingInputStream != null )
+            {
+                try
+                {
+                    testProvidingInputStream.close();
+                }
+                catch ( IOException e )
+                {
+                    // ignore
+                }
+            }
+        }
+    }
+
     private final int forkedProcessTimeoutInSeconds;
 
     private final ProviderConfiguration providerConfiguration;
@@ -105,7 +137,6 @@ public class ForkStarter
         }
     };
 
-
     public ForkStarter( ProviderConfiguration providerConfiguration, StartupConfiguration startupConfiguration,
                         ForkConfiguration forkConfiguration, int forkedProcessTimeoutInSeconds,
                         StartupReportConfiguration startupReportConfiguration )
@@ -167,8 +198,9 @@ public class ForkStarter
     {
 
         ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount );
-        ExecutorService executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, TimeUnit.SECONDS,
-                                                                  new ArrayBlockingQueue<Runnable>( forkCount ) );
+        ExecutorService executorService =
+            new ThreadPoolExecutor( forkCount, forkCount, 60, TimeUnit.SECONDS,
+                                    new ArrayBlockingQueue<Runnable>( forkCount ) );
 
         try
         {
@@ -196,16 +228,15 @@ public class ForkStarter
                     public RunResult call()
                         throws Exception
                     {
-                        TestProvidingInputStream testProvidingInputStream =
-                            new TestProvidingInputStream( messageQueue );
+                        TestProvidingInputStream testProvidingInputStream = new TestProvidingInputStream( messageQueue );
 
                         ForkClient forkClient =
-                            new ForkClient( defaultReporterFactory, startupReportConfiguration.getTestVmSystemProperties(),
+                            new ForkClient( defaultReporterFactory,
+                                            startupReportConfiguration.getTestVmSystemProperties(),
                                             testProvidingInputStream );
 
                         return fork( null, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
-                                     forkClient, effectiveSystemProperties, finalThreadNumber,
-                                     testProvidingInputStream );
+                                     forkClient, effectiveSystemProperties, finalThreadNumber, testProvidingInputStream );
                     }
                 };
 
@@ -270,12 +301,13 @@ public class ForkStarter
                         if ( thisThreadsThreadNumber > forkCount )
                         {
                             // this would be a bug in the ThreadPoolExecutor
-                            throw new IllegalStateException(
-                                "More threads than " + forkCount + " have been created by the ThreadPoolExecutor." );
+                            throw new IllegalStateException( "More threads than " + forkCount
+                                + " have been created by the ThreadPoolExecutor." );
                         }
 
-                        ForkClient forkClient = new ForkClient( defaultReporterFactory,
-                                                                startupReportConfiguration.getTestVmSystemProperties() );
+                        ForkClient forkClient =
+                            new ForkClient( defaultReporterFactory,
+                                            startupReportConfiguration.getTestVmSystemProperties() );
                         return fork( testSet, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
                                      forkClient, effectiveSystemProperties, thisThreadsThreadNumber, null );
                     }
@@ -353,8 +385,9 @@ public class ForkStarter
                     AbstractSurefireMojo.createCopyAndReplaceThreadNumPlaceholder( effectiveSystemProperties,
                                                                                    threadNumber );
                 systPropsFile =
-                    SystemPropertyManager.writePropertiesFile( filteredProperties, forkConfiguration.getTempDirectory(),
-                                                               "surefire_" + systemPropertiesFileCounter++,
+                    SystemPropertyManager.writePropertiesFile( filteredProperties,
+                                                               forkConfiguration.getTempDirectory(), "surefire_"
+                                                                   + systemPropertiesFileCounter++,
                                                                forkConfiguration.isDebug() );
             }
         }
@@ -365,22 +398,31 @@ public class ForkStarter
 
         final Classpath bootClasspathConfiguration = forkConfiguration.getBootClasspath();
 
-        final Classpath additionlClassPathUrls = startupConfiguration.useSystemClassLoader()
-            ? startupConfiguration.getClasspathConfiguration().getTestClasspath()
-            : null;
+        final Classpath additionlClassPathUrls =
+            startupConfiguration.useSystemClassLoader() ? startupConfiguration.getClasspathConfiguration().getTestClasspath()
+                            : null;
 
         // Surefire-booter + all test classes if "useSystemClassloader"
         // Surefire-booter if !useSystemClassLoader
         Classpath bootClasspath = Classpath.join( bootClasspathConfiguration, additionlClassPathUrls );
 
-        @SuppressWarnings( "unchecked" ) OutputStreamFlushableCommandline cli =
+        @SuppressWarnings( "unchecked" )
+        OutputStreamFlushableCommandline cli =
             forkConfiguration.createCommandLine( bootClasspath.getClassPath(),
                                                  startupConfiguration.getClassLoaderConfiguration(),
                                                  startupConfiguration.isShadefire(), threadNumber );
 
+        final InputStreamCloser inputStreamCloserHook;
         if ( testProvidingInputStream != null )
         {
             testProvidingInputStream.setFlushReceiverProvider( cli );
+
+            inputStreamCloserHook = new InputStreamCloser( testProvidingInputStream );
+            addShutDownHook( inputStreamCloserHook );
+        }
+        else
+        {
+            inputStreamCloserHook = null;
         }
 
         cli.createArg().setFile( surefireProperties );
@@ -410,7 +452,6 @@ public class ForkStarter
                 throw new SurefireBooterForkException( "Error occurred in starting fork, check output in log" );
             }
 
-
         }
         catch ( CommandLineTimeOutException e )
         {
@@ -424,6 +465,11 @@ public class ForkStarter
         finally
         {
             threadedStreamConsumer.close();
+            if ( inputStreamCloserHook != null )
+            {
+                removeShutdownHook( inputStreamCloserHook );
+                inputStreamCloserHook.run();
+            }
             if ( runResult == null )
             {
                 runResult = defaultReporterFactory.getGlobalRunStatistics().getRunResult();
@@ -433,16 +479,16 @@ public class ForkStarter
                 StackTraceWriter errorInFork = forkClient.getErrorInFork();
                 if ( errorInFork != null )
                 {
-                    //noinspection ThrowFromFinallyBlock
-                    throw new RuntimeException(
-                        "There was an error in the forked process\n" + errorInFork.writeTraceToString() );
+                    // noinspection ThrowFromFinallyBlock
+                    throw new RuntimeException( "There was an error in the forked process\n"
+                        + errorInFork.writeTraceToString() );
                 }
                 if ( !forkClient.isSaidGoodBye() )
                 {
-                    //noinspection ThrowFromFinallyBlock
+                    // noinspection ThrowFromFinallyBlock
                     throw new RuntimeException(
-                        "The forked VM terminated without saying properly goodbye. VM crash or System.exit called ?" +
-                            "\nCommand was" + cli.toString() );
+                                                "The forked VM terminated without saying properly goodbye. VM crash or System.exit called ?"
+                                                    + "\nCommand was" + cli.toString() );
                 }
 
             }
@@ -460,15 +506,14 @@ public class ForkStarter
         {
             final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
             ClassLoader testsClassLoader = classpathConfiguration.createTestClassLoader( false );
-            ClassLoader surefireClassLoader =
-                classpathConfiguration.createInprocSurefireClassLoader( testsClassLoader );
+            ClassLoader surefireClassLoader = classpathConfiguration.createInprocSurefireClassLoader( testsClassLoader );
 
             CommonReflector commonReflector = new CommonReflector( surefireClassLoader );
             Object reporterFactory = commonReflector.createReportingReporterFactory( startupReportConfiguration );
 
             final ProviderFactory providerFactory =
-                new ProviderFactory( startupConfiguration, providerConfiguration, surefireClassLoader, testsClassLoader,
-                                     reporterFactory );
+                new ProviderFactory( startupConfiguration, providerConfiguration, surefireClassLoader,
+                                     testsClassLoader, reporterFactory );
             SurefireProvider surefireProvider = providerFactory.createProvider( false );
             return surefireProvider.getSuites();
         }
@@ -478,4 +523,37 @@ public class ForkStarter
         }
     }
 
+    // TODO use ShutdownHookUtils, once it's public again
+    public static void addShutDownHook( Thread hook )
+    {
+        try
+        {
+            Runtime.getRuntime().addShutdownHook( hook );
+        }
+        catch ( IllegalStateException ignore )
+        {
+            // ignore
+        }
+        catch ( AccessControlException ignore )
+        {
+            // ignore
+        }
+    }
+
+    // TODO use ShutdownHookUtils, once it's public again
+    public static void removeShutdownHook( Thread hook )
+    {
+        try
+        {
+            Runtime.getRuntime().removeShutdownHook( hook );
+        }
+        catch ( IllegalStateException ignore )
+        {
+            // ignore
+        }
+        catch ( AccessControlException ignore )
+        {
+            // ignore
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/e54dbd81/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
index 1e63234..df14d35 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
@@ -25,16 +25,14 @@ import java.util.Queue;
 import java.util.concurrent.Semaphore;
 
 /**
- * An {@link InputStream} that, when read, provides test class names out of
- * a queue.
+ * An {@link InputStream} that, when read, provides test class names out of a queue.
  * <p/>
- * The Stream provides only one test at a time, but only after {@link #provideNewTest()}
- * has been invoked.
+ * The Stream provides only one test at a time, but only after {@link #provideNewTest()} has been invoked.
  * <p/>
- * After providing each test class name, followed by a newline character, a flush is
- * performed on the {@link FlushReceiver} provided by the {@link FlushReceiverProvider}
- * that can be set using {@link #setFlushReceiverProvider(FlushReceiverProvider)}.
- *
+ * After providing each test class name, followed by a newline character, a flush is performed on the
+ * {@link FlushReceiver} provided by the {@link FlushReceiverProvider} that can be set using
+ * {@link #setFlushReceiverProvider(FlushReceiverProvider)}.
+ * 
  * @author Andreas Gudian
  */
 public class TestProvidingInputStream
@@ -50,9 +48,11 @@ public class TestProvidingInputStream
 
     private FlushReceiverProvider flushReceiverProvider;
 
+    private boolean closed = false;
+
     /**
      * C'tor
-     *
+     * 
      * @param testItemQueue source of the tests to be read from this stream
      */
     public TestProvidingInputStream( Queue<String> testItemQueue )
@@ -81,6 +81,11 @@ public class TestProvidingInputStream
 
             semaphore.acquireUninterruptibly();
 
+            if ( closed )
+            {
+                return -1;
+            }
+
             String currentElement = testItemQueue.poll();
             if ( null != currentElement )
             {
@@ -111,4 +116,10 @@ public class TestProvidingInputStream
     {
         semaphore.release();
     }
+
+    public void close()
+    {
+        closed = true;
+        semaphore.release();
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/e54dbd81/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
index 9fde697..932f148 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java
@@ -44,7 +44,7 @@ import org.apache.maven.surefire.util.internal.StringUtils;
 
 /**
  * Knows how to reconstruct *all* the state transmitted over stdout by the forked process.
- *
+ * 
  * @author Kristian Rosenvold
  */
 public class ForkClient
@@ -156,7 +156,11 @@ public class ForkClient
                 case ForkingRunListener.BOOTERCODE_ERROR:
                     errorInFork = deserializeStackStraceWriter( new StringTokenizer( remaining, "," ) );
                     break;
+                case ForkingRunListener.BOOTERCODE_CRASH:
+                    closeTestProvidingInputStream();
+                    break;
                 case ForkingRunListener.BOOTERCODE_BYE:
+                    closeTestProvidingInputStream();
                     saidGoodBye = true;
                     break;
                 default:
@@ -173,6 +177,14 @@ public class ForkClient
         }
     }
 
+    private void closeTestProvidingInputStream()
+    {
+        if ( null != testProvidingInputStream )
+        {
+            testProvidingInputStream.close();
+        }
+    }
+
     public void consumeMultiLineContent( String s )
         throws IOException
     {
@@ -217,9 +229,9 @@ public class ForkClient
         String stackTraceMessage = nullableCsv( tokens.nextToken() );
         String smartStackTrace = nullableCsv( tokens.nextToken() );
         String stackTrace = tokens.hasMoreTokens() ? nullableCsv( tokens.nextToken() ) : null;
-        stackTraceWriter = stackTrace != null
-            ? new DeserializedStacktraceWriter( stackTraceMessage, smartStackTrace, stackTrace )
-            : null;
+        stackTraceWriter =
+            stackTrace != null ? new DeserializedStacktraceWriter( stackTraceMessage, smartStackTrace, stackTrace )
+                            : null;
         return stackTraceWriter;
     }
 
@@ -242,7 +254,7 @@ public class ForkClient
 
     /**
      * Used when getting reporters on the plugin side of a fork.
-     *
+     * 
      * @param channelNumber The logical channel number
      * @return A mock provider reporter
      */

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/e54dbd81/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
index 9974c5c..6ec7223 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java
@@ -69,6 +69,8 @@ public class ForkingRunListener
 
     public static final byte BOOTERCODE_TEST_SKIPPED = (byte) '9';
 
+    public static final byte BOOTERCODE_CRASH = (byte) 'C';
+
     public static final byte BOOTERCODE_TEST_ASSUMPTIONFAILURE = (byte) 'G';
 
     public static final byte BOOTERCODE_CONSOLE = (byte) 'H';
@@ -81,6 +83,7 @@ public class ForkingRunListener
 
     public static final byte BOOTERCODE_BYE = (byte) 'Z';
 
+
     private final PrintStream target;
 
     private final Integer testSetChannelId;

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/e54dbd81/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index c06297a..4b04a99 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -21,6 +21,7 @@ package org.apache.maven.surefire.booter;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
 import java.lang.reflect.InvocationTargetException;
@@ -35,7 +36,7 @@ import org.apache.maven.surefire.util.LazyTestsToRun;
  * <p/>
  * Deals with deserialization of the booter wire-level protocol
  * <p/>
- *
+ * 
  * @author Jason van Zyl
  * @author Emmanuel Venisse
  * @author Kristian Rosenvold
@@ -43,10 +44,58 @@ import org.apache.maven.surefire.util.LazyTestsToRun;
 public class ForkedBooter
 {
 
+    private final static long SYSTEM_EXIT_TIMEOUT = 30 * 1000;
+
+    private final static String GOODBYE_MESSAGE = ( (char) ForkingRunListener.BOOTERCODE_BYE ) + ",0,BYE!";
+
+    private final static String CRASH_MESSAGE = ( (char) ForkingRunListener.BOOTERCODE_CRASH ) + ",0,F!";
+
+    private static boolean sayGoodbye = false;
+
+    private static final class GoodbyeReporterAndStdStreamCloser
+        implements Runnable
+    {
+
+        private InputStream originalIn;
+
+        private PrintStream originalOut;
+
+        public GoodbyeReporterAndStdStreamCloser()
+        {
+            this.originalIn = System.in;
+            this.originalOut = System.out;
+        }
+
+        public void run()
+        {
+            try
+            {
+                originalIn.close();
+            }
+            catch ( IOException e )
+            {
+            }
+
+            if ( sayGoodbye )
+            {
+                originalOut.println( GOODBYE_MESSAGE );
+            }
+            else
+            {
+                originalOut.println( CRASH_MESSAGE );
+            }
+
+            originalOut.flush();
+            originalOut.close();
+        }
+    }
+
     /**
      * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
-     * then calls the Surefire class' run method. <p/> The system exit code will be 1 if an exception is thrown.
-     *
+     * then calls the Surefire class' run method.
+     * <p/>
+     * The system exit code will be 1 if an exception is thrown.
+     * 
      * @param args Commandline arguments
      * @throws Throwable Upon throwables
      */
@@ -54,6 +103,9 @@ public class ForkedBooter
         throws Throwable
     {
         final PrintStream originalOut = System.out;
+
+        Runtime.getRuntime().addShutdownHook( new Thread( new GoodbyeReporterAndStdStreamCloser() ) );
+
         try
         {
             if ( args.length > 1 )
@@ -71,8 +123,8 @@ public class ForkedBooter
             boolean readTestsFromInputStream = providerConfiguration.isReadTestsFromInStream();
 
             final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
-            final ClassLoader testClassLoader = classpathConfiguration.createForkingTestClassLoader(
-                startupConfiguration.isManifestOnlyJarRequestedAndUsable() );
+            final ClassLoader testClassLoader =
+                classpathConfiguration.createForkingTestClassLoader( startupConfiguration.isManifestOnlyJarRequestedAndUsable() );
 
             startupConfiguration.writeSurefireTestClasspathProperty();
 
@@ -92,8 +144,7 @@ public class ForkedBooter
 
             try
             {
-                runSuitesInProcess( testSet, testClassLoader, startupConfiguration, providerConfiguration,
-                                    originalOut );
+                runSuitesInProcess( testSet, testClassLoader, startupConfiguration, providerConfiguration, originalOut );
             }
             catch ( InvocationTargetException t )
             {
@@ -111,10 +162,8 @@ public class ForkedBooter
                 ForkingRunListener.encode( stringBuffer, stackTraceWriter, false );
                 originalOut.println( ( (char) ForkingRunListener.BOOTERCODE_ERROR ) + ",0," + stringBuffer.toString() );
             }
-            // Say bye.
-            originalOut.println( ( (char) ForkingRunListener.BOOTERCODE_BYE ) + ",0,BYE!" );
-            originalOut.flush();
-            // noinspection CallToSystemExit
+
+            sayGoodbye = true;
             exit( 0 );
         }
         catch ( Throwable t )
@@ -127,15 +176,12 @@ public class ForkedBooter
         }
     }
 
-    private final static long SYSTEM_EXIT_TIMEOUT = 30 * 1000;
-
     private static void exit( final int returnCode )
     {
         launchLastDitchDaemonShutdownThread( returnCode );
         System.exit( returnCode );
     }
 
-
     private static RunResult runSuitesInProcess( Object testSet, ClassLoader testsClassLoader,
                                                  StartupConfiguration startupConfiguration,
                                                  ProviderConfiguration providerConfiguration,

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/e54dbd81/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CrashDetectionIT.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CrashDetectionIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CrashDetectionIT.java
index 7dc868f..31e3e01 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CrashDetectionIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/CrashDetectionIT.java
@@ -27,8 +27,13 @@ import org.apache.maven.surefire.its.fixture.SurefireIntegrationTestCase;
 public class CrashDetectionIT
     extends SurefireIntegrationTestCase
 {
-    public void testArgLine()
+    public void testCrashInFork()
     {
         unpack( "crash-detection" ).maven().withFailure().executeTest();
     }
+
+    public void testCrashInReusableFork()
+    {
+        unpack( "crash-detection" ).forkOncePerThread().threadCount( 1 ).maven().withFailure().executeTest();
+    }
 }