[SUREFIRE-1181] "forkedProcessTimeoutInSeconds" does not kill forked JVM although interrupted build
Project: http://git-wip-us.apache.org/repos/asf/maven-surefire/repo Commit: http://git-wip-us.apache.org/repos/asf/maven-surefire/commit/9b5752f9 Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/9b5752f9 Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/9b5752f9 Branch: refs/heads/master Commit: 9b5752f99be3408ff64f608ccce1373e69d1f010 Parents: 225e3cb Author: tibordigana <tibo...@lycos.com> Authored: Sun Oct 4 21:40:39 2015 +0200 Committer: tibordigana <tibo...@lycos.com> Committed: Sun Oct 4 21:40:39 2015 +0200 ---------------------------------------------------------------------- .../surefire/booterclient/ForkStarter.java | 67 ++++++++++++++----- .../booterclient/output/ForkClient.java | 69 +++++++++++++++----- .../booterclient/ForkingRunListenerTest.java | 19 ++++-- .../booterclient/MockNotifiableTestStream.java | 49 ++++++++++++++ .../surefire/booter/MasterProcessCommand.java | 10 ++- .../booter/MasterProcessCommandTest.java | 3 +- .../maven/surefire/booter/ForkedBooter.java | 32 +++++---- 7 files changed, 195 insertions(+), 54 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/9b5752f9/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 4742b7c..6f9624c 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 @@ -31,8 +31,8 @@ import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvid import org.apache.maven.plugin.surefire.booterclient.output.ForkClient; import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer; import org.apache.maven.plugin.surefire.report.DefaultReporterFactory; +import org.apache.maven.shared.utils.cli.CommandLineCallable; import org.apache.maven.shared.utils.cli.CommandLineException; -import org.apache.maven.shared.utils.cli.CommandLineTimeOutException; import org.apache.maven.surefire.booter.Classpath; import org.apache.maven.surefire.booter.ClasspathConfiguration; import org.apache.maven.surefire.booter.KeyValueSource; @@ -74,8 +74,9 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.apache.maven.shared.utils.cli.CommandLineUtils.executeCommandLine; +import static org.apache.maven.shared.utils.cli.CommandLineUtils.executeCommandLineAsCallable; import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.addShutDownHook; import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.removeShutdownHook; import static org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME; @@ -110,6 +111,8 @@ public class ForkStarter { private static final long PING_IN_SECONDS = 10; + private static final int TIMEOUT_CHECK_PERIOD_MILLIS = 100; + private static final ThreadFactory FORKED_JVM_DAEMON_THREAD_FACTORY = newDaemonThreadFactory( "surefire-fork-starter" ); @@ -118,6 +121,10 @@ public class ForkStarter private final ScheduledExecutorService pingThreadScheduler = createPingScheduler(); + private final ScheduledExecutorService timeoutCheckScheduler; + + private final Queue<ForkClient> currentForkClients; + /** * Closes an InputStream */ @@ -179,6 +186,9 @@ public class ForkStarter defaultReporterFactory = new DefaultReporterFactory( startupReportConfiguration ); defaultReporterFactory.runStarting(); defaultReporterFactories = new ConcurrentLinkedQueue<DefaultReporterFactory>(); + currentForkClients = new ConcurrentLinkedQueue<ForkClient>(); + timeoutCheckScheduler = createTimeoutCheckScheduler(); + triggerTimeoutCheck(); } public RunResult run( SurefireProperties effectiveSystemProperties, DefaultScanResult scanResult ) @@ -197,6 +207,7 @@ public class ForkStarter defaultReporterFactory.mergeFromOtherFactories( defaultReporterFactories ); defaultReporterFactory.close(); pingThreadScheduler.shutdownNow(); + timeoutCheckScheduler.shutdownNow(); } } @@ -205,11 +216,11 @@ public class ForkStarter { DefaultReporterFactory forkedReporterFactory = new DefaultReporterFactory( startupReportConfiguration ); defaultReporterFactories.add( forkedReporterFactory ); - PropertiesWrapper props = new PropertiesWrapper( providerProperties ); - ForkClient forkClient = - new ForkClient( forkedReporterFactory, startupReportConfiguration.getTestVmSystemProperties() ); TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder(); + PropertiesWrapper props = new PropertiesWrapper( providerProperties ); TestLessInputStream stream = builder.build(); + ForkClient forkClient = + new ForkClient( forkedReporterFactory, startupReportConfiguration.getTestVmSystemProperties(), stream ); Thread shutdown = createImmediateShutdownHookThread( builder, providerConfiguration.getShutdown() ); ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder ); try @@ -351,7 +362,8 @@ public class ForkStarter new DefaultReporterFactory( startupReportConfiguration ); defaultReporterFactories.add( forkedReporterFactory ); Properties vmProps = startupReportConfiguration.getTestVmSystemProperties(); - ForkClient forkClient = new ForkClient( forkedReporterFactory, vmProps ) + ForkClient forkClient = new ForkClient( forkedReporterFactory, vmProps, + builder.getImmediateCommands() ) { @Override protected void stopOnNextTest() @@ -529,21 +541,24 @@ public class ForkStarter try { - final int timeout = forkedProcessTimeoutInSeconds > 0 ? forkedProcessTimeoutInSeconds : 0; + CommandLineCallable future = + executeCommandLineAsCallable( cli, testProvidingInputStream, threadedStreamConsumer, + threadedStreamConsumer, 0, inputStreamCloser, + Charset.forName( FORK_STREAM_CHARSET_NAME ) ); - final int result = executeCommandLine( cli, testProvidingInputStream, threadedStreamConsumer, - threadedStreamConsumer, timeout, inputStreamCloser, - Charset.forName( FORK_STREAM_CHARSET_NAME ) ); + currentForkClients.add( forkClient ); - if ( result != SUCCESS ) + int result = future.call(); + + if ( forkClient.hadTimeout() ) + { + runResult = timeout( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult() ); + } + else if ( result != SUCCESS ) { throw new SurefireBooterForkException( "Error occurred in starting fork, check output in log" ); } } - catch ( CommandLineTimeOutException e ) - { - runResult = timeout( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult() ); - } catch ( CommandLineException e ) { runResult = failure( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult(), e ); @@ -551,6 +566,7 @@ public class ForkStarter } finally { + currentForkClients.remove( forkClient ); threadedStreamConsumer.close(); inputStreamCloser.run(); removeShutdownHook( inputStreamCloserHook ); @@ -650,6 +666,12 @@ public class ForkStarter return Executors.newScheduledThreadPool( 1, threadFactory ); } + private static ScheduledExecutorService createTimeoutCheckScheduler() + { + ThreadFactory threadFactory = newDaemonThreadFactory( "timeout-check-timer" ); + return Executors.newScheduledThreadPool( 1, threadFactory ); + } + private ScheduledFuture<?> triggerPingTimerForShutdown( final TestLessInputStreamBuilder builder ) { return pingThreadScheduler.scheduleAtFixedRate( new Runnable() @@ -674,4 +696,19 @@ public class ForkStarter } }, 0, PING_IN_SECONDS, SECONDS ); } + + private ScheduledFuture<?> triggerTimeoutCheck() + { + return pingThreadScheduler.scheduleAtFixedRate( new Runnable() + { + public void run() + { + long systemTime = System.currentTimeMillis(); + for ( ForkClient forkClient : currentForkClients ) + { + forkClient.tryToTimeout( systemTime, forkedProcessTimeoutInSeconds ); + } + } + }, 0, TIMEOUT_CHECK_PERIOD_MILLIS, MILLISECONDS ); + } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/9b5752f9/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 6cbc9bc..4d01b1a 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 @@ -28,6 +28,7 @@ import java.util.NoSuchElementException; import java.util.Properties; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream; import org.apache.maven.plugin.surefire.report.DefaultReporterFactory; @@ -42,6 +43,8 @@ import org.apache.maven.surefire.report.RunListener; import org.apache.maven.surefire.report.StackTraceWriter; import org.apache.maven.surefire.util.internal.StringUtils; +import static org.apache.maven.surefire.booter.Shutdown.KILL; + /** * Knows how to reconstruct *all* the state transmitted over stdout by the forked process. * @@ -50,6 +53,9 @@ import org.apache.maven.surefire.util.internal.StringUtils; public class ForkClient implements StreamConsumer { + private static final long START_TIME_ZERO = 0L; + private static final long START_TIME_NEGATIVE_TIMEOUT = -1L; + private final DefaultReporterFactory defaultReporterFactory; private final NotifiableTestStream notifiableTestStream; @@ -58,15 +64,16 @@ public class ForkClient private final Properties testVmSystemProperties; + /** + * <t>testSetStartedAt</t> is set to non-zero after received + * {@link ForkingRunListener#BOOTERCODE_TESTSET_STARTING test-set}. + */ + private final AtomicLong testSetStartedAt = new AtomicLong( START_TIME_ZERO ); + private volatile boolean saidGoodBye; private volatile StackTraceWriter errorInFork; - public ForkClient( DefaultReporterFactory defaultReporterFactory, Properties testVmSystemProperties ) - { - this( defaultReporterFactory, testVmSystemProperties, null ); - } - public ForkClient( DefaultReporterFactory defaultReporterFactory, Properties testVmSystemProperties, NotifiableTestStream notifiableTestStream ) { @@ -79,12 +86,29 @@ public class ForkClient { } - public DefaultReporterFactory getDefaultReporterFactory() + /** + * Called in concurrent Thread. + */ + public final void tryToTimeout( long currentTimeMillis, int forkedProcessTimeoutInSeconds ) + { + if ( forkedProcessTimeoutInSeconds > 0 ) + { + final long forkedProcessTimeoutInMillis = 1000 * forkedProcessTimeoutInSeconds; + final long startedAt = testSetStartedAt.get(); + if ( startedAt > START_TIME_ZERO && currentTimeMillis - startedAt >= forkedProcessTimeoutInMillis ) + { + testSetStartedAt.set( START_TIME_NEGATIVE_TIMEOUT ); + notifiableTestStream.shutdown( KILL ); + } + } + } + + public final DefaultReporterFactory getDefaultReporterFactory() { return defaultReporterFactory; } - public void consumeLine( String s ) + public final void consumeLine( String s ) { if ( StringUtils.isNotBlank( s ) ) { @@ -92,6 +116,21 @@ public class ForkClient } } + private void setCurrentStartTime() + { + if ( testSetStartedAt.get() == START_TIME_ZERO ) // JIT can optimize <= no JNI call + { + // Not necessary to call JNI library library #currentTimeMillis + // which may waste 10 - 30 machine cycles in callback. Callbacks should be fast. + testSetStartedAt.compareAndSet( START_TIME_ZERO, System.currentTimeMillis() ); + } + } + + public final boolean hadTimeout() + { + return testSetStartedAt.get() == START_TIME_NEGATIVE_TIMEOUT; + } + private void processLine( String s ) { try @@ -111,6 +150,7 @@ public class ForkClient { case ForkingRunListener.BOOTERCODE_TESTSET_STARTING: getOrCreateReporter( channelNumber ).testSetStarting( createReportEntry( remaining ) ); + setCurrentStartTime(); break; case ForkingRunListener.BOOTERCODE_TESTSET_COMPLETED: getOrCreateReporter( channelNumber ).testSetCompleted( createReportEntry( remaining ) ); @@ -155,10 +195,7 @@ public class ForkClient getOrCreateConsoleLogger( channelNumber ).info( createConsoleMessage( remaining ) ); break; case ForkingRunListener.BOOTERCODE_NEXT_TEST: - if ( notifiableTestStream != null ) - { - notifiableTestStream.provideNewTest(); - } + notifiableTestStream.provideNewTest(); break; case ForkingRunListener.BOOTERCODE_ERROR: errorInFork = deserializeStackTraceWriter( new StringTokenizer( remaining, "," ) ); @@ -213,7 +250,7 @@ public class ForkClient } } - public void consumeMultiLineContent( String s ) + public final void consumeMultiLineContent( String s ) throws IOException { BufferedReader stringReader = new BufferedReader( new StringReader( s ) ); @@ -280,7 +317,7 @@ public class ForkClient * @param channelNumber The logical channel number * @return A mock provider reporter */ - public RunListener getReporter( int channelNumber ) + public final RunListener getReporter( int channelNumber ) { return testSetReporters.get( channelNumber ); } @@ -310,17 +347,17 @@ public class ForkClient { } - public boolean isSaidGoodBye() + public final boolean isSaidGoodBye() { return saidGoodBye; } - public StackTraceWriter getErrorInFork() + public final StackTraceWriter getErrorInFork() { return errorInFork; } - public boolean isErrorInFork() + public final boolean isErrorInFork() { return errorInFork != null; } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/9b5752f9/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java index 31e511c..63d3417 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkingRunListenerTest.java @@ -42,6 +42,10 @@ import org.apache.maven.surefire.report.StackTraceWriter; import junit.framework.Assert; import junit.framework.TestCase; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + /** * @author Kristian Rosenvold */ @@ -216,11 +220,12 @@ public class ForkingRunListenerTest TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory(); final Properties testVmSystemProperties = new Properties(); - ForkClient forkStreamClient = new ForkClient( providerReporterFactory, testVmSystemProperties ); + ForkClient forkStreamClient = new ForkClient( providerReporterFactory, testVmSystemProperties, + new MockNotifiableTestStream() ); forkStreamClient.consumeMultiLineContent( content.toString( "utf-8" ) ); - assertTrue( testVmSystemProperties.size() > 1 ); + assertThat( testVmSystemProperties.size(), is( greaterThan( 1 ) ) ); } public void testMultipleEntries() @@ -240,7 +245,8 @@ public class ForkingRunListenerTest forkingReporter.testSetCompleted( reportEntry ); TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory(); - ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new Properties() ); + ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new Properties(), + new MockNotifiableTestStream() ); forkStreamClient.consumeMultiLineContent( content.toString( "utf-8" ) ); @@ -263,7 +269,8 @@ public class ForkingRunListenerTest new ForkingRunListener( printStream, anotherChannel, false ).testSkipped( secondExpected ); TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory(); - final ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new Properties() ); + final ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new Properties(), + new MockNotifiableTestStream() ); forkStreamClient.consumeMultiLineContent( content.toString( "utf-8" ) ); MockReporter reporter = (MockReporter) forkStreamClient.getReporter( defaultChannel ); @@ -337,12 +344,12 @@ public class ForkingRunListenerTest throws ReporterException, IOException { TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory(); - final ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new Properties() ); + final ForkClient forkStreamClient = new ForkClient( providerReporterFactory, new Properties(), + new MockNotifiableTestStream() ); forkStreamClient.consumeMultiLineContent( content.toString( ) ); reporter = (MockReporter) forkStreamClient.getReporter( defaultChannel ); } - public String getFirstEvent() { return reporter.getEvents().get( 0 ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/9b5752f9/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockNotifiableTestStream.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockNotifiableTestStream.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockNotifiableTestStream.java new file mode 100644 index 0000000..12a5f92 --- /dev/null +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockNotifiableTestStream.java @@ -0,0 +1,49 @@ +package org.apache.maven.plugin.surefire.booterclient; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream; +import org.apache.maven.surefire.booter.*; + +/** + * Mock of {@link NotifiableTestStream} for testing purposes. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.19 + */ +final class MockNotifiableTestStream + implements NotifiableTestStream +{ + public void provideNewTest() + { + } + + public void skipSinceNextTest() + { + } + + public void shutdown( Shutdown shutdownType ) + { + } + + public void noop() + { + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/9b5752f9/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java index 8059537..5e6bca9 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java @@ -44,7 +44,7 @@ public enum MasterProcessCommand RUN_CLASS( 0, String.class ), TEST_SET_FINISHED( 1, Void.class ), SKIP_SINCE_NEXT_TEST( 2, Void.class ), - SHUTDOWN( 3, Shutdown.class ), + SHUTDOWN( 3, String.class ), /** To tell a forked process that the master process is still alive. Repeated after 10 seconds. */ NOOP( 4, Void.class ); @@ -79,10 +79,16 @@ public enum MasterProcessCommand @SuppressWarnings( "checkstyle:magicnumber" ) public byte[] encode( String data ) { + if ( !hasDataType() ) + { + throw new IllegalArgumentException( "cannot use data without data type" ); + } + if ( getDataType() != String.class ) { - throw new IllegalArgumentException( "Data type can be only " + getDataType() ); + throw new IllegalArgumentException( "Data type can be only " + String.class ); } + byte[] dataBytes = fromDataType( data ); byte[] encoded = new byte[8 + dataBytes.length]; int command = getId(); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/9b5752f9/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java index f7f7767..8396d8b 100644 --- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java +++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java @@ -29,7 +29,6 @@ import java.io.IOException; import static org.apache.maven.surefire.booter.MasterProcessCommand.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertNotNull; /** * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> @@ -105,7 +104,7 @@ public class MasterProcessCommandTest assertNull( decoded ); break; case SHUTDOWN: - assertEquals( Shutdown.class, command.getDataType() ); + assertEquals( String.class, command.getDataType() ); encoded = command.fromDataType( Shutdown.EXIT.name() ); assertThat( encoded.length, is( 4 ) ); decoded = command.toDataTypeAsString( encoded ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/9b5752f9/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 c7376f7..050da57 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 @@ -73,7 +73,8 @@ public final class ForkedBooter */ public static void main( String... args ) { - ScheduledFuture<?> pingScheduler = listenToShutdownCommands(); + final MasterProcessReader reader = startupMasterProcessReader(); + final ScheduledFuture<?> pingScheduler = listenToShutdownCommands( reader ); final PrintStream originalOut = System.out; try { @@ -138,7 +139,7 @@ public final class ForkedBooter encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut ); originalOut.flush(); // noinspection CallToSystemExit - exit( 0, DEFAULT ); + exit( 0, DEFAULT, reader ); } catch ( Throwable t ) { @@ -146,7 +147,7 @@ public final class ForkedBooter // noinspection UseOfSystemOutOrSystemErr t.printStackTrace( System.err ); // noinspection ProhibitedExceptionThrown,CallToSystemExit - exit( 1, DEFAULT ); + exit( 1, DEFAULT, reader ); } finally { @@ -154,13 +155,18 @@ public final class ForkedBooter } } - private static ScheduledFuture<?> listenToShutdownCommands() + private static MasterProcessReader startupMasterProcessReader() { - MasterProcessReader reader = MasterProcessReader.getReader(); - reader.addShutdownListener( createExitHandler() ); + return MasterProcessReader.getReader(); + } + + private static ScheduledFuture<?> listenToShutdownCommands( MasterProcessReader reader ) + { + reader.addShutdownListener( createExitHandler( reader ) ); AtomicBoolean pingDone = new AtomicBoolean( true ); reader.addNoopListener( createPingHandler( pingDone ) ); - return JVM_TERMINATOR.scheduleAtFixedRate( createPingJob( pingDone ), 0, PING_TIMEOUT_IN_SECONDS, SECONDS ); + return JVM_TERMINATOR.scheduleAtFixedRate( createPingJob( pingDone, reader ), + 0, PING_TIMEOUT_IN_SECONDS, SECONDS ); } private static MasterProcessListener createPingHandler( final AtomicBoolean pingDone ) @@ -174,18 +180,18 @@ public final class ForkedBooter }; } - private static MasterProcessListener createExitHandler() + private static MasterProcessListener createExitHandler( final MasterProcessReader reader ) { return new MasterProcessListener() { public void update( Command command ) { - exit( 1, command.toShutdownData() ); + exit( 1, command.toShutdownData(), reader ); } }; } - private static Runnable createPingJob( final AtomicBoolean pingDone ) + private static Runnable createPingJob( final AtomicBoolean pingDone, final MasterProcessReader reader ) { return new Runnable() { @@ -194,7 +200,7 @@ public final class ForkedBooter boolean hasPing = pingDone.getAndSet( false ); if ( !hasPing ) { - exit( 1, KILL ); + exit( 1, KILL, reader ); } } }; @@ -206,9 +212,9 @@ public final class ForkedBooter out.write( encodeBytes, 0, encodeBytes.length ); } - private static void exit( int returnCode, Shutdown shutdownType ) + private static void exit( int returnCode, Shutdown shutdownType, MasterProcessReader reader ) { - MasterProcessReader.getReader().stop(); + reader.stop(); switch ( shutdownType ) { case KILL: