You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ti...@apache.org on 2020/04/03 09:21:04 UTC

[maven-surefire] branch maven2surefire-jvm-communication updated: fixed performance problem in TCP/Pipes communication (we do NOT flush every time, used buffered channels, used Async Sockets instead of blocking NIO Sockets)

This is an automated email from the ASF dual-hosted git repository.

tibordigana pushed a commit to branch maven2surefire-jvm-communication
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git


The following commit(s) were added to refs/heads/maven2surefire-jvm-communication by this push:
     new 979034c  fixed performance problem in TCP/Pipes communication (we do NOT flush every time, used buffered channels, used Async Sockets instead of blocking NIO Sockets)
979034c is described below

commit 979034c18feaab4e71e786de0fed7e19ca360ced
Author: tibordigana <ti...@apache.org>
AuthorDate: Fri Apr 3 11:20:50 2020 +0200

    fixed performance problem in TCP/Pipes communication (we do NOT flush every time, used buffered channels, used Async Sockets instead of blocking NIO Sockets)
---
 .../surefire/booterclient/output/ForkClient.java   |  13 +-
 .../surefire/extensions/EventConsumerThread.java   |   4 +-
 .../surefire/extensions/SurefireForkChannel.java   |  60 ++++--
 .../booterclient/ForkingRunListenerTest.java       |  12 +-
 .../TestLessInputStreamBuilderTest.java            |   2 +-
 .../TestProvidingInputStreamTest.java              |   2 +-
 .../booterclient/output/ForkClientTest.java        |   2 +-
 .../surefire/extensions/AsyncSocketTest.java       | 228 +++++++++++++++++++++
 .../maven/plugin/surefire/extensions/E2ETest.java  | 105 +++++++---
 .../extensions/ForkedProcessEventNotifierTest.java |  76 ++++---
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |   4 +
 .../maven/surefire/extensions/ForkChannelTest.java |  33 ++-
 .../maven/surefire/booter/BaseProviderFactory.java |   7 -
 .../surefire/booter/ForkingReporterFactory.java    |   1 -
 .../maven/surefire/booter/ForkingRunListener.java  |   1 -
 .../MasterProcessChannelDecoder.java               |   4 +-
 .../MasterProcessChannelEncoder.java               |   4 +-
 .../surefire/providerapi/ProviderParameters.java   |   2 -
 .../AbstractNoninterruptibleWritableChannel.java   |  28 +--
 .../maven/surefire/util/internal/Channels.java     | 188 ++++++++++++++---
 .../util/internal/WritableBufferedByteChannel.java |  36 ++++
 .../surefire/booter/ForkingRunListenerTest.java    |   1 -
 .../surefire/util/internal/ChannelsReaderTest.java |  58 +++++-
 .../surefire/util/internal/ChannelsWriterTest.java |  43 +++-
 .../maven/surefire/booter/CommandReader.java       |   2 -
 .../apache/maven/surefire/booter/ForkedBooter.java |  25 ++-
 .../maven/surefire/booter/LazyTestsToRun.java      |   1 -
 .../spi/LegacyMasterProcessChannelDecoder.java     |   2 +-
 .../spi/LegacyMasterProcessChannelEncoder.java     |  98 +++++----
 ...LegacyMasterProcessChannelProcessorFactory.java |  11 +-
 ...refireMasterProcessChannelProcessorFactory.java |  52 ++++-
 .../maven/surefire/booter/CommandReaderTest.java   |  10 +-
 .../surefire/booter/ForkedBooterMockTest.java      |   2 -
 .../maven/surefire/booter/ForkedBooterTest.java    |   3 +-
 .../spi/LegacyMasterProcessChannelEncoderTest.java |  77 ++++---
 .../extensions/util/CommandlineStreams.java        |   9 +-
 .../spi/MasterProcessChannelProcessorFactory.java  |   4 +-
 .../apache/maven/surefire/its/ConsoleOutputIT.java |  19 +-
 .../src/test/java/consoleOutput/Test1.java         |  20 +-
 .../src/test/java/consoleoutput_noisy/Test3.java   |  52 +++++
 40 files changed, 995 insertions(+), 306 deletions(-)

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 b4de014..bc76dfe 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
@@ -24,7 +24,7 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.surefire.eventapi.Event;
 import org.apache.maven.surefire.extensions.EventHandler;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunListener;
@@ -111,6 +111,7 @@ public class ForkClient
         notifier.setStopOnNextTestListener( new StopOnNextTestListener() );
         notifier.setConsoleDebugListener( new DebugListener() );
         notifier.setConsoleWarningListener( new WarningListener() );
+        notifier.setExitErrorEventListener( new ExitErrorEventListener() );
     }
 
     private final class TestSetStartingListener
@@ -303,6 +304,16 @@ public class ForkClient
         }
     }
 
+    private final class ExitErrorEventListener implements ForkedProcessExitErrorListener
+    {
+        @Override
+        public void handle( StackTraceWriter stackTrace )
+        {
+            getOrCreateConsoleLogger()
+                .error( "System Exit has timed out in the forked process " + forkNumber );
+        }
+    }
+
     /**
      * Overridden by a subclass, see {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter}.
      */
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
index b92a238..8b8e2f3 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/EventConsumerThread.java
@@ -19,7 +19,6 @@ package org.apache.maven.plugin.surefire.extensions;
  * under the License.
  */
 
-import org.apache.commons.codec.binary.Base64;
 import org.apache.maven.plugin.surefire.booterclient.output.DeserializedStacktraceWriter;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.booter.ForkedProcessEventType;
@@ -51,6 +50,7 @@ import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.report.TestSetReportEntry;
+import org.apache.maven.surefire.shared.codec.binary.Base64;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
@@ -203,7 +203,7 @@ public class EventConsumerThread extends CloseableDaemonThread
 
     private boolean read( ByteBuffer buffer ) throws IOException
     {
-        if ( buffer.hasRemaining() )
+        if ( buffer.hasRemaining() && buffer.position() > 0 )
         {
             return true;
         }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
index 3285486..93607fb 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java
@@ -28,21 +28,30 @@ import org.apache.maven.surefire.extensions.ForkChannel;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
 
 import javax.annotation.Nonnull;
+import java.io.Closeable;
 import java.io.IOException;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketOption;
-import java.nio.channels.Channel;
+import java.nio.channels.AsynchronousServerSocketChannel;
+import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
 import java.nio.channels.WritableByteChannel;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import static java.net.StandardSocketOptions.SO_KEEPALIVE;
 import static java.net.StandardSocketOptions.SO_REUSEADDR;
 import static java.net.StandardSocketOptions.TCP_NODELAY;
-import static java.nio.channels.ServerSocketChannel.open;
+import static java.nio.channels.AsynchronousChannelGroup.withThreadPool;
+import static java.nio.channels.AsynchronousServerSocketChannel.open;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newInputStream;
+import static org.apache.maven.surefire.util.internal.Channels.newOutputStream;
+import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
 
 /**
  * The TCP/IP server accepting only one client connection. The forked JVM connects to the server using the
@@ -60,34 +69,52 @@ import static java.nio.channels.ServerSocketChannel.open;
  */
 final class SurefireForkChannel extends ForkChannel
 {
+    private static final ExecutorService THREAD_POOL = Executors.newCachedThreadPool( newDaemonThreadFactory() );
+
     private final ConsoleLogger logger;
-    private final ServerSocketChannel server;
+    private final AsynchronousServerSocketChannel server;
+    private final String localHost;
     private final int localPort;
-    private volatile SocketChannel channel;
+    private volatile AsynchronousSocketChannel worker;
 
     SurefireForkChannel( int forkChannelId, @Nonnull ConsoleLogger logger ) throws IOException
     {
         super( forkChannelId );
         this.logger = logger;
-        server = open();
+        server = open( withThreadPool( THREAD_POOL ) );
         setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
         InetAddress ip = Inet4Address.getLoopbackAddress();
         server.bind( new InetSocketAddress( ip, 0 ), 1 );
-        localPort = ( (InetSocketAddress) server.getLocalAddress() ).getPort();
+        InetSocketAddress localAddress = (InetSocketAddress) server.getLocalAddress();
+        localHost = localAddress.getHostString();
+        localPort = localAddress.getPort();
     }
 
     @Override
     public void connectToClient() throws IOException
     {
-        if ( channel != null )
+        if ( worker != null )
         {
             throw new IllegalStateException( "already accepted TCP client connection" );
         }
-        channel = server.accept();
+
+        try
+        {
+            worker = server.accept().get();
+        }
+        catch ( InterruptedException e )
+        {
+            throw new IOException( e.getLocalizedMessage(), e );
+        }
+        catch ( ExecutionException e )
+        {
+            throw new IOException( e.getLocalizedMessage(), e.getCause() );
+        }
     }
 
     @SafeVarargs
-    private final void setTrueOptions( SocketOption<Boolean>... options ) throws IOException
+    private final void setTrueOptions( SocketOption<Boolean>... options )
+        throws IOException
     {
         for ( SocketOption<Boolean> option : options )
         {
@@ -101,7 +128,7 @@ final class SurefireForkChannel extends ForkChannel
     @Override
     public String getForkNodeConnectionString()
     {
-        return "tcp://127.0.0.1:" + localPort;
+        return "tcp://" + localHost + ":" + localPort;
     }
 
     @Override
@@ -114,6 +141,10 @@ final class SurefireForkChannel extends ForkChannel
     public CloseableDaemonThread bindCommandReader( @Nonnull CommandReader commands,
                                                     WritableByteChannel stdIn )
     {
+        // dont use newBufferedChannel here - may cause the command is not sent and the JVM hangs
+        // only newChannel flushes the message
+        // newBufferedChannel does not flush
+        WritableByteChannel channel = newChannel( newOutputStream( worker ) );
         return new StreamFeeder( "commands-fork-" + getForkChannelId(), channel, commands, logger );
     }
 
@@ -122,6 +153,7 @@ final class SurefireForkChannel extends ForkChannel
                                                    @Nonnull CountdownCloseable countdownCloseable,
                                                    ReadableByteChannel stdOut )
     {
+        ReadableByteChannel channel = newBufferedChannel( newInputStream( worker ) );
         return new EventConsumerThread( "fork-" + getForkChannelId() + "-event-thread-", channel,
             eventHandler, countdownCloseable, logger );
     }
@@ -129,8 +161,8 @@ final class SurefireForkChannel extends ForkChannel
     @Override
     public void close() throws IOException
     {
-        //noinspection EmptyTryBlock
-        try ( Channel c1 = channel; Channel c2 = server )
+        //noinspection unused,EmptyTryBlock,EmptyTryBlock
+        try ( Closeable c1 = worker; Closeable c2 = server )
         {
             // only close all channels
         }
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 4ca19ad..8aeba39 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
@@ -38,6 +38,7 @@ import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.report.SimpleReportEntry;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.report.TestSetReportEntry;
+import org.apache.maven.surefire.util.internal.WritableBufferedByteChannel;
 
 import javax.annotation.Nonnull;
 import java.io.ByteArrayInputStream;
@@ -53,7 +54,8 @@ import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import static java.nio.channels.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newChannel;
 import static org.mockito.Mockito.mock;
 
 /**
@@ -243,10 +245,11 @@ public class ForkingRunListenerTest
         ReportEntry expected = createDefaultReportEntry();
         SimpleReportEntry secondExpected = createAnotherDefaultReportEntry();
 
-        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( printStream ) ), false )
+        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newBufferedChannel( printStream ) ), false )
                 .testStarting( expected );
 
-        new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( anotherPrintStream ) ), false )
+        new ForkingRunListener(
+            new LegacyMasterProcessChannelEncoder( newBufferedChannel( anotherPrintStream ) ), false )
                 .testSkipped( secondExpected );
 
         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
@@ -362,7 +365,8 @@ public class ForkingRunListenerTest
 
     private RunListener createForkingRunListener()
     {
-        return new ForkingRunListener( new LegacyMasterProcessChannelEncoder( newChannel( printStream ) ), false );
+        WritableBufferedByteChannel channel = (WritableBufferedByteChannel) newChannel( printStream );
+        return new ForkingRunListener( new LegacyMasterProcessChannelEncoder( channel ), false );
     }
 
     private class StandardTestRun
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
index 2aab4c2..7c89492 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java
@@ -22,7 +22,7 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
 import org.apache.maven.surefire.booter.Command;
 import org.apache.maven.surefire.booter.MasterProcessCommand;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
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 1d348ce..3e2023e 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
@@ -22,7 +22,7 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
 import org.apache.maven.surefire.booter.Command;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
 import org.apache.maven.plugin.surefire.extensions.StreamFeeder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
 import org.junit.Test;
 
 import java.io.IOException;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
index 60f6872..453e735 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClientTest.java
@@ -66,7 +66,7 @@ import java.util.concurrent.TimeUnit;
 
 import static java.nio.channels.Channels.newChannel;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.commons.codec.binary.Base64.encodeBase64String;
+import static org.apache.maven.surefire.shared.codec.binary.Base64.encodeBase64String;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_DEBUG;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_ERR;
 import static org.apache.maven.plugin.surefire.booterclient.MockReporter.CONSOLE_INFO;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java
new file mode 100644
index 0000000..88cba07
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/AsyncSocketTest.java
@@ -0,0 +1,228 @@
+package org.apache.maven.plugin.surefire.extensions;
+
+/*
+ * 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.surefire.util.internal.DaemonThreadFactory;
+import org.junit.Test;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketOption;
+import java.nio.channels.AsynchronousChannelGroup;
+import java.nio.channels.AsynchronousServerSocketChannel;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static java.net.StandardSocketOptions.SO_KEEPALIVE;
+import static java.net.StandardSocketOptions.SO_REUSEADDR;
+import static java.net.StandardSocketOptions.TCP_NODELAY;
+import static org.apache.maven.surefire.util.internal.Channels.newInputStream;
+import static org.apache.maven.surefire.util.internal.Channels.newOutputStream;
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ *
+ */
+public class AsyncSocketTest
+{
+    private static final String LONG_STRING =
+        "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
+
+    private final CountDownLatch barrier = new CountDownLatch( 1 );
+    private final AtomicLong writeTime = new AtomicLong();
+    private final AtomicLong readTime = new AtomicLong();
+
+    private volatile InetSocketAddress address;
+
+    @Test
+    public void test() throws Exception
+    {
+        int forks = 2;
+        ThreadFactory factory = DaemonThreadFactory.newDaemonThreadFactory();
+        ExecutorService executorService = Executors.newCachedThreadPool( factory );
+        if ( executorService instanceof ThreadPoolExecutor )
+        {
+            ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
+            threadPoolExecutor.setCorePoolSize( Math.min( forks, Runtime.getRuntime().availableProcessors() ) );
+            threadPoolExecutor.prestartCoreThread();
+        }
+        AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool( executorService );
+        AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open( group );
+        setTrueOptions( server, SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
+        server.bind( null, 1 );
+        address = (InetSocketAddress) server.getLocalAddress();
+
+        System.gc();
+        TimeUnit.SECONDS.sleep( 3L );
+
+        Thread tc = new Thread()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    client();
+                }
+                catch ( Exception e )
+                {
+                    e.printStackTrace();
+                }
+            }
+        };
+        tc.setDaemon( true );
+        tc.start();
+
+        Future<AsynchronousSocketChannel> acceptFuture = server.accept();
+        AsynchronousSocketChannel worker = acceptFuture.get();
+        if ( !worker.isOpen() )
+        {
+            throw new IOException( "client socket closed" );
+        }
+        final InputStream is = newInputStream( worker );
+        final OutputStream os = new BufferedOutputStream( newOutputStream( worker ), 64 * 1024 );
+
+        Thread tt = new Thread()
+        {
+            public void run()
+            {
+                try
+                {
+                    byte[] b = new byte[1024];
+                    is.read( b );
+                }
+                catch ( Exception e )
+                {
+                    //e.printStackTrace();
+                }
+
+            }
+        };
+        tt.setName( "fork-1-event-thread-" );
+        tt.setDaemon( true );
+        tt.start();
+
+        Thread t = new Thread()
+        {
+            @SuppressWarnings( "checkstyle:magicnumber" )
+            public void run()
+            {
+                try
+                {
+                    byte[] data = LONG_STRING.getBytes( StandardCharsets.US_ASCII );
+                    long t1 = System.currentTimeMillis();
+                    int i = 0;
+                    for ( ; i < 320_000; i++ )
+                    {
+                        os.write( data );
+                        long t2 = System.currentTimeMillis();
+                        long spent = t2 - t1;
+
+                        if ( i % 100_000 == 0 )
+                        {
+                            System.out.println( spent + "ms: " + i );
+                        }
+                    }
+                    os.flush();
+                    long spent = System.currentTimeMillis() - t1;
+                    writeTime.set( spent );
+                    System.out.println( spent + "ms: " + i );
+                }
+                catch ( IOException e )
+                {
+                    e.printStackTrace();
+                }
+
+            }
+        };
+        t.setName( "commands-fork-1" );
+        t.setDaemon( true );
+        t.start();
+
+        barrier.await();
+        tt.join();
+        t.join();
+        tc.join();
+        worker.close();
+        server.close();
+
+        // 160 millis on write using the asynchronous sockets
+        // 320 millis on NIO blocking sockets
+        assertThat( writeTime.get() )
+            .isLessThan( 1000L );
+
+        // 160 millis on read using the asynchronous sockets
+        // 320 millis on NIO blocking sockets
+        assertThat( readTime.get() )
+            .isLessThan( 1000L );
+    }
+
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    private void client() throws Exception
+    {
+        InetSocketAddress hostAddress = new InetSocketAddress( InetAddress.getLoopbackAddress(), address.getPort() );
+        AsynchronousSocketChannel clientSocketChannel = AsynchronousSocketChannel.open();
+        clientSocketChannel.connect( hostAddress ).get(); // Wait until connection is done.
+        InputStream is = new BufferedInputStream( newInputStream( clientSocketChannel ), 64 * 1024 );
+        List<byte[]> bytes = new ArrayList<>();
+        long t1 = System.currentTimeMillis();
+        for ( int i = 0; i < 320_000; i++ )
+        {
+            byte[] b = new byte[100];
+            is.read( b );
+            bytes.add( b );
+        }
+        long t2 = System.currentTimeMillis();
+        long spent = t2 - t1;
+        readTime.set( spent );
+        System.out.println( new String( bytes.get( bytes.size() - 1 ) ) );
+        System.out.println( "received within " + spent + "ms" );
+        clientSocketChannel.close();
+        barrier.countDown();
+    }
+
+    @SafeVarargs
+    private static void setTrueOptions( AsynchronousServerSocketChannel server, SocketOption<Boolean>... options )
+        throws IOException
+    {
+        for ( SocketOption<Boolean> option : options )
+        {
+            if ( server.supportedOptions().contains( option ) )
+            {
+                server.setOption( option, true );
+            }
+        }
+    }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
index ec76a8c..c22da94 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java
@@ -1,23 +1,47 @@
 package org.apache.maven.plugin.surefire.extensions;
 
+/*
+ * 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.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.eventapi.Event;
 import org.apache.maven.surefire.extensions.EventHandler;
 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
-import org.apache.maven.surefire.report.ConsoleOutputCapture;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.junit.Test;
 
 import javax.annotation.Nonnull;
 import java.io.Closeable;
-import java.io.IOException;
-import java.io.PrintStream;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 
+import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 
+/**
+ * Simulates the End To End use case where Maven process and Surefire process communicate using the TCP/IP protocol.
+ */
+@SuppressWarnings( "checkstyle:magicnumber" )
 public class E2ETest
 {
     private static final String LONG_STRING =
@@ -27,14 +51,20 @@ public class E2ETest
     public void test() throws Exception
     {
         ConsoleLogger logger = mock( ConsoleLogger.class );
-        SurefireForkChannel server = new SurefireForkChannel(1, logger );
+        final SurefireForkChannel server = new SurefireForkChannel( 1, logger );
 
         final String connection = server.getForkNodeConnectionString();
 
-        SurefireMasterProcessChannelProcessorFactory factory = new SurefireMasterProcessChannelProcessorFactory();
+        final SurefireMasterProcessChannelProcessorFactory factory = new SurefireMasterProcessChannelProcessorFactory();
         factory.connect( connection );
         final MasterProcessChannelEncoder encoder = factory.createEncoder();
 
+        System.gc();
+
+        TimeUnit.SECONDS.sleep( 3L );
+
+        final CountDownLatch awaitHandlerFinished = new CountDownLatch( 2 );
+
         Thread t = new Thread()
         {
             @Override
@@ -49,20 +79,25 @@ public class E2ETest
                     }
                 };
 
-                PrintStream out = System.out;
-                PrintStream err = System.err;
+                //PrintStream out = System.out;
+                //PrintStream err = System.err;
 
-                ConsoleOutputCapture.startCapture( target );
+                //ConsoleOutputCapture.startCapture( target );
 
                 try
                 {
-                    for ( int i = 0; i < 320_000; i++ )
+                    long t1 = System.currentTimeMillis();
+                    for ( int i = 0; i < 400_000; i++ )
                     {
-                        System.out.println( LONG_STRING );
+                        //System.out.println( LONG_STRING );
+                        encoder.stdOut( LONG_STRING, true );
                     }
-                    System.setOut( out );
-                    System.setErr( err );
-                    TimeUnit.MINUTES.sleep( 1L );
+                    long t2 = System.currentTimeMillis();
+                    long spent = t2 - t1;
+                    //System.setOut( out );
+                    //System.setErr( err );
+                    System.out.println( spent + "ms on write" );
+                    awaitHandlerFinished.countDown();
                 }
                 catch ( Exception e )
                 {
@@ -75,31 +110,39 @@ public class E2ETest
 
         server.connectToClient();
 
+        final AtomicLong readTime = new AtomicLong();
+
         EventHandler<Event> h = new EventHandler<Event>()
         {
-            volatile int i;
-            volatile long t1;
+            private final AtomicInteger counter = new AtomicInteger();
+            private volatile long t1;
 
             @Override
             public void handleEvent( @Nonnull Event event )
             {
                 try
                 {
-                    if ( i++ == 0 )
+                    if ( counter.getAndIncrement() == 0 )
                     {
                         t1 = System.currentTimeMillis();
                     }
 
-                    if ( i == 320_000 )
+                    long t2 = System.currentTimeMillis();
+                    long spent = t2 - t1;
+
+                    if ( counter.get() % 100_000 == 0 )
+                    {
+                        System.out.println( spent + "ms: " + counter.get() );
+                    }
+
+                    if ( counter.get() == 320_000 )
                     {
-                        long t2 = System.currentTimeMillis();
-                        TimeUnit.SECONDS.sleep( 1L );
-                        System.out.println( "Forked JVM spent "
-                            + ( t2 - t1 )
-                            + "ms on transferring all lines of the log." );
+                        readTime.set( spent );
+                        System.out.println( spent + "ms on read" );
+                        awaitHandlerFinished.countDown();
                     }
                 }
-                catch ( InterruptedException e )
+                catch ( Exception e )
                 {
                     e.printStackTrace();
                 }
@@ -109,18 +152,24 @@ public class E2ETest
         Closeable c = new Closeable()
         {
             @Override
-            public void close() throws IOException
+            public void close()
             {
-
             }
         };
 
         server.bindEventHandler( h, new CountdownCloseable( c, 1 ), null )
-        .start();
+            .start();
 
-        TimeUnit.SECONDS.sleep( 60L );
+        assertThat( awaitHandlerFinished.await( 30L, TimeUnit.SECONDS ) )
+            .isTrue();
 
         factory.close();
         server.close();
+
+        // 2 seconds while using the encoder/decoder
+        // 160 millis of sending pure data without encoder/decoder
+        assertThat( readTime.get() )
+            .isPositive()
+            .isLessThanOrEqualTo( 3_000L );
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
index cb2e382..369abe5 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/ForkedProcessEventNotifierTest.java
@@ -39,6 +39,7 @@ import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.util.internal.ObjectUtils;
+import org.apache.maven.surefire.util.internal.WritableBufferedByteChannel;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.runners.Enclosed;
@@ -68,10 +69,11 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import static java.nio.channels.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newChannel;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.copyOfRange;
-import static org.apache.commons.codec.binary.Base64.encodeBase64String;
+import static org.apache.maven.surefire.shared.codec.binary.Base64.encodeBase64String;
 import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -114,9 +116,11 @@ public class ForkedProcessEventNotifierTest
         public void shouldHaveSystemProperty() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             Map<String, String> props = ObjectUtils.systemProps();
             encoder.sendSystemProperties( props );
+            wChannel.close();
 
             ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
             PropertyEventAssertionListener listener = new PropertyEventAssertionListener();
@@ -307,7 +311,8 @@ public class ForkedProcessEventNotifierTest
         public void shouldSendByeEvent() throws Exception
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.bye();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -343,7 +348,8 @@ public class ForkedProcessEventNotifierTest
         public void shouldSendStopOnNextTestEvent() throws Exception
         {
             Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.stopOnNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -401,7 +407,8 @@ public class ForkedProcessEventNotifierTest
             when( reportEntry.getStackTraceWriter() ).thenReturn( stackTraceWriter );
 
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.testFailed( reportEntry, true );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -427,7 +434,8 @@ public class ForkedProcessEventNotifierTest
         public void shouldSendNextTestEvent() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.acquireNextTest();
             String read = new String( out.toByteArray(), UTF_8 );
 
@@ -457,7 +465,8 @@ public class ForkedProcessEventNotifierTest
         public void testConsole() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleInfoLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -483,7 +492,8 @@ public class ForkedProcessEventNotifierTest
         public void testError() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleErrorLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -509,7 +519,8 @@ public class ForkedProcessEventNotifierTest
         public void testErrorWithException() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             Throwable throwable = new Throwable( "msg" );
             encoder.consoleErrorLog( throwable );
 
@@ -538,7 +549,8 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             StackTraceWriter stackTraceWriter = new DeserializedStacktraceWriter( "1", "2", "3" );
             encoder.consoleErrorLog( stackTraceWriter, false );
 
@@ -566,7 +578,8 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleDebugLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -596,7 +609,8 @@ public class ForkedProcessEventNotifierTest
         {
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
             encoder.consoleWarningLog( "msg" );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
@@ -622,8 +636,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdOutStream() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdOut( "msg", false );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -649,8 +665,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdOutStreamPrint() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdOut( "", false );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -676,8 +694,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdOutStreamPrintWithNull() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdOut( null, false );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -703,8 +723,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdOutStreamPrintln() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdOut( "", true );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -730,8 +752,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdOutStreamPrintlnWithNull() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdOut( null, true );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -757,8 +781,10 @@ public class ForkedProcessEventNotifierTest
         public void testStdErrStream() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.stdErr( "msg", false );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -784,8 +810,10 @@ public class ForkedProcessEventNotifierTest
         public void shouldCountSameNumberOfSystemProperties() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            WritableBufferedByteChannel wChannel = newBufferedChannel( out );
+            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( wChannel );
             encoder.sendSystemProperties( ObjectUtils.systemProps() );
+            wChannel.close();
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -839,14 +867,15 @@ public class ForkedProcessEventNotifierTest
         public void shouldHandleExit() throws Exception
         {
             final Stream out = Stream.newStream();
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
             StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
             when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
             when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
             when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
             when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
-            encoder.sendExitEvent( stackTraceWriter, false );
+            encoder.sendExitError( stackTraceWriter, false );
 
             ReadableByteChannel channel = newChannel( new ByteArrayInputStream( out.toByteArray() ) );
 
@@ -950,7 +979,8 @@ public class ForkedProcessEventNotifierTest
 
             final Stream out = Stream.newStream();
 
-            LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+            LegacyMasterProcessChannelEncoder encoder =
+                new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
             LegacyMasterProcessChannelEncoder.class.getMethod( operation[0], ReportEntry.class, boolean.class )
                     .invoke( encoder, reportEntry, trim );
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
index a232544..94754a7 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
@@ -40,7 +40,9 @@ import org.apache.maven.plugin.surefire.booterclient.ModularClasspathForkConfigu
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStreamBuilderTest;
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStreamTest;
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClientTest;
+import org.apache.maven.plugin.surefire.extensions.AsyncSocketTest;
 import org.apache.maven.plugin.surefire.extensions.ConsoleOutputReporterTest;
+import org.apache.maven.plugin.surefire.extensions.E2ETest;
 import org.apache.maven.plugin.surefire.extensions.ForkedProcessEventNotifierTest;
 import org.apache.maven.plugin.surefire.extensions.StatelessReporterTest;
 import org.apache.maven.plugin.surefire.extensions.StreamFeederTest;
@@ -108,6 +110,8 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( ForkStarterTest.class ) );
         suite.addTest( new JUnit4TestAdapter( ForkChannelTest.class ) );
         suite.addTest( new JUnit4TestAdapter( StreamFeederTest.class ) );
+        suite.addTest( new JUnit4TestAdapter( E2ETest.class ) );
+        suite.addTest( new JUnit4TestAdapter( AsyncSocketTest.class ) );
         return suite;
     }
 }
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
index 68f2324..c3f6a8c 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java
@@ -35,10 +35,11 @@ import java.net.Socket;
 import java.net.URI;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.nio.charset.StandardCharsets.US_ASCII;
-import static java.util.concurrent.TimeUnit.SECONDS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.fest.assertions.Assertions.assertThat;
 
 /**
@@ -71,43 +72,35 @@ public class ForkChannelTest
             assertThat( uri.getPort() )
                 .isPositive();
 
-            Consumer consumer = new Consumer();
-
-            Client client = new Client( uri.getPort() );
-            client.start();
-
-            channel.connectToClient();
-            SECONDS.sleep( 3L );
-
-            TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
+            final TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
             TestLessInputStream commandReader = builder.build();
-
-            channel.bindCommandReader( commandReader, null ).start();
-
-            final AtomicBoolean isCloseableCalled = new AtomicBoolean();
+            final CountDownLatch isCloseableCalled = new CountDownLatch( 1 );
             Closeable closeable = new Closeable()
             {
                 @Override
                 public void close()
                 {
-                    isCloseableCalled.set( true );
+                    isCloseableCalled.countDown();
                 }
             };
             CountdownCloseable cc = new CountdownCloseable( closeable, 1 );
-            channel.bindEventHandler( consumer, cc, null ).start();
+            Consumer consumer = new Consumer();
 
-            SECONDS.sleep( 3L );
+            Client client = new Client( uri.getPort() );
+            client.start();
 
-            commandReader.noop();
+            channel.connectToClient();
+            channel.bindCommandReader( commandReader, null ).start();
+            channel.bindEventHandler( consumer, cc, null ).start();
 
-            SECONDS.sleep( 3L );
+            commandReader.noop();
 
             client.join( TESTCASE_TIMEOUT );
 
             assertThat( hasError.get() )
                 .isFalse();
 
-            assertThat( isCloseableCalled.get() )
+            assertThat( isCloseableCalled.await( TESTCASE_TIMEOUT, MILLISECONDS ) )
                 .isTrue();
 
             assertThat( consumer.lines )
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
index 6ac0ce2..9ae9fbd 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
@@ -21,7 +21,6 @@ package org.apache.maven.surefire.booter;
 
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.providerapi.CommandChainReader;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.DefaultDirectConsoleReporter;
@@ -263,12 +262,6 @@ public class BaseProviderFactory
         this.systemExitTimeout = systemExitTimeout;
     }
 
-    @Override
-    public MasterProcessChannelEncoder getForkedChannelEncoder()
-    {
-        return masterProcessChannelEncoder;
-    }
-
     public void setForkedChannelEncoder( MasterProcessChannelEncoder masterProcessChannelEncoder )
     {
         this.masterProcessChannelEncoder = masterProcessChannelEncoder;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
index 1c6db50..c08b52e 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java
@@ -19,7 +19,6 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.suite.RunResult;
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 6148149..2603e8f 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
@@ -20,7 +20,6 @@ package org.apache.maven.surefire.booter;
  */
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
 import org.apache.maven.surefire.report.ConsoleStream;
 import org.apache.maven.surefire.report.ReportEntry;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelDecoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelDecoder.java
similarity index 94%
rename from surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelDecoder.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelDecoder.java
index 4dc4908..dd06590 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelDecoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelDecoder.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.providerapi;
+package org.apache.maven.surefire.booter;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -19,8 +19,6 @@ package org.apache.maven.surefire.providerapi;
  * under the License.
  */
 
-import org.apache.maven.surefire.booter.Command;
-
 import javax.annotation.Nonnull;
 import java.io.IOException;
 
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelEncoder.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
similarity index 95%
rename from surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelEncoder.java
rename to surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
index b734b61..062a57c 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/MasterProcessChannelEncoder.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessChannelEncoder.java
@@ -1,4 +1,4 @@
-package org.apache.maven.surefire.providerapi;
+package org.apache.maven.surefire.booter;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -80,5 +80,5 @@ public interface MasterProcessChannelEncoder
 
     void acquireNextTest();
 
-    void sendExitEvent( StackTraceWriter stackTraceWriter, boolean trimStackTraces );
+    void sendExitError( StackTraceWriter stackTraceWriter, boolean trimStackTraces );
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
index 47a6a7e..f620cbf 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java
@@ -147,7 +147,5 @@ public interface ProviderParameters
 
     Integer getSystemExitTimeout();
 
-    MasterProcessChannelEncoder getForkedChannelEncoder();
-
     CommandChainReader getCommandReader();
 }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java
index cb08e34..4c60bce 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/AbstractNoninterruptibleWritableChannel.java
@@ -19,12 +19,10 @@ package org.apache.maven.surefire.util.internal;
  * under the License.
  */
 
-import java.io.Flushable;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.NonWritableChannelException;
-import java.nio.channels.WritableByteChannel;
 
 /**
  * The channel used for writes which cannot be implicitly closed after the operational Thread
@@ -32,21 +30,27 @@ import java.nio.channels.WritableByteChannel;
  *
  * @since 3.0.0-M5
  */
-abstract class AbstractNoninterruptibleWritableChannel implements WritableByteChannel, Flushable
+abstract class AbstractNoninterruptibleWritableChannel implements WritableBufferedByteChannel
 {
-    private final boolean flushable;
     private volatile boolean open = true;
 
-    AbstractNoninterruptibleWritableChannel( boolean flushable )
-    {
-        this.flushable = flushable;
-    }
-
     protected abstract void writeImpl( ByteBuffer src ) throws IOException;
     protected abstract void closeImpl() throws IOException;
+    protected abstract void flushImpl() throws IOException;
+
+    @Override
+    public final int write( ByteBuffer src ) throws IOException
+    {
+        return write( src, true );
+    }
 
     @Override
-    public final synchronized int write( ByteBuffer src ) throws IOException
+    public final void writeBuffered( ByteBuffer src ) throws IOException
+    {
+        write( src, false );
+    }
+
+    int write( ByteBuffer src, boolean flush ) throws IOException
     {
         if ( !isOpen() )
         {
@@ -70,9 +74,9 @@ abstract class AbstractNoninterruptibleWritableChannel implements WritableByteCh
             countWrittenBytes = src.remaining();
             writeImpl( src );
             src.position( src.limit() );
-            if ( flushable )
+            if ( flush )
             {
-                flush();
+                flushImpl();
             }
         }
         return countWrittenBytes;
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
index 65cb9b4..a536fc6 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/Channels.java
@@ -19,61 +19,195 @@ package org.apache.maven.surefire.util.internal;
  * under the License.
  */
 
+import javax.annotation.Nonnegative;
 import javax.annotation.Nonnull;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
+import java.nio.channels.AsynchronousCloseException;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
+import java.util.concurrent.ExecutionException;
 
 import static java.util.Objects.requireNonNull;
 
 /**
- * Converts {@link OutputStream}, {@link java.io.PrintStream}, {@link InputStream}
- * to the Java {@link java.nio.channels.Channel}.
+ * Converts {@link OutputStream}, {@link java.io.PrintStream}, {@link InputStream} to the Java {@link
+ * java.nio.channels.Channel}.
  * <br>
- * We do not use the Java's utility class {@link java.nio.channels.Channels} because the utility
- * closes the stream as soon as the particular Thread is interrupted.
- * If the frameworks (Zookeeper, Netty) interrupts the thread, the communication channels become
- * closed and the JVM hangs. Therefore we developed internal utility which is safe for the Surefire.
+ * We do not use the Java's utility class {@link java.nio.channels.Channels} because the utility closes the stream as
+ * soon as the particular Thread is interrupted. If the frameworks (Zookeeper, Netty) interrupts the thread, the
+ * communication channels become closed and the JVM hangs. Therefore we developed internal utility which is safe for the
+ * Surefire.
  *
  * @since 3.0.0-M5
  */
 public final class Channels
 {
+    private static final int BUFFER_SIZE = 64 * 1024;
+
     private Channels()
     {
         throw new IllegalStateException( "no instantiable constructor" );
     }
 
-    public static WritableByteChannel newChannel( @Nonnull  OutputStream out )
+    public static WritableByteChannel newChannel( @Nonnull OutputStream out )
     {
-        return newChannel( out, false );
+        return newChannel( out, 0 );
     }
 
-    public static WritableByteChannel newFlushableChannel( @Nonnull OutputStream out )
+    public static WritableBufferedByteChannel newBufferedChannel( @Nonnull OutputStream out )
     {
-        return newChannel( out, true );
+        return newChannel( out, BUFFER_SIZE );
     }
 
     public static ReadableByteChannel newChannel( @Nonnull final InputStream is )
     {
-        requireNonNull( is, "the stream should not be null" );
+        return newChannel( is, 0 );
+    }
+
+    public static ReadableByteChannel newBufferedChannel( @Nonnull final InputStream is )
+    {
+        return newChannel( is, BUFFER_SIZE );
+    }
 
-        if ( is instanceof FileInputStream && FileInputStream.class.equals( is.getClass() ) )
+    public static OutputStream newOutputStream( final AsynchronousByteChannel channel )
+    {
+        return new OutputStream()
         {
-            return ( (FileInputStream) is ).getChannel();
-        }
+            @Override
+            public synchronized void write( byte[] b, int off, int len ) throws IOException
+            {
+                if ( off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0 )
+                {
+                    throw new IndexOutOfBoundsException(
+                        "b.length = " + b.length + ", off = " + off + ", len = " + len );
+                }
+                else if ( len > 0 )
+                {
+                    ByteBuffer bb = ByteBuffer.wrap( b, off, len );
+                    while ( bb.hasRemaining() )
+                    {
+                        try
+                        {
+                            channel.write( bb ).get();
+                        }
+                        catch ( ExecutionException e )
+                        {
+                            Throwable t = e.getCause();
+                            throw new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
+                        }
+                        catch ( Exception e )
+                        {
+                            throw new IOException( e.getLocalizedMessage(), e );
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void write( int b ) throws IOException
+            {
+                write( new byte[] {(byte) b} );
+            }
+
+            @Override
+            public synchronized void close() throws IOException
+            {
+                if ( channel.isOpen() )
+                {
+                    try
+                    {
+                        channel.close();
+                    }
+                    catch ( AsynchronousCloseException e )
+                    {
+                        // closed channel anyway
+                    }
+                }
+            }
+        };
+    }
+
+    public static InputStream newInputStream( final AsynchronousByteChannel channel )
+    {
+        return new InputStream()
+        {
+            @Override
+            public synchronized int read( byte[] b, int off, int len ) throws IOException
+            {
+                if ( off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0 )
+                {
+                    throw new IndexOutOfBoundsException(
+                        "b.length = " + b.length + ", off = " + off + ", len = " + len );
+                }
+                else if ( len == 0 )
+                {
+                    return 0;
+                }
+                ByteBuffer bb = ByteBuffer.wrap( b, off, len );
+                try
+                {
+                    return channel.read( bb ).get();
+                }
+                catch ( ExecutionException e )
+                {
+                    Throwable t = e.getCause();
+                    throw new IOException( ( t == null ? e : t ).getLocalizedMessage(), t );
+                }
+                catch ( Exception e )
+                {
+                    throw new IOException( e.getLocalizedMessage(), e );
+                }
+            }
+
+            @Override
+            public int read() throws IOException
+            {
+                int count;
+                byte[] b = new byte[1];
+                do
+                {
+                    count = read( b, 0, 1 );
+                }
+                while ( count == 0 );
+
+                return count == -1 ? -1 : b[0];
+            }
+
+            @Override
+            public synchronized void close() throws IOException
+            {
+                if ( channel.isOpen() )
+                {
+                    try
+                    {
+                        channel.close();
+                    }
+                    catch ( AsynchronousCloseException e )
+                    {
+                        // closed channel anyway
+                    }
+                }
+            }
+        };
+    }
+
+    private static ReadableByteChannel newChannel( @Nonnull InputStream is, @Nonnegative int bufferSize )
+    {
+        requireNonNull( is, "the stream should not be null" );
+        final InputStream bis = bufferSize == 0 ? is : new BufferedInputStream( is, bufferSize );
 
         return new AbstractNoninterruptibleReadableChannel()
         {
             @Override
             protected int readImpl( ByteBuffer src ) throws IOException
             {
-                int count = is.read( src.array(), src.arrayOffset() + src.position(), src.remaining() );
+                int count = bis.read( src.array(), src.arrayOffset() + src.position(), src.remaining() );
                 if ( count > 0 )
                 {
                     src.position( count + src.position() );
@@ -84,38 +218,34 @@ public final class Channels
             @Override
             protected void closeImpl() throws IOException
             {
-                is.close();
+                bis.close();
             }
         };
     }
 
-    private static WritableByteChannel newChannel( @Nonnull final OutputStream out, final boolean flushable )
+    private static WritableBufferedByteChannel newChannel( @Nonnull OutputStream out, @Nonnegative int bufferSize )
     {
         requireNonNull( out, "the stream should not be null" );
+        final OutputStream bos = bufferSize == 0 ? out : new BufferedOutputStream( out, bufferSize );
 
-        if ( out instanceof FileOutputStream && FileOutputStream.class.equals( out.getClass() ) )
-        {
-            return ( (FileOutputStream) out ).getChannel();
-        }
-
-        return new AbstractNoninterruptibleWritableChannel( flushable )
+        return new AbstractNoninterruptibleWritableChannel()
         {
             @Override
             protected void writeImpl( ByteBuffer src ) throws IOException
             {
-                out.write( src.array(), src.arrayOffset() + src.position(), src.remaining() );
+                bos.write( src.array(), src.arrayOffset() + src.position(), src.remaining() );
             }
 
             @Override
             protected void closeImpl() throws IOException
             {
-                out.close();
+                bos.close();
             }
 
             @Override
-            public void flush() throws IOException
+            protected void flushImpl() throws IOException
             {
-                out.flush();
+                bos.flush();
             }
         };
     }
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/WritableBufferedByteChannel.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/WritableBufferedByteChannel.java
new file mode 100644
index 0000000..7f64549
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/WritableBufferedByteChannel.java
@@ -0,0 +1,36 @@
+package org.apache.maven.surefire.util.internal;
+
+/*
+ * 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 java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
+
+/**
+ * Extends {@link WritableByteChannel} with buffered (i.e. non-flushable) write
+ * operations, see {@link #writeBuffered(ByteBuffer)}. The messages are buffered
+ * and the channel is flushed after the buffer has overflew.
+ * <br>
+ * The method {@link #write(ByteBuffer)} flushes every written message.
+ */
+public interface WritableBufferedByteChannel extends WritableByteChannel
+{
+    void writeBuffered( ByteBuffer src ) throws IOException;
+}
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
index b125b0c..258b165 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/ForkingRunListenerTest.java
@@ -20,7 +20,6 @@ package org.apache.maven.surefire.booter;
  */
 
 import junit.framework.TestCase;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.mockito.ArgumentCaptor;
 
 import static org.fest.assertions.Assertions.assertThat;
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
index 7e3a685..0b64a42 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsReaderTest.java
@@ -38,7 +38,7 @@ import static java.nio.file.Files.write;
 import static org.fest.assertions.Assertions.assertThat;
 
 /**
- * The tests for {@link Channels#newChannel(InputStream)}.
+ * The tests for {@link Channels#newChannel(InputStream)} and {@link Channels#newBufferedChannel(InputStream)}.
  */
 public class ChannelsReaderTest
 {
@@ -107,6 +107,62 @@ public class ChannelsReaderTest
     }
 
     @Test
+    public void bufferedChannel() throws Exception
+    {
+        ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {1, 2, 3} );
+        ReadableByteChannel channel = Channels.newBufferedChannel( is );
+        ByteBuffer bb = ByteBuffer.allocate( 4 );
+
+        int countWritten = channel.read( bb );
+
+        assertThat( countWritten )
+            .isEqualTo( 3 );
+
+        assertThat( bb.arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.position() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.remaining() )
+            .isEqualTo( 1 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 4 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 4 );
+
+        bb.flip();
+
+        assertThat( bb.arrayOffset() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.position() )
+            .isEqualTo( 0 );
+
+        assertThat( bb.remaining() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 4 );
+
+        assertThat( bb.array() )
+            .isEqualTo( new byte[] {1, 2, 3, 0} );
+
+        assertThat( channel.isOpen() )
+            .isTrue();
+
+        channel.close();
+
+        assertThat( channel.isOpen() )
+            .isFalse();
+    }
+
+    @Test
     public void biggerBuffer() throws Exception
     {
         ByteArrayInputStream is = new ByteArrayInputStream( new byte[] {1, 2, 3} );
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
index a1f6a66..35c9ddd 100644
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/ChannelsWriterTest.java
@@ -38,7 +38,7 @@ import static java.nio.file.Files.readAllBytes;
 import static org.fest.assertions.Assertions.assertThat;
 
 /**
- * The tests for {@link Channels#newChannel(OutputStream)} and {@link Channels#newFlushableChannel(OutputStream)}.
+ * The tests for {@link Channels#newChannel(OutputStream)} and {@link Channels#newBufferedChannel(OutputStream)}.
  */
 public class ChannelsWriterTest
 {
@@ -63,7 +63,7 @@ public class ChannelsWriterTest
                 super.flush();
             }
         };
-        WritableByteChannel channel = Channels.newFlushableChannel( out );
+        WritableByteChannel channel = Channels.newBufferedChannel( out );
         ByteBuffer bb = ByteBuffer.wrap( new byte[] {1, 2, 3} );
         int countWritten = channel.write( bb );
         assertThat( countWritten )
@@ -119,6 +119,45 @@ public class ChannelsWriterTest
     }
 
     @Test
+    public void bufferedChannel() throws Exception
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        WritableBufferedByteChannel channel = Channels.newBufferedChannel( out );
+        ByteBuffer bb = ByteBuffer.allocate( 5 );
+        bb.put( (byte) 1 );
+        bb.put( (byte) 2 );
+        bb.put( (byte) 3 );
+
+        channel.writeBuffered( bb );
+
+        assertThat( out.toByteArray() )
+            .isEmpty();
+
+        channel.write( ByteBuffer.allocate( 0 ) );
+
+        assertThat( out.toByteArray() )
+            .isEmpty();
+
+        channel.write( ByteBuffer.wrap( new byte[] {4} ) );
+
+        assertThat( out.toByteArray() )
+            .hasSize( 4 )
+            .isEqualTo( new byte[] {1, 2, 3, 4} );
+
+        assertThat( bb.position() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.limit() )
+            .isEqualTo( 3 );
+
+        assertThat( bb.capacity() )
+            .isEqualTo( 5 );
+
+        assertThat( channel.isOpen() )
+            .isTrue();
+    }
+
+    @Test
     public void shouldFailAfterClosed() throws IOException
     {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
index e437f97..28766f5 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
@@ -22,8 +22,6 @@ package org.apache.maven.surefire.booter;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.providerapi.CommandChainReader;
 import org.apache.maven.surefire.providerapi.CommandListener;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 
 import java.io.EOFException;
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 46d9d74..1983ae2 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
@@ -23,8 +23,6 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.providerapi.CommandListener;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
@@ -166,6 +164,15 @@ public final class ForkedBooter
         }
         finally
         {
+            //noinspection ResultOfMethodCallIgnored
+            Thread.interrupted();
+
+            if ( eventChannel.checkError() )
+            {
+                DumpErrorSingleton.getSingleton()
+                    .dumpText( "The channel (std/out or TCP/IP) failed to send a stream from this subprocess." );
+            }
+
             acknowledgedExit();
         }
     }
@@ -382,9 +389,6 @@ public final class ForkedBooter
 
     private void acknowledgedExit()
     {
-        //noinspection ResultOfMethodCallIgnored
-        Thread.interrupted();
-
         commandReader.addByeAckListener( new CommandListener()
                                           {
                                               @Override
@@ -397,7 +401,11 @@ public final class ForkedBooter
         eventChannel.bye();
         launchLastDitchDaemonShutdownThread( 0 );
         long timeoutMillis = max( systemExitTimeoutInSeconds * ONE_SECOND_IN_MILLIS, ONE_SECOND_IN_MILLIS );
-        acquireOnePermit( exitBarrier, timeoutMillis );
+        boolean timeoutElapsed = !acquireOnePermit( exitBarrier, timeoutMillis );
+        if ( timeoutElapsed )
+        {
+            eventChannel.sendExitError( null, false );
+        }
         cancelPingScheduler();
         commandReader.stop();
         closeForkChannel();
@@ -544,15 +552,16 @@ public final class ForkedBooter
         return pluginProcessChecker != null && pluginProcessChecker.canUse();
     }
 
-    private static void acquireOnePermit( Semaphore barrier, long timeoutMillis )
+    private static boolean acquireOnePermit( Semaphore barrier, long timeoutMillis )
     {
         try
         {
-            barrier.tryAcquire( timeoutMillis, MILLISECONDS );
+            return barrier.tryAcquire( timeoutMillis, MILLISECONDS );
         }
         catch ( InterruptedException e )
         {
             // cancel schedulers, stop the command reader and exit 0
+            return true;
         }
     }
 
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
index 568a2c5..47ffa01 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
@@ -22,7 +22,6 @@ package org.apache.maven.surefire.booter;
 import java.util.Collections;
 import java.util.Iterator;
 
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.util.CloseableIterator;
 import org.apache.maven.surefire.util.TestsToRun;
 
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
index e8d3efb..df100e9 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelDecoder.java
@@ -22,7 +22,7 @@ package org.apache.maven.surefire.booter.spi;
 import org.apache.maven.surefire.booter.Command;
 import org.apache.maven.surefire.booter.DumpErrorSingleton;
 import org.apache.maven.surefire.booter.MasterProcessCommand;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.util.internal.ImmutableMap;
 
 import javax.annotation.Nonnull;
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
index a85e4ef..4a0c226 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoder.java
@@ -22,21 +22,22 @@ package org.apache.maven.surefire.booter.spi;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
 import org.apache.maven.surefire.booter.DumpErrorSingleton;
 import org.apache.maven.surefire.booter.ForkedProcessEventType;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.RunMode;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.shared.codec.binary.Base64;
+import org.apache.maven.surefire.util.internal.WritableBufferedByteChannel;
 
 import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedChannelException;
-import java.nio.channels.WritableByteChannel;
 import java.nio.charset.Charset;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.nio.charset.StandardCharsets.US_ASCII;
 import static java.nio.charset.StandardCharsets.UTF_8;
@@ -75,21 +76,20 @@ import static org.apache.maven.surefire.report.RunMode.RERUN_TEST_AFTER_FAILURE;
  */
 public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEncoder
 {
-    static final String MAGIC_NUMBER_DELIMITED = ':' + MAGIC_NUMBER + ':';
     private static final Base64 BASE64 = new Base64();
     private static final Charset STREAM_ENCODING = US_ASCII;
     private static final Charset STRING_ENCODING = UTF_8;
 
-    protected final WritableByteChannel out;
+    private final WritableBufferedByteChannel out;
     private final RunMode runMode;
-    private volatile boolean trouble;
+    private final AtomicBoolean trouble = new AtomicBoolean();
 
-    public LegacyMasterProcessChannelEncoder( @Nonnull WritableByteChannel out )
+    public LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out )
     {
         this( out, NORMAL_RUN );
     }
 
-    protected LegacyMasterProcessChannelEncoder( @Nonnull WritableByteChannel out, @Nonnull RunMode runMode )
+    protected LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out, @Nonnull RunMode runMode )
     {
         this.out = requireNonNull( out );
         this.runMode = requireNonNull( runMode );
@@ -110,7 +110,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     @Override
     public boolean checkError()
     {
-        return trouble;
+        return trouble.get();
     }
 
     @Override
@@ -121,56 +121,56 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
             String key = entry.getKey();
             String value = entry.getValue();
             StringBuilder event = encode( BOOTERCODE_SYSPROPS, runMode, key, value );
-            encodeAndPrintEvent( event );
+            encodeAndPrintEvent( event, false );
         }
     }
 
     @Override
     public void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testSetCompleted( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testStarting( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_STARTING, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_STARTING, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testSucceeded( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testFailed( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_FAILED, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_FAILED, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testSkipped( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testError( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_ERROR, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_ERROR, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
     public void testAssumptionFailure( ReportEntry reportEntry, boolean trimStackTraces )
     {
-        encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry, trimStackTraces );
+        encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry, trimStackTraces, true );
     }
 
     @Override
@@ -191,14 +191,14 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     {
         String base64Message = toBase64( message );
         StringBuilder event = encodeMessage( eventType, runMode.geRunName(), base64Message );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, false );
     }
 
     @Override
     public void consoleInfoLog( String msg )
     {
         StringBuilder event = print( BOOTERCODE_CONSOLE_INFO.getOpcode(), msg );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, true );
     }
 
     @Override
@@ -206,7 +206,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     {
         StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
         encode( encoded, msg, null, null );
-        encodeAndPrintEvent( encoded );
+        encodeAndPrintEvent( encoded, true );
     }
 
     @Override
@@ -220,74 +220,75 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     {
         StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
         encode( encoded, msg, null, ConsoleLoggerUtils.toString( t ) );
-        encodeAndPrintEvent( encoded );
+        encodeAndPrintEvent( encoded, true );
     }
 
     @Override
     public void consoleErrorLog( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
     {
-        error( stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR );
+        error( stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR, true );
     }
 
     @Override
     public void consoleDebugLog( String msg )
     {
         StringBuilder event = print( BOOTERCODE_CONSOLE_DEBUG.getOpcode(), msg );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, true );
     }
 
     @Override
     public void consoleWarningLog( String msg )
     {
         StringBuilder event = print( BOOTERCODE_CONSOLE_WARNING.getOpcode(), msg );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, true );
     }
 
     @Override
     public void bye()
     {
-        encodeOpcode( BOOTERCODE_BYE );
+        encodeOpcode( BOOTERCODE_BYE, true );
     }
 
     @Override
     public void stopOnNextTest()
     {
-        encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST );
+        encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST, true );
     }
 
     @Override
     public void acquireNextTest()
     {
-        encodeOpcode( BOOTERCODE_NEXT_TEST );
+        encodeOpcode( BOOTERCODE_NEXT_TEST, true );
     }
 
     @Override
-    public void sendExitEvent( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
+    public void sendExitError( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
     {
-        error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR );
+        error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR, true );
     }
 
-    private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType event )
+    private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType event,
+                        @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
     {
         StringBuilder encoded = encodeHeader( event.getOpcode(), null );
         encode( encoded, stackTraceWriter, trimStackTraces );
-        encodeAndPrintEvent( encoded );
+        encodeAndPrintEvent( encoded, sendImmediately );
     }
 
     private void encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry,
-                         boolean trimStackTraces )
+                         boolean trimStackTraces, @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
     {
         StringBuilder event = encode( operation.getOpcode(), runMode.geRunName(), reportEntry, trimStackTraces );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, sendImmediately );
     }
 
-    private void encodeOpcode( ForkedProcessEventType operation )
+    private void encodeOpcode( ForkedProcessEventType operation, boolean sendImmediately )
     {
         StringBuilder event = encodeOpcode( operation.getOpcode(), null );
-        encodeAndPrintEvent( event );
+        encodeAndPrintEvent( event, sendImmediately );
     }
 
-    private void encodeAndPrintEvent( StringBuilder event )
+    private void encodeAndPrintEvent( StringBuilder event, boolean sendImmediately )
     {
         try
         {
@@ -298,7 +299,16 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
                 .toString()
                 .getBytes( STREAM_ENCODING );
 
-            out.write( ByteBuffer.wrap( array ) );
+            ByteBuffer bb = ByteBuffer.wrap( array );
+
+            if ( sendImmediately )
+            {
+                out.write( bb );
+            }
+            else
+            {
+                out.writeBuffered( bb );
+            }
         }
         catch ( ClosedChannelException e )
         {
@@ -307,9 +317,11 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
         }
         catch ( IOException e )
         {
-            DumpErrorSingleton.getSingleton()
-                .dumpException( e );
-            trouble = true;
+            if ( trouble.compareAndSet( false, true ) )
+            {
+                DumpErrorSingleton.getSingleton()
+                    .dumpException( e );
+            }
         }
     }
 
@@ -415,7 +427,7 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     }
 
     /**
-     * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEventType)}
+     * Used in {@link #bye()}, {@link #stopOnNextTest()} and {@link #encodeOpcode(ForkedProcessEventType, boolean)}
      * and private methods extending the buffer.
      *
      * @param operation opcode
@@ -425,7 +437,9 @@ public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEn
     static StringBuilder encodeOpcode( String operation, String runMode )
     {
         StringBuilder s = new StringBuilder( 128 )
-            .append( MAGIC_NUMBER_DELIMITED )
+            .append( ':' )
+            .append( MAGIC_NUMBER )
+            .append( ':' )
             .append( operation )
             .append( ':' );
 
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
index 5713b99..c09516b 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelProcessorFactory.java
@@ -19,15 +19,14 @@ package org.apache.maven.surefire.booter.spi;
  * under the License.
  */
 
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 
 import java.io.IOException;
 import java.net.MalformedURLException;
 
-import static org.apache.maven.surefire.util.internal.Channels.newChannel;
-import static org.apache.maven.surefire.util.internal.Channels.newFlushableChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
 
 /**
  * Producer of encoder and decoder for process pipes.
@@ -57,13 +56,13 @@ public class LegacyMasterProcessChannelProcessorFactory
     @Override
     public MasterProcessChannelDecoder createDecoder()
     {
-        return new LegacyMasterProcessChannelDecoder( newChannel( System.in ) );
+        return new LegacyMasterProcessChannelDecoder( newBufferedChannel( System.in ) );
     }
 
     @Override
     public MasterProcessChannelEncoder createEncoder()
     {
-        return new LegacyMasterProcessChannelEncoder( newFlushableChannel( System.out ) );
+        return new LegacyMasterProcessChannelEncoder( newBufferedChannel( System.out ) );
     }
 
     @Override
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
index 1893009..6f52756 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java
@@ -19,16 +19,28 @@ package org.apache.maven.surefire.booter.spi;
  * under the License.
  */
 
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
+import java.net.SocketOption;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.nio.channels.SocketChannel;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.util.concurrent.ExecutionException;
+
+import static java.net.StandardSocketOptions.SO_KEEPALIVE;
+import static java.net.StandardSocketOptions.SO_REUSEADDR;
+import static java.net.StandardSocketOptions.TCP_NODELAY;
+import static java.nio.channels.AsynchronousChannelGroup.withFixedThreadPool;
+import static java.nio.channels.AsynchronousSocketChannel.open;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newInputStream;
+import static org.apache.maven.surefire.util.internal.Channels.newOutputStream;
+import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory;
 
 /**
  * Producer of TCP/IP encoder and decoder.
@@ -40,7 +52,7 @@ import java.nio.channels.SocketChannel;
 public class SurefireMasterProcessChannelProcessorFactory
     implements MasterProcessChannelProcessorFactory
 {
-    private volatile SocketChannel channel;
+    private volatile AsynchronousSocketChannel clientSocketChannel;
 
     @Override
     public boolean canUse( String channelConfig )
@@ -59,32 +71,52 @@ public class SurefireMasterProcessChannelProcessorFactory
         try
         {
             URI uri = new URI( channelConfig );
-            channel = SocketChannel.open( new InetSocketAddress( uri.getHost(), uri.getPort() ) );
+            InetSocketAddress hostAddress = new InetSocketAddress( uri.getHost(), uri.getPort() );
+            clientSocketChannel = open( withFixedThreadPool( 2, newDaemonThreadFactory() ) );
+            setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE );
+            clientSocketChannel.connect( hostAddress ).get();
         }
-        catch ( URISyntaxException e )
+        catch ( URISyntaxException | InterruptedException e )
         {
             throw new IOException( e.getLocalizedMessage(), e );
         }
+        catch ( ExecutionException e )
+        {
+            throw new IOException( e.getLocalizedMessage(), e.getCause() );
+        }
     }
 
     @Override
     public MasterProcessChannelDecoder createDecoder()
     {
-        return new LegacyMasterProcessChannelDecoder( channel );
+        return new LegacyMasterProcessChannelDecoder( newBufferedChannel( newInputStream( clientSocketChannel ) ) );
     }
 
     @Override
     public MasterProcessChannelEncoder createEncoder()
     {
-        return new LegacyMasterProcessChannelEncoder( channel );
+        return new LegacyMasterProcessChannelEncoder( newBufferedChannel( newOutputStream( clientSocketChannel ) ) );
     }
 
     @Override
     public void close() throws IOException
     {
-        if ( channel != null )
+        if ( clientSocketChannel != null && clientSocketChannel.isOpen() )
+        {
+            clientSocketChannel.close();
+        }
+    }
+
+    @SafeVarargs
+    private final void setTrueOptions( SocketOption<Boolean>... options )
+        throws IOException
+    {
+        for ( SocketOption<Boolean> option : options )
         {
-            channel.close();
+            if ( clientSocketChannel.supportedOptions().contains( option ) )
+            {
+                clientSocketChannel.setOption( option, true );
+            }
         }
     }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
index edaae08..1c85048 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/CommandReaderTest.java
@@ -23,8 +23,8 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
 import org.apache.maven.surefire.testset.TestSetFailedException;
+import org.apache.maven.surefire.util.internal.WritableBufferedByteChannel;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -34,7 +34,6 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
-import java.nio.channels.WritableByteChannel;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.concurrent.BlockingQueue;
@@ -44,7 +43,8 @@ import java.util.concurrent.FutureTask;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import static java.nio.channels.Channels.newChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newChannel;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -262,8 +262,8 @@ public class CommandReaderTest
         }
     }
 
-    private static WritableByteChannel nul()
+    private static WritableBufferedByteChannel nul()
     {
-        return newChannel( new PrintStream( new ByteArrayOutputStream() ) );
+        return newBufferedChannel( new PrintStream( new ByteArrayOutputStream() ) );
     }
 }
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
index fa8011d..5604fd1 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java
@@ -23,8 +23,6 @@ import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
 import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
 import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
 import org.junit.Rule;
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
index 3f6d148..aac78f7 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java
@@ -151,8 +151,9 @@ public class ForkedBooterTest
     public void testBarrier() throws Exception
     {
         Semaphore semaphore = new Semaphore( 2 );
-        invokeMethod( ForkedBooter.class, "acquireOnePermit", semaphore, 30_000L );
+        boolean acquiredOnePermit = invokeMethod( ForkedBooter.class, "acquireOnePermit", semaphore, 30_000L );
 
+        assertThat( acquiredOnePermit ).isTrue();
         assertThat( semaphore.availablePermits() ).isEqualTo( 1 );
     }
 
diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
index acfcac3..761e30f 100644
--- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
+++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/spi/LegacyMasterProcessChannelEncoderTest.java
@@ -23,6 +23,7 @@ import org.apache.maven.surefire.report.ReportEntry;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.util.internal.ObjectUtils;
+import org.apache.maven.surefire.util.internal.WritableBufferedByteChannel;
 import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
@@ -34,17 +35,17 @@ import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.util.Map;
 
-import static java.nio.channels.Channels.newChannel;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.copyOfRange;
-import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.MAGIC_NUMBER_DELIMITED;
-import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.toBase64;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
 import static org.apache.maven.surefire.shared.codec.binary.Base64.encodeBase64String;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encode;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeHeader;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeMessage;
 import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.encodeOpcode;
+import static org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder.toBase64;
 import static org.apache.maven.surefire.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
+import static org.apache.maven.surefire.booter.ForkedProcessEventType.MAGIC_NUMBER;
 import static org.apache.maven.surefire.report.RunMode.NORMAL_RUN;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -71,7 +72,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void shouldHaveSystemProperty()
     {
         StringBuilder actualEncoded = encode( BOOTERCODE_SYSPROPS, NORMAL_RUN, "arg1", "arg2" );
-        String expected = MAGIC_NUMBER_DELIMITED + BOOTERCODE_SYSPROPS.getOpcode()
+        String expected = ':' + MAGIC_NUMBER + ':' + BOOTERCODE_SYSPROPS.getOpcode()
             + ":normal-run:UTF-8:YXJnMQ==:YXJnMg==:";
 
         assertThat( actualEncoded.toString() )
@@ -235,7 +236,7 @@ public class LegacyMasterProcessChannelEncoderTest
                 );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetStarting( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -266,7 +267,7 @@ public class LegacyMasterProcessChannelEncoderTest
         assertThat( printedLines.readLine() ).isNull();
 
         out = Stream.newStream();
-        encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetStarting( reportEntry, false );
         printedLines = out.newReader( UTF_8 );
@@ -334,7 +335,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSetCompleted( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -402,7 +403,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testStarting( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -470,7 +471,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSucceeded( reportEntry, true );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -538,7 +539,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testFailed( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -605,7 +606,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testSkipped( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -671,7 +672,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testError( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -735,7 +736,7 @@ public class LegacyMasterProcessChannelEncoderTest
         String encodedMessage = encodeBase64String( toArray( UTF_8.encode( reportEntry.getMessage() ) ) );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.testAssumptionFailure( reportEntry, false );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -770,7 +771,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testBye() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.bye();
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -783,7 +784,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStopOnNextTest() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.stopOnNextTest();
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -796,7 +797,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testAcquireNextTest() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.acquireNextTest();
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -821,7 +822,7 @@ public class LegacyMasterProcessChannelEncoderTest
                 .isEqualTo( ":maven-surefire-event:some-opcode:normal-run:UTF-8:msg:" );
 
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
         encoded = encoder.print( "some-opcode", "msg" );
         assertThat( encoded.toString() )
                 .isEqualTo( ":maven-surefire-event:some-opcode:UTF-8:bXNn:" );
@@ -835,7 +836,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleInfo()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleInfoLog( "msg" );
 
@@ -854,7 +855,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleError()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleErrorLog( "msg" );
 
@@ -872,7 +873,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog1() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleErrorLog( new Exception( "msg" ) );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -885,7 +886,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog2() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleErrorLog( "msg2", new Exception( "msg" ) );
         LineNumberReader printedLines = out.newReader( UTF_8 );
@@ -898,7 +899,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleErrorLog3() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
@@ -917,7 +918,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleDebug()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleDebugLog( "msg" );
 
@@ -936,7 +937,7 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testConsoleWarning()
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
 
         encoder.consoleWarningLog( "msg" );
 
@@ -955,9 +956,11 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdOutStream() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         encoder.stdOut( "msg", false );
+        channel.close();
 
         String expected = ":maven-surefire-event:std-out-stream:normal-run:UTF-8:bXNn:";
 
@@ -972,9 +975,11 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdOutStreamLn() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         encoder.stdOut( "msg", true );
+        channel.close();
 
         String expected = ":maven-surefire-event:std-out-stream-new-line:normal-run:UTF-8:bXNn:";
 
@@ -989,9 +994,11 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdErrStream() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         encoder.stdErr( "msg", false );
+        channel.close();
 
         String expected = ":maven-surefire-event:std-err-stream:normal-run:UTF-8:bXNn:";
 
@@ -1006,9 +1013,11 @@ public class LegacyMasterProcessChannelEncoderTest
     public void testStdErrStreamLn() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         encoder.stdErr( "msg", true );
+        channel.close();
 
         String expected = ":maven-surefire-event:std-err-stream-new-line:normal-run:UTF-8:bXNn:";
 
@@ -1024,11 +1033,13 @@ public class LegacyMasterProcessChannelEncoderTest
     public void shouldCountSameNumberOfSystemProperties() throws IOException
     {
         Stream out = Stream.newStream();
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        WritableBufferedByteChannel channel = newBufferedChannel( out );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( channel );
 
         Map<String, String> sysProps = ObjectUtils.systemProps();
         int expectedSize = sysProps.size();
         encoder.sendSystemProperties( sysProps );
+        channel.close();
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
 
@@ -1048,13 +1059,13 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
         when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
-        encoder.sendExitEvent( stackTraceWriter, false );
+        encoder.sendExitError( stackTraceWriter, false );
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
@@ -1066,13 +1077,13 @@ public class LegacyMasterProcessChannelEncoderTest
     {
         Stream out = Stream.newStream();
 
-        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newChannel( out ) );
+        LegacyMasterProcessChannelEncoder encoder = new LegacyMasterProcessChannelEncoder( newBufferedChannel( out ) );
         StackTraceWriter stackTraceWriter = mock( StackTraceWriter.class );
         when( stackTraceWriter.getThrowable() ).thenReturn( new SafeThrowable( "1" ) );
         when( stackTraceWriter.smartTrimmedStackTrace() ).thenReturn( "2" );
         when( stackTraceWriter.writeTraceToString() ).thenReturn( "3" );
         when( stackTraceWriter.writeTrimmedTraceToString() ).thenReturn( "4" );
-        encoder.sendExitEvent( stackTraceWriter, true );
+        encoder.sendExitError( stackTraceWriter, true );
 
         LineNumberReader printedLines = out.newReader( UTF_8 );
         assertThat( printedLines.readLine() )
diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
index cb57b9c..ddc43ea 100644
--- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
+++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/util/CommandlineStreams.java
@@ -28,8 +28,7 @@ import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
 
-import static org.apache.maven.surefire.util.internal.Channels.newChannel;
-import static org.apache.maven.surefire.util.internal.Channels.newFlushableChannel;
+import static org.apache.maven.surefire.util.internal.Channels.newBufferedChannel;
 
 /**
  *
@@ -44,12 +43,12 @@ public final class CommandlineStreams implements Closeable
     public CommandlineStreams( @Nonnull Process process )
     {
         InputStream stdOutStream = process.getInputStream();
-        stdOutChannel = newChannel( stdOutStream );
+        stdOutChannel = newBufferedChannel( stdOutStream );
 
         InputStream stdErrStream = process.getErrorStream();
-        stdErrChannel = newChannel( stdErrStream );
+        stdErrChannel = newBufferedChannel( stdErrStream );
 
-        stdInChannel = newFlushableChannel( process.getOutputStream() );
+        stdInChannel = newBufferedChannel( process.getOutputStream() );
     }
 
     public ReadableByteChannel getStdOutChannel()
diff --git a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
index 989a52d..c1c5c3c 100644
--- a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
+++ b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java
@@ -19,8 +19,8 @@ package org.apache.maven.surefire.spi;
  * under the License.
  */
 
-import org.apache.maven.surefire.providerapi.MasterProcessChannelDecoder;
-import org.apache.maven.surefire.providerapi.MasterProcessChannelEncoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelDecoder;
+import org.apache.maven.surefire.booter.MasterProcessChannelEncoder;
 
 import java.io.Closeable;
 import java.io.IOException;
diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
index 21b92b9..1fd03a5 100644
--- a/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
+++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/ConsoleOutputIT.java
@@ -19,13 +19,13 @@ package org.apache.maven.surefire.its;
  * under the License.
  */
 
-import com.googlecode.junittoolbox.ParallelParameterized;
 import org.apache.maven.surefire.its.fixture.OutputValidator;
 import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase;
 import org.apache.maven.surefire.its.fixture.SurefireLauncher;
 import org.apache.maven.surefire.its.fixture.TestFile;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
@@ -40,7 +40,7 @@ import static org.hamcrest.Matchers.equalTo;
  *
  * @author Kristian Rosenvold
  */
-@RunWith( ParallelParameterized.class )
+@RunWith( Parameterized.class )
 public class ConsoleOutputIT
     extends SurefireJUnit4IntegrationTestCase
 {
@@ -67,7 +67,7 @@ public class ConsoleOutputIT
     public void properNewlinesAndEncodingWithDefaultEncodings() throws Exception
     {
         OutputValidator outputValidator = unpack().forkOnce().executeTest();
-        validate( outputValidator, profileId == null, true );
+        validate( outputValidator, true );
     }
 
     @Test
@@ -77,7 +77,7 @@ public class ConsoleOutputIT
                 .forkOnce()
                 .argLine( "-Dfile.encoding=UTF-16" )
                 .executeTest();
-        validate( outputValidator, profileId == null, true );
+        validate( outputValidator, true );
     }
 
     @Test
@@ -86,7 +86,7 @@ public class ConsoleOutputIT
         OutputValidator outputValidator = unpack()
                 .forkNever()
                 .executeTest();
-        validate( outputValidator, false, false );
+        validate( outputValidator, false );
     }
 
     private SurefireLauncher unpack()
@@ -103,7 +103,7 @@ public class ConsoleOutputIT
         return launcher;
     }
 
-    private void validate( final OutputValidator outputValidator, boolean includeShutdownHook, boolean canFork )
+    private void validate( final OutputValidator outputValidator, boolean canFork )
         throws Exception
     {
         TestFile xmlReportFile = outputValidator.getSurefireReportsXmlFile( "TEST-consoleOutput.Test1.xml" );
@@ -116,13 +116,6 @@ public class ConsoleOutputIT
         outputFile.assertContainsText( "SoutLine" );
         outputFile.assertContainsText( "äöüß" );
 
-        if ( includeShutdownHook )
-        {
-            //todo it should not be reported in the last test which is completed
-            //todo this text should be in null-output.txt
-            outputFile.assertContainsText( "Printline in shutdown hook" );
-        }
-
         String cls = profileId == null ? LEGACY_FORK_NODE : SUREFIRE_FORK_NODE;
 
         if ( canFork )
diff --git a/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java b/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java
index 93e6a83..db74acf 100644
--- a/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java
+++ b/surefire-its/src/test/resources/consoleOutput/src/test/java/consoleOutput/Test1.java
@@ -29,26 +29,8 @@ import static org.junit.Assert.fail;
 
 public class Test1
 {
-    static
+    public Test1()
     {
-       System.out.println("Printline in static block");
-        Runtime.getRuntime().addShutdownHook(  new Thread( ){
-            @Override
-            public void run()
-            {
-                System.out.println( "Printline in shutdown hook" );
-            }
-        });
-    }
-
-    @Override
-    protected void finalize()
-        throws Throwable
-    {
-        System.out.println( "Printline in finalizer" );
-    }
-
-    public Test1(){
        System.out.println("In constructor");
     }
 
diff --git a/surefire-its/src/test/resources/consoleoutput-noisy/src/test/java/consoleoutput_noisy/Test3.java b/surefire-its/src/test/resources/consoleoutput-noisy/src/test/java/consoleoutput_noisy/Test3.java
new file mode 100644
index 0000000..84c6198
--- /dev/null
+++ b/surefire-its/src/test/resources/consoleoutput-noisy/src/test/java/consoleoutput_noisy/Test3.java
@@ -0,0 +1,52 @@
+package consoleoutput_noisy;
+
+/*
+ * 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.junit.Test;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import java.io.File;
+
+/**
+ *
+ */
+public class Test3
+{
+    @Test
+    public void test() throws Exception
+    {
+        long t1 = System.currentTimeMillis();
+        System.out.println( "t1 = " + t1 );
+        for ( int i = 0; i < 320_000; i++ )
+        {
+            System.out.println( "01234567890123456789012345678901234567890123456789"
+                + "01234567890123456789012345678901234567890123456789" );
+        }
+        long t2 = System.currentTimeMillis();
+        System.out.println( "t2 = " + t2 );
+
+        File target = new File( System.getProperty( "user.dir" ) );
+        new File( target, ( t2 - t1 ) + "" )
+            .createNewFile();
+    }
+}