Repository: maven-surefire Updated Branches: refs/heads/SUREFIRE-1342 d468680be -> 703021371
[SUREFIRE-1342] Acknowledge normal exit of JVM and drain shared memory between processes Project: http://git-wip-us.apache.org/repos/asf/maven-surefire/repo Commit: http://git-wip-us.apache.org/repos/asf/maven-surefire/commit/70302137 Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/70302137 Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/70302137 Branch: refs/heads/SUREFIRE-1342 Commit: 70302137108d496720354b180debd224f0fcfd5d Parents: d468680 Author: Tibor17 <tibo...@lycos.com> Authored: Mon Mar 13 03:10:08 2017 +0100 Committer: Tibor17 <tibo...@lycos.com> Committed: Mon Mar 13 03:10:08 2017 +0100 ---------------------------------------------------------------------- .../surefire/booterclient/ForkStarter.java | 1 + .../lazytestprovider/AbstractCommandStream.java | 8 +- .../lazytestprovider/NotifiableTestStream.java | 2 + .../lazytestprovider/TestLessInputStream.java | 49 +++++++++++- .../TestProvidingInputStream.java | 18 +++-- .../booterclient/output/ForkClient.java | 1 + .../output/ThreadedStreamConsumer.java | 48 +++++------ .../booterclient/MockNotifiableTestStream.java | 5 ++ .../TestProvidingInputStreamTest.java | 1 + .../apache/maven/surefire/booter/Command.java | 1 + .../maven/surefire/booter/CommandReader.java | 22 ++--- .../surefire/booter/MasterProcessCommand.java | 3 +- .../apache/maven/surefire/booter/Shutdown.java | 15 ++++ .../booter/MasterProcessCommandTest.java | 7 ++ .../maven/surefire/booter/ForkedBooter.java | 84 ++++++++++++++------ 15 files changed, 187 insertions(+), 78 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/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 54d304a..b69d010 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 @@ -644,6 +644,7 @@ public class ForkStarter } finally { + currentForkClients.remove( forkClient ); closer.close(); if ( runResult == null ) { http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java index 4d6331c..3531ccf 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java @@ -41,10 +41,12 @@ public abstract class AbstractCommandStream protected abstract boolean isClosed(); /** - * Unnecessarily opposite to {@link #isClosed()} however may respect - * {@link #getLastCommand() last command} and {@link #isClosed()}. + * Opposite to {@link #isClosed()}. */ - protected abstract boolean canContinue(); + protected boolean canContinue() + { + return !isClosed(); + } /** * Possibly waiting for next command (see {@link #nextCommand()}) unless the stream is atomically http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java index 5c89173..b181de1 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java @@ -45,4 +45,6 @@ public interface NotifiableTestStream void shutdown( Shutdown shutdownType ); void noop(); + + void acknowledgeByeEventReceived(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java index b6ae42c..d0ae47a 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java @@ -33,6 +33,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import static org.apache.maven.surefire.booter.Command.BYE_ACK; import static org.apache.maven.surefire.booter.Command.NOOP; import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST; import static org.apache.maven.surefire.booter.Command.toShutdown; @@ -93,15 +94,19 @@ public final class TestLessInputStream } @Override - protected boolean isClosed() + public void acknowledgeByeEventReceived() { - return closed.get(); + if ( canContinue() ) + { + immediateCommands.add( BYE_ACK ); + barrier.release(); + } } @Override - protected boolean canContinue() + protected boolean isClosed() { - return !isClosed(); + return closed.get(); } @Override @@ -356,6 +361,24 @@ public final class TestLessInputStream lock.unlock(); } } + + @Override + public void acknowledgeByeEventReceived() + { + Lock lock = rwLock.readLock(); + lock.lock(); + try + { + for ( TestLessInputStream aliveStream : TestLessInputStreamBuilder.this.aliveStreams ) + { + aliveStream.acknowledgeByeEventReceived(); + } + } + finally + { + lock.unlock(); + } + } } /** @@ -419,6 +442,24 @@ public final class TestLessInputStream } } + @Override + public void acknowledgeByeEventReceived() + { + Lock lock = rwLock.readLock(); + lock.lock(); + try + { + if ( TestLessInputStreamBuilder.this.addTailNodeIfAbsent( BYE_ACK ) ) + { + release(); + } + } + finally + { + lock.unlock(); + } + } + private void release() { for ( TestLessInputStream aliveStream : TestLessInputStreamBuilder.this.aliveStreams ) http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/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 766843d..69f73a2 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 @@ -28,7 +28,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; -import static org.apache.maven.surefire.booter.MasterProcessCommand.TEST_SET_FINISHED; +import static org.apache.maven.surefire.booter.Command.BYE_ACK; import static org.apache.maven.surefire.booter.Command.NOOP; import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST; import static org.apache.maven.surefire.booter.Command.toRunClass; @@ -110,6 +110,16 @@ public final class TestProvidingInputStream } @Override + public void acknowledgeByeEventReceived() + { + if ( canContinue() ) + { + commands.add( BYE_ACK ); + barrier.release(); + } + } + + @Override protected Command nextCommand() { Command cmd = commands.poll(); @@ -137,12 +147,6 @@ public final class TestProvidingInputStream return closed.get(); } - @Override - protected boolean canContinue() - { - return getLastCommand() != TEST_SET_FINISHED && !isClosed(); - } - /** * Signal that a new test is to be provided. */ http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/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 d6afcb2..faace0f 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 @@ -265,6 +265,7 @@ public class ForkClient break; case BOOTERCODE_BYE: saidGoodBye = true; + notifiableTestStream.acknowledgeByeEventReceived(); break; case BOOTERCODE_STOP_ON_NEXT_TEST: stopOnNextTest(); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java index c7d39ae..67154a4 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java @@ -26,6 +26,7 @@ import java.io.Closeable; import java.io.IOException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; import static java.lang.Thread.currentThread; @@ -41,16 +42,14 @@ public final class ThreadedStreamConsumer private static final int ITEM_LIMIT_BEFORE_SLEEP = 10 * 1000; - private static final long CLOSE_TIMEOUT_MILLIS = 5 * 60 * 1000L; - private final BlockingQueue<String> items = new ArrayBlockingQueue<String>( ITEM_LIMIT_BEFORE_SLEEP ); + private final AtomicBoolean stop = new AtomicBoolean(); + private final Thread thread; private final Pumper pumper; - private volatile boolean stop; - final class Pumper implements Runnable { @@ -76,14 +75,14 @@ public final class ThreadedStreamConsumer */ public void run() { - while ( !ThreadedStreamConsumer.this.stop ) + while ( !ThreadedStreamConsumer.this.stop.get() ) { try { String item = ThreadedStreamConsumer.this.items.take(); if ( shouldStopQueueing( item ) ) { - break; + return; } target.consumeLine( item ); } @@ -114,7 +113,7 @@ public final class ThreadedStreamConsumer public void consumeLine( String s ) { - if ( stop && !thread.isAlive() ) + if ( stop.get() && !thread.isAlive() ) { items.clear(); return; @@ -134,29 +133,22 @@ public final class ThreadedStreamConsumer public void close() throws IOException { - if ( stop ) - { - return; - } - - try - { - items.put( END_ITEM ); - thread.join( CLOSE_TIMEOUT_MILLIS ); - } - catch ( InterruptedException e ) + if ( stop.compareAndSet( false, true ) ) { - currentThread().interrupt(); - throw new IOException( e ); - } - finally - { - stop = true; - } + items.clear(); + try + { + items.put( END_ITEM ); + } + catch ( InterruptedException e ) + { + currentThread().interrupt(); + } - if ( pumper.hasErrors() ) - { - pumper.throwErrors(); + if ( pumper.hasErrors() ) + { + pumper.throwErrors(); + } } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/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 index 12a5f92..9e94e7f 100644 --- 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 @@ -46,4 +46,9 @@ final class MockNotifiableTestStream public void noop() { } + + @Override + public void acknowledgeByeEventReceived() + { + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java index 6fc171e..b117a38 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java @@ -95,6 +95,7 @@ public class TestProvidingInputStreamTest assertThat( is.read(), is( 0 ) ); assertThat( is.read(), is( 0 ) ); assertThat( is.read(), is( 0 ) ); + is.close(); assertThat( is.read(), is( -1 ) ); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java index 49ae52d..5024992 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java @@ -36,6 +36,7 @@ public final class Command public static final Command TEST_SET_FINISHED = new Command( MasterProcessCommand.TEST_SET_FINISHED ); public static final Command SKIP_SINCE_NEXT_TEST = new Command( MasterProcessCommand.SKIP_SINCE_NEXT_TEST ); public static final Command NOOP = new Command( MasterProcessCommand.NOOP ); + public static final Command BYE_ACK = new Command( MasterProcessCommand.BYE_ACK ); private final MasterProcessCommand command; private final String data; http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java index 57f1c2c..fb3ad05 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java @@ -42,6 +42,7 @@ import static java.lang.Thread.State.TERMINATED; import static java.lang.StrictMath.max; import static org.apache.maven.surefire.booter.Command.toShutdown; import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_NEXT_TEST; +import static org.apache.maven.surefire.booter.MasterProcessCommand.BYE_ACK; import static org.apache.maven.surefire.booter.MasterProcessCommand.NOOP; import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS; import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN; @@ -166,6 +167,11 @@ public final class CommandReader addListener( NOOP, listener ); } + public void addByeAckListener( CommandListener listener ) + { + addListener( BYE_ACK, listener ); + } + private void addListener( MasterProcessCommand cmd, CommandListener listener ) { listeners.add( new BiProperty<MasterProcessCommand, CommandListener>( cmd, listener ) ); @@ -470,17 +476,15 @@ public final class CommandReader CommandReader.this.makeQueueFull(); CommandReader.this.wakeupIterator(); insertToListeners( toShutdown( shutdown ) ); - switch ( shutdown ) + if ( shutdown.isExit() ) { - case EXIT: - System.exit( 1 ); - case KILL: - Runtime.getRuntime().halt( 1 ); - case DEFAULT: - default: - // should not happen; otherwise you missed enum case - break; + System.exit( 1 ); + } + else if ( shutdown.isKill() ) + { + Runtime.getRuntime().halt( 1 ); } + // else is default: should not happen; otherwise you missed enum case } } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/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 d5e314a..229c8af 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 @@ -46,7 +46,8 @@ public enum MasterProcessCommand SHUTDOWN( 3, String.class ), /** To tell a forked process that the master process is still alive. Repeated after 10 seconds. */ - NOOP( 4, Void.class ); + NOOP( 4, Void.class ), + BYE_ACK( 5, Void.class ); private static final Charset ASCII = Charset.forName( "ASCII" ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java index 262cb29..77a09cf 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java @@ -44,6 +44,21 @@ public enum Shutdown return param; } + public boolean isKill() + { + return this == KILL; + } + + public boolean isExit() + { + return this == EXIT; + } + + public boolean isDefaultShutdown() + { + return this == DEFAULT; + } + public static boolean isKnown( String param ) { for ( Shutdown shutdown : values() ) http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/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 8396d8b..6e66a18 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 @@ -117,6 +117,13 @@ public class MasterProcessCommandTest decoded = command.toDataTypeAsString( encoded ); assertNull( decoded ); break; + case BYE_ACK: + assertEquals( Void.class, command.getDataType() ); + encoded = command.fromDataType( dummyData ); + assertThat( encoded.length, is( 0 ) ); + decoded = command.toDataTypeAsString( encoded ); + assertNull( decoded ); + break; default: fail(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/70302137/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 b76df2f..041cd2c 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 @@ -36,21 +36,22 @@ import java.lang.reflect.InvocationTargetException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; +import static java.lang.Math.max; import static java.lang.System.err; import static java.lang.System.out; import static java.lang.System.setErr; import static java.lang.System.setOut; import static java.lang.Thread.currentThread; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.maven.surefire.booter.CommandReader.getReader; import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_BYE; import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_ERROR; import static org.apache.maven.surefire.booter.ForkingRunListener.encode; -import static org.apache.maven.surefire.booter.Shutdown.EXIT; -import static org.apache.maven.surefire.booter.Shutdown.KILL; import static org.apache.maven.surefire.booter.SystemPropertyManager.setSystemProperties; import static org.apache.maven.surefire.util.ReflectionUtils.instantiateOneArg; import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory; @@ -70,6 +71,7 @@ public final class ForkedBooter { private static final long DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30; private static final long PING_TIMEOUT_IN_SECONDS = 20; + private static final long ONE_SECOND_IN_MILLIS = 1000; private static final ScheduledExecutorService JVM_TERMINATOR = createJvmTerminator(); private static volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS; @@ -157,7 +159,7 @@ public final class ForkedBooter encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut ); originalOut.flush(); // noinspection CallToSystemExit - exit( 0, EXIT, reader, false ); + exit( 0, reader ); } catch ( Throwable t ) { @@ -166,7 +168,7 @@ public final class ForkedBooter // noinspection UseOfSystemOutOrSystemErr t.printStackTrace( err ); // noinspection ProhibitedExceptionThrown,CallToSystemExit - exit( 1, EXIT, reader, false ); + exit( 1 ); } finally { @@ -181,11 +183,11 @@ public final class ForkedBooter private static ScheduledFuture<?> listenToShutdownCommands( CommandReader reader ) { - reader.addShutdownListener( createExitHandler( reader ) ); + reader.addShutdownListener( createExitHandler() ); AtomicBoolean pingDone = new AtomicBoolean( true ); reader.addNoopListener( createPingHandler( pingDone ) ); - return JVM_TERMINATOR.scheduleAtFixedRate( createPingJob( pingDone, reader ), - 0, PING_TIMEOUT_IN_SECONDS, SECONDS ); + Runnable pingJob = createPingJob( pingDone ); + return JVM_TERMINATOR.scheduleAtFixedRate( pingJob, 0, PING_TIMEOUT_IN_SECONDS, SECONDS ); } private static CommandListener createPingHandler( final AtomicBoolean pingDone ) @@ -199,18 +201,27 @@ public final class ForkedBooter }; } - private static CommandListener createExitHandler( final CommandReader reader ) + private static CommandListener createExitHandler() { return new CommandListener() { public void update( Command command ) { - exit( 1, command.toShutdownData(), reader, true ); + Shutdown shutdown = command.toShutdownData(); + if ( shutdown.isKill() ) + { + kill(); + } + else if ( shutdown.isExit() ) + { + exit( 1 ); + } + // else refers to shutdown=testset, but not used now, keeping reader open } }; } - private static Runnable createPingJob( final AtomicBoolean pingDone, final CommandReader reader ) + private static Runnable createPingJob( final AtomicBoolean pingDone ) { return new Runnable() { @@ -219,7 +230,7 @@ public final class ForkedBooter boolean hasPing = pingDone.getAndSet( false ); if ( !hasPing ) { - exit( 1, KILL, reader, true ); + kill(); } } }; @@ -231,23 +242,44 @@ public final class ForkedBooter out.write( encodeBytes, 0, encodeBytes.length ); } - private static void exit( int returnCode, Shutdown shutdownType, CommandReader reader, boolean stopReaderOnExit ) + private static void kill() + { + Runtime.getRuntime().halt( 1 ); + } + + private static void exit( int returnCode ) + { + launchLastDitchDaemonShutdownThread( returnCode ); + System.exit( returnCode ); + } + + private static void exit( int returnCode, final CommandReader reader ) { - switch ( shutdownType ) + final Semaphore barrier = new Semaphore( 0 ); + reader.addByeAckListener( new CommandListener() + { + @Override + public void update( Command command ) + { + barrier.release(); + } + } + ); + launchLastDitchDaemonShutdownThread( returnCode ); + final long timeoutMillis = max( systemExitTimeoutInSeconds * ONE_SECOND_IN_MILLIS, ONE_SECOND_IN_MILLIS ); + acquireOnePermit( barrier, timeoutMillis ); + System.exit( returnCode ); + } + + private static boolean acquireOnePermit( Semaphore barrier, long timeoutMillis ) + { + try { - case KILL: - Runtime.getRuntime().halt( returnCode ); - case EXIT: - if ( stopReaderOnExit ) - { - reader.stop(); - } - launchLastDitchDaemonShutdownThread( returnCode ); - System.exit( returnCode ); - case DEFAULT: - // refers to shutdown=testset, but not used now, keeping reader open - default: - break; + return barrier.tryAcquire( timeoutMillis, MILLISECONDS ); + } + catch ( InterruptedException e ) + { + return true; } }