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/07/19 10:11:37 UTC

[maven-surefire] branch fastqueue updated (74bf248 -> 89d0d74)

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

tibordigana pushed a change to branch fastqueue
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git.


 discard 74bf248  improvement with minimizing the creation of new strings in stripIllegalFilenameChars()
 discard 82063b4  [SUREFIRE-1825] Unable to zip the Cucumber TXT report file on Linux and MacOS
 discard a2523ab  [github] Upload artifact surefire-its
 discard 57b2d08  faster queue
     new 6868567  [SUREFIRE-1826] Improved performance of ThreadedStreamConsumer
     new 5e68427  [github] Upload artifact surefire-its
     new 53ea3ea  [SUREFIRE-1825] Unable to zip the Cucumber TXT report file on Linux and MacOS
     new 89d0d74  improvement with minimizing the creation of new strings in stripIllegalFilenameChars()

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (74bf248)
            \
             N -- N -- N   refs/heads/fastqueue (89d0d74)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/maven-windows-it1.yml | 5 ++++-
 .github/workflows/maven-windows-it2.yml | 7 +++++--
 .github/workflows/maven.yml             | 7 +++++--
 3 files changed, 14 insertions(+), 5 deletions(-)


[maven-surefire] 02/04: [github] Upload artifact surefire-its

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5e684276de8567f0ddd921b2b9c64b99efd79cad
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Jul 18 14:35:36 2020 +0200

    [github] Upload artifact surefire-its
---
 .github/workflows/maven-windows-it1.yml | 23 ++++++++++++++++++++++-
 .github/workflows/maven-windows-it2.yml | 23 ++++++++++++++++++++++-
 .github/workflows/maven.yml             | 20 +++++++++++++++++++-
 .github/workflows/smoketest.yml         |  8 +++++++-
 4 files changed, 70 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/maven-windows-it1.yml b/.github/workflows/maven-windows-it1.yml
index 4679153..17164ed 100644
--- a/.github/workflows/maven-windows-it1.yml
+++ b/.github/workflows/maven-windows-it1.yml
@@ -17,12 +17,21 @@
 
 name: GitHub CI for Windows 1
 
-on: [push, pull_request]
+on:
+  push:
+    branches:
+      - '**'
+  pull_request:
+    branches:
+      - '**'
 
 jobs:
   build:
     runs-on: windows-latest
 
+    strategy:
+      fail-fast: false
+
     steps:
       - name: Checkout
         uses: actions/checkout@v1
@@ -34,3 +43,15 @@ jobs:
 
       - name: Build with Maven
         run: mvn install -e -B -V -nsu --no-transfer-progress -P run-its "-Dit.test=**/jiras/*IT*.java,TestMethodPatternIT,TestMultipleMethodPatternsIT,TestMultipleMethodPatternsTestNGIT"
+
+      - name: Upload artifact surefire-its
+        uses: actions/upload-artifact@v2-preview
+        #if: failure()
+        with:
+          name: ${{ matrix.os }}-surefire-its
+          path: |
+            surefire-its/target/*/log.txt
+            surefire-its/target/**/surefire-reports/*
+            surefire-its/target/**/failsafe-reports/*
+            !surefire-its/target/*-1617
+            !surefire-its/target/failsafe-reports
diff --git a/.github/workflows/maven-windows-it2.yml b/.github/workflows/maven-windows-it2.yml
index 58a04c1..3e1f409 100644
--- a/.github/workflows/maven-windows-it2.yml
+++ b/.github/workflows/maven-windows-it2.yml
@@ -17,12 +17,21 @@
 
 name: GitHub CI for Windows 2
 
-on: [push, pull_request]
+on:
+  push:
+    branches:
+      - '**'
+  pull_request:
+    branches:
+      - '**'
 
 jobs:
   build:
     runs-on: windows-latest
 
+    strategy:
+      fail-fast: false
+
     steps:
       - name: Checkout
         uses: actions/checkout@v1
@@ -37,3 +46,15 @@ jobs:
 
       - name: Build with Maven without Unit Tests
         run: mvn install -e -B -V -nsu --no-transfer-progress -rf :surefire-its -P run-its "-Dit.test=!**/jiras/*IT*.java,!TestMethodPatternIT,!TestMultipleMethodPatternsIT,!TestMultipleMethodPatternsTestNGIT"
+
+      - name: Upload artifact surefire-its
+        uses: actions/upload-artifact@v2-preview
+        if: failure()
+        with:
+          name: ${{ matrix.os }}-surefire-its
+          path: |
+            surefire-its/target/*/log.txt
+            surefire-its/target/**/surefire-reports/*
+            surefire-its/target/**/failsafe-reports/*
+            !surefire-its/target/*-1617
+            !surefire-its/target/failsafe-reports
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index d0c9786..41dde5e 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -17,7 +17,13 @@
 
 name: GitHub CI for *Nix
 
-on: [push, pull_request]
+on:
+  push:
+    branches:
+      - '**'
+  pull_request:
+    branches:
+      - '**'
 
 jobs:
   build:
@@ -40,3 +46,15 @@ jobs:
 
       - name: Build with Maven
         run: mvn install -e -B -V -nsu --no-transfer-progress -P run-its
+
+      - name: Upload artifact surefire-its
+        uses: actions/upload-artifact@v2-preview
+        if: failure()
+        with:
+          name: ${{ matrix.os }}-surefire-its
+          path: |
+            surefire-its/target/*/log.txt
+            surefire-its/target/**/surefire-reports/*
+            surefire-its/target/**/failsafe-reports/*
+            !surefire-its/target/*-1617
+            !surefire-its/target/failsafe-reports
diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml
index 7500579..67c17c7 100644
--- a/.github/workflows/smoketest.yml
+++ b/.github/workflows/smoketest.yml
@@ -17,7 +17,13 @@
 
 name: Unit Tests
 
-on: [push, pull_request]
+on:
+  push:
+    branches:
+      - '**'
+  pull_request:
+    branches:
+      - '**'
 
 jobs:
   build:


[maven-surefire] 03/04: [SUREFIRE-1825] Unable to zip the Cucumber TXT report file on Linux and MacOS

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 53ea3ea7c320c62c9068c1d226a8f1233e99e7e0
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Jul 18 22:26:55 2020 +0200

    [SUREFIRE-1825] Unable to zip the Cucumber TXT report file on Linux and MacOS
---
 .../org/apache/maven/plugin/surefire/report/FileReporterUtils.java  | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
index 31d1904..5c0e5b1 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
@@ -47,6 +47,10 @@ public final class FileReporterUtils
 
     private static String getOSSpecificIllegalChars()
     {
-        return IS_OS_WINDOWS ? "\\/:*?\"<>|\0" : "/\0";
+        // forbidden and quoted characters
+        // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
+        // https://cygwin.com/cygwin-ug-net/using-specialnames.html
+        // https://www.cyberciti.biz/faq/linuxunix-rules-for-naming-file-and-directory-names/
+        return IS_OS_WINDOWS ? "[],\\/:*?\"<>|\0" : "()&\\/:*?\"<>|\0";
     }
 }


[maven-surefire] 04/04: improvement with minimizing the creation of new strings in stripIllegalFilenameChars()

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 89d0d745a1c881bb68cfdd5efbdde113eb231e58
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Jul 18 23:22:36 2020 +0200

    improvement with minimizing the creation of new strings in stripIllegalFilenameChars()
---
 .../maven/plugin/surefire/report/FileReporterUtils.java    | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
index 5c0e5b1..5ef0610 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
@@ -35,14 +35,18 @@ public final class FileReporterUtils
 
     public static String stripIllegalFilenameChars( String original )
     {
-        String result = original;
+        StringBuilder result = new StringBuilder( original );
         String illegalChars = getOSSpecificIllegalChars();
-        for ( int i = 0; i < illegalChars.length(); i++ )
+        for ( int i = 0, len = result.length(); i < len; i++ )
         {
-            result = result.replace( illegalChars.charAt( i ), '_' );
+            char charFromOriginal = result.charAt( i );
+            boolean isIllegalChar = illegalChars.indexOf( charFromOriginal ) != -1;
+            if ( isIllegalChar )
+            {
+                result.setCharAt( i, '_' );
+            }
         }
-
-        return result;
+        return result.toString();
     }
 
     private static String getOSSpecificIllegalChars()


[maven-surefire] 01/04: [SUREFIRE-1826] Improved performance of ThreadedStreamConsumer

Posted by ti...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 68685672ec799c2cc92f6583379e66ee3d7167aa
Author: tibordigana <ti...@apache.org>
AuthorDate: Sat Jul 11 02:59:12 2020 +0200

    [SUREFIRE-1826] Improved performance of ThreadedStreamConsumer
---
 .../output/ThreadedStreamConsumer.java             | 190 ++++++++++++++++-----
 .../surefire/report/ConsoleOutputFileReporter.java |  34 +---
 .../output/ThreadedStreamConsumerTest.java         | 151 ++++++++++++++++
 .../org/apache/maven/surefire/JUnit4SuiteTest.java |   2 +
 4 files changed, 308 insertions(+), 69 deletions(-)

diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
index 7136834..1114948 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumer.java
@@ -20,41 +20,42 @@ package org.apache.maven.plugin.surefire.booterclient.output;
  */
 
 import org.apache.maven.surefire.api.event.Event;
-import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
 import org.apache.maven.surefire.extensions.EventHandler;
-import org.apache.maven.surefire.api.util.internal.DaemonThreadFactory;
+import org.apache.maven.surefire.shared.utils.cli.StreamConsumer;
 
 import javax.annotation.Nonnull;
 import java.io.Closeable;
 import java.io.IOException;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.AbstractQueuedSynchronizer;
 
-import static java.lang.Thread.currentThread;
+import static org.apache.maven.surefire.api.util.internal.DaemonThreadFactory.newDaemonThread;
 
 /**
- * Knows how to reconstruct *all* the state transmitted over stdout by the forked process.
+ * Knows how to reconstruct *all* the state transmitted over Channel by the forked process.
+ * <br>
+ * After applying the performance improvements with {@link QueueSynchronizer} the throughput becomes
+ * 6.33 mega messages per second
+ * (158 nano seconds per message, 5 million messages within 0.79 seconds - see the test ThreadedStreamConsumerTest)
+ * on CPU i5 Dual Core 2.6 GHz and Oracle JDK 11.
  *
  * @author Kristian Rosenvold
  */
 public final class ThreadedStreamConsumer
-        implements EventHandler<Event>, Closeable
+    implements EventHandler<Event>, Closeable
 {
+    private static final int QUEUE_MAX_ITEMS = 10_000;
     private static final Event END_ITEM = new FinalEvent();
 
-    private static final int ITEM_LIMIT_BEFORE_SLEEP = 10_000;
-
-    private final BlockingQueue<Event> items = new ArrayBlockingQueue<>( ITEM_LIMIT_BEFORE_SLEEP );
-
+    private final QueueSynchronizer<Event> synchronizer = new QueueSynchronizer<>( QUEUE_MAX_ITEMS, END_ITEM );
     private final AtomicBoolean stop = new AtomicBoolean();
-
-    private final Thread thread;
-
+    private final AtomicBoolean isAlive = new AtomicBoolean( true );
     private final Pumper pumper;
 
     final class Pumper
-            implements Runnable
+        implements Runnable
     {
         private final EventHandler<Event> target;
 
@@ -79,15 +80,17 @@ public final class ThreadedStreamConsumer
         @Override
         public void run()
         {
-            while ( !ThreadedStreamConsumer.this.stop.get() || !ThreadedStreamConsumer.this.items.isEmpty() )
+            while ( !stop.get() || !synchronizer.isEmptyQueue() )
             {
                 try
                 {
-                    Event item = ThreadedStreamConsumer.this.items.take();
+                    Event item = synchronizer.awaitNext();
+
                     if ( shouldStopQueueing( item ) )
                     {
-                        return;
+                        break;
                     }
+
                     target.handleEvent( item );
                 }
                 catch ( Throwable t )
@@ -95,6 +98,8 @@ public final class ThreadedStreamConsumer
                     errors.addException( t );
                 }
             }
+
+            isAlive.set( false );
         }
 
         boolean hasErrors()
@@ -111,7 +116,7 @@ public final class ThreadedStreamConsumer
     public ThreadedStreamConsumer( EventHandler<Event> target )
     {
         pumper = new Pumper( target );
-        thread = DaemonThreadFactory.newDaemonThread( pumper, "ThreadedStreamConsumer" );
+        Thread thread = newDaemonThread( pumper, "ThreadedStreamConsumer" );
         thread.start();
     }
 
@@ -122,37 +127,24 @@ public final class ThreadedStreamConsumer
         {
             return;
         }
-        else if ( !thread.isAlive() )
+        // Do NOT call Thread.isAlive() - slow.
+        // It makes worse performance from 790 millis to 1250 millis for 5 million messages.
+        else if ( !isAlive.get() )
         {
-            items.clear();
+            synchronizer.clearQueue();
             return;
         }
 
-        try
-        {
-            items.put( event );
-        }
-        catch ( InterruptedException e )
-        {
-            currentThread().interrupt();
-            throw new IllegalStateException( e );
-        }
+        synchronizer.pushNext( event );
     }
 
     @Override
     public void close()
-            throws IOException
+        throws IOException
     {
         if ( stop.compareAndSet( false, true ) )
         {
-            try
-            {
-                items.put( END_ITEM );
-            }
-            catch ( InterruptedException e )
-            {
-                currentThread().interrupt();
-            }
+            synchronizer.markStopped();
         }
 
         if ( pumper.hasErrors() )
@@ -167,7 +159,7 @@ public final class ThreadedStreamConsumer
      * @param item    element from <code>items</code>
      * @return {@code true} if tail of the queue
      */
-    private boolean shouldStopQueueing( Event item )
+    private static boolean shouldStopQueueing( Event item )
     {
         return item == END_ITEM;
     }
@@ -224,4 +216,122 @@ public final class ThreadedStreamConsumer
             return false;
         }
     }
+
+    /**
+     * This synchronization helper mostly avoids the locks.
+     * If the queue size has reached zero or {@code maxQueueSize} then the threads are locked (parked/unparked).
+     * The thread instance T1 is reader (see the class "Pumper") and T2 is the writer (see the method "handleEvent").
+     *
+     * @param <T> element type in the queue
+     */
+    static class QueueSynchronizer<T>
+    {
+        private final SyncT1 t1 = new SyncT1();
+        private final SyncT2 t2 = new SyncT2();
+        private final ConcurrentLinkedDeque<T> queue = new ConcurrentLinkedDeque<>();
+        private final AtomicInteger queueSize = new AtomicInteger();
+        private final int maxQueueSize;
+        private final T stopItemMarker;
+
+        QueueSynchronizer( int maxQueueSize, T stopItemMarker )
+        {
+            this.maxQueueSize = maxQueueSize;
+            this.stopItemMarker = stopItemMarker;
+        }
+
+        private class SyncT1 extends AbstractQueuedSynchronizer
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected int tryAcquireShared( int arg )
+            {
+                return queueSize.get() == 0 ? -1 : 1;
+            }
+
+            @Override
+            protected boolean tryReleaseShared( int arg )
+            {
+                return true;
+            }
+
+            void waitIfZero() throws InterruptedException
+            {
+                acquireSharedInterruptibly( 1 );
+            }
+
+            void release()
+            {
+                releaseShared( 0 );
+            }
+        }
+
+        private class SyncT2 extends AbstractQueuedSynchronizer
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected int tryAcquireShared( int arg )
+            {
+                return queueSize.get() < maxQueueSize ? 1 : -1;
+            }
+
+            @Override
+            protected boolean tryReleaseShared( int arg )
+            {
+                return true;
+            }
+
+            void awaitMax()
+            {
+                acquireShared( 1 );
+            }
+
+            void tryRelease()
+            {
+                if ( queueSize.get() == 0 )
+                {
+                    releaseShared( 0 );
+                }
+            }
+        }
+
+        void markStopped()
+        {
+            addNext( stopItemMarker );
+        }
+
+        void pushNext( T t )
+        {
+            t2.awaitMax();
+            addNext( t );
+        }
+
+        T awaitNext() throws InterruptedException
+        {
+            t2.tryRelease();
+            t1.waitIfZero();
+            queueSize.decrementAndGet();
+            return queue.pollFirst();
+        }
+
+        boolean isEmptyQueue()
+        {
+            return queue.isEmpty();
+        }
+
+        void clearQueue()
+        {
+            queue.clear();
+        }
+
+        private void addNext( T t )
+        {
+            queue.addLast( t );
+            if ( queueSize.getAndIncrement() == 0 )
+            {
+                t1.release();
+            }
+        }
+    }
 }
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleOutputFileReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleOutputFileReporter.java
index c5b8f22..729a072 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleOutputFileReporter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/ConsoleOutputFileReporter.java
@@ -30,7 +30,6 @@ import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.util.concurrent.atomic.AtomicStampedReference;
-import java.util.concurrent.locks.ReentrantLock;
 
 import static org.apache.maven.plugin.surefire.report.FileReporter.getReportFile;
 import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
@@ -58,8 +57,6 @@ public class ConsoleOutputFileReporter
     private final AtomicStampedReference<FilterOutputStream> fileOutputStream =
             new AtomicStampedReference<>( null, OPEN );
 
-    private final ReentrantLock lock = new ReentrantLock();
-
     private volatile String reportEntryName;
 
     public ConsoleOutputFileReporter( File reportsDirectory, String reportNameSuffix, boolean usePhrasedFileName,
@@ -73,17 +70,9 @@ public class ConsoleOutputFileReporter
     }
 
     @Override
-    public void testSetStarting( TestSetReportEntry reportEntry )
+    public synchronized void testSetStarting( TestSetReportEntry reportEntry )
     {
-        lock.lock();
-        try
-        {
-            closeNullReportFile( reportEntry );
-        }
-        finally
-        {
-            lock.unlock();
-        }
+        closeNullReportFile( reportEntry );
     }
 
     @Override
@@ -92,24 +81,15 @@ public class ConsoleOutputFileReporter
     }
 
     @Override
-    public void close()
+    public synchronized void close()
     {
         // The close() method is called in main Thread T2.
-        lock.lock();
-        try
-        {
-            closeReportFile();
-        }
-        finally
-        {
-            lock.unlock();
-        }
+        closeReportFile();
     }
 
     @Override
-    public void writeTestOutput( String output, boolean newLine, boolean stdout )
+    public synchronized void writeTestOutput( String output, boolean newLine, boolean stdout )
     {
-        lock.lock();
         try
         {
             // This method is called in single thread T1 per fork JVM (see ThreadedStreamConsumer).
@@ -148,10 +128,6 @@ public class ConsoleOutputFileReporter
             // todo use UncheckedIOException in Java 8
             throw new RuntimeException( e );
         }
-        finally
-        {
-            lock.unlock();
-        }
     }
 
     @SuppressWarnings( "checkstyle:emptyblock" )
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumerTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumerTest.java
new file mode 100644
index 0000000..a859c76
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/output/ThreadedStreamConsumerTest.java
@@ -0,0 +1,151 @@
+package org.apache.maven.plugin.surefire.booterclient.output;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer.QueueSynchronizer;
+import org.apache.maven.surefire.api.event.Event;
+import org.apache.maven.surefire.api.event.StandardStreamOutWithNewLineEvent;
+import org.apache.maven.surefire.extensions.EventHandler;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.FutureTask;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ *
+ */
+@SuppressWarnings( "checkstyle:magicnumber" )
+public class ThreadedStreamConsumerTest
+{
+    @Test
+    public void testQueueSynchronizer() throws Exception
+    {
+        final CountDownLatch countDown = new CountDownLatch( 5_000_000 );
+        final QueueSynchronizer<Integer> sync = new QueueSynchronizer<>(  8 * 1024, null );
+
+        Thread t = new Thread()
+        {
+            @Override
+            public void run()
+            {
+                while ( true )
+                {
+                    try
+                    {
+                        sync.awaitNext();
+                        countDown.countDown();
+                    }
+                    catch ( InterruptedException e )
+                    {
+                        throw new IllegalStateException( e );
+                    }
+                }
+            }
+        };
+        t.setDaemon( true );
+        t.start();
+
+        SECONDS.sleep( 1 );
+        System.gc();
+        SECONDS.sleep( 2 );
+
+        long t1 = System.currentTimeMillis();
+
+        for ( int i = 0; i < 5_000_000; i++ )
+        {
+            sync.pushNext( i );
+        }
+
+        assertThat( countDown.await( 3L, SECONDS ) )
+            .isTrue();
+
+        long t2 = System.currentTimeMillis();
+        System.out.println( ( t2 - t1 ) + " millis in testQueueSynchronizer()" );
+    }
+
+    @Test
+    public void testThreadedStreamConsumer() throws Exception
+    {
+        final CountDownLatch countDown = new CountDownLatch( 5_000_000 );
+        EventHandler<Event> handler = new EventHandler<Event>()
+        {
+            @Override
+            public void handleEvent( @Nonnull Event event )
+            {
+                countDown.countDown();
+            }
+        };
+
+        ThreadedStreamConsumer streamConsumer = new ThreadedStreamConsumer( handler );
+
+        SECONDS.sleep( 1 );
+        System.gc();
+        SECONDS.sleep( 2 );
+
+        long t1 = System.currentTimeMillis();
+
+        Event event = new StandardStreamOutWithNewLineEvent( NORMAL_RUN, "" );
+        for ( int i = 0; i < 5_000_000; i++ )
+        {
+            streamConsumer.handleEvent( event );
+        }
+
+        assertThat( countDown.await( 3L, SECONDS ) )
+            .isTrue();
+
+        long t2 = System.currentTimeMillis();
+        System.out.println( ( t2 - t1 ) + " millis in testThreadedStreamConsumer()" );
+
+        streamConsumer.close();
+    }
+
+    @Test
+    public void test3() throws Exception
+    {
+        final QueueSynchronizer<String> sync = new QueueSynchronizer<>( 2, null );
+        sync.pushNext( "1" );
+        sync.pushNext( "2" );
+        String s1 = sync.awaitNext();
+        String s2 = sync.awaitNext();
+        assertThat( s1 ).isEqualTo( "1" );
+        assertThat( s2 ).isEqualTo( "2" );
+        FutureTask<Void> future = new FutureTask<>( new Callable<Void>()
+        {
+            @Override
+            public Void call() throws Exception
+            {
+                sync.awaitNext();
+                return null;
+            }
+        } );
+        Thread t = new Thread( future );
+        t.setDaemon( true );
+        t.start();
+        SECONDS.sleep( 3L );
+        assertThat( t.getState() )
+            .isEqualTo( Thread.State.WAITING );
+    }
+}
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 36425ed..9770f8a 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
@@ -41,6 +41,7 @@ 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.booterclient.output.ThreadedStreamConsumerTest;
 import org.apache.maven.plugin.surefire.extensions.ConsoleOutputReporterTest;
 import org.apache.maven.plugin.surefire.extensions.E2ETest;
 import org.apache.maven.plugin.surefire.extensions.ForkedProcessEventNotifierTest;
@@ -112,6 +113,7 @@ public class JUnit4SuiteTest extends TestCase
         suite.addTest( new JUnit4TestAdapter( ForkChannelTest.class ) );
         suite.addTest( new JUnit4TestAdapter( StreamFeederTest.class ) );
         suite.addTest( new JUnit4TestAdapter( E2ETest.class ) );
+        suite.addTest( new JUnit4TestAdapter( ThreadedStreamConsumerTest.class ) );
         return suite;
     }
 }