maven-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tibordig...@apache.org
Subject [maven-surefire] 02/02: [SUREFIRE-1506] Sporadic NullPointerException in ConsoleOutputFileReporter#close()
Date Fri, 30 Mar 2018 13:00:13 GMT
This is an automated email from the ASF dual-hosted git repository.

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

commit 63b2c36895ee003fcbf3336b9b470d0e001d076f
Author: Tibor17 <tibordigana@apache.org>
AuthorDate: Sun Mar 25 22:59:17 2018 +0200

    [SUREFIRE-1506] Sporadic NullPointerException in ConsoleOutputFileReporter#close()
---
 .../surefire/report/ConsoleOutputFileReporter.java | 131 +++++++++++++++------
 1 file changed, 95 insertions(+), 36 deletions(-)

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 84cbabe..b2a53b1 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
@@ -22,8 +22,10 @@ package org.apache.maven.plugin.surefire.report;
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicStampedReference;
+import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.maven.surefire.report.ReportEntry;
 
@@ -31,8 +33,6 @@ import static org.apache.maven.plugin.surefire.report.FileReporter.getReportFile
 
 /**
  * Surefire output consumer proxy that writes test output to a {@link java.io.File} for each
test suite.
- * <br>
- * This class is not threadsafe, but can be serially handed off from thread to thread.
  *
  * @author Kristian Rosenvold
  * @author Carlos Sanchez
@@ -40,13 +40,20 @@ import static org.apache.maven.plugin.surefire.report.FileReporter.getReportFile
 public class ConsoleOutputFileReporter
     implements TestcycleConsoleOutputReceiver
 {
-    private final File reportsDirectory;
+    private static final int STREAM_BUFFER_SIZE = 16 * 1024;
+    private static final int OPEN = 0;
+    private static final int CLOSED_TO_REOPEN = 1;
+    private static final int CLOSED = 2;
 
+    private final File reportsDirectory;
     private final String reportNameSuffix;
 
-    private String reportEntryName;
+    private final AtomicStampedReference<FilterOutputStream> fileOutputStream =
+            new AtomicStampedReference<FilterOutputStream>( null, OPEN );
 
-    private OutputStream fileOutputStream;
+    private final ReentrantLock lock = new ReentrantLock();
+
+    private volatile String reportEntryName;
 
     public ConsoleOutputFileReporter( File reportsDirectory, String reportNameSuffix )
     {
@@ -57,8 +64,15 @@ public class ConsoleOutputFileReporter
     @Override
     public void testSetStarting( ReportEntry reportEntry )
     {
-        close();
-        reportEntryName = reportEntry.getName();
+        lock.lock();
+        try
+        {
+            closeNullReportFile( reportEntry );
+        }
+        finally
+        {
+            lock.unlock();
+        }
     }
 
     @Override
@@ -67,53 +81,98 @@ public class ConsoleOutputFileReporter
     }
 
     @Override
-    @SuppressWarnings( "checkstyle:emptyblock" )
     public void close()
     {
-        if ( fileOutputStream != null )
+        // The close() method is called in main Thread T2.
+        lock.lock();
+        try
         {
-            //noinspection EmptyCatchBlock
-            try
-            {
-                fileOutputStream.flush();
-            }
-            catch ( IOException e )
-            {
-            }
-            finally
-            {
-                try
-                {
-                    fileOutputStream.close();
-                }
-                catch ( IOException ignored )
-                {
-                }
-            }
-            fileOutputStream = null;
+            closeReportFile();
+        }
+        finally
+        {
+            lock.unlock();
         }
     }
 
     @Override
     public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
     {
+        lock.lock();
         try
         {
-            if ( fileOutputStream == null )
+            // This method is called in single thread T1 per fork JVM (see ThreadedStreamConsumer).
+            // The close() method is called in main Thread T2.
+            int[] stamp = new int[1];
+            FilterOutputStream os = fileOutputStream.get( stamp );
+            if ( stamp[0] != 2 )
             {
-                if ( !reportsDirectory.exists() )
+                if ( os == null )
                 {
-                    //noinspection ResultOfMethodCallIgnored
-                    reportsDirectory.mkdirs();
+                    if ( !reportsDirectory.exists() )
+                    {
+                        //noinspection ResultOfMethodCallIgnored
+                        reportsDirectory.mkdirs();
+                    }
+                    File file = getReportFile( reportsDirectory, reportEntryName, reportNameSuffix,
"-output.txt" );
+                    os = new BufferedOutputStream( new FileOutputStream( file ), STREAM_BUFFER_SIZE
);
+                    fileOutputStream.set( os, OPEN );
                 }
-                File file = getReportFile( reportsDirectory, reportEntryName, reportNameSuffix,
"-output.txt" );
-                fileOutputStream = new BufferedOutputStream( new FileOutputStream( file ),
16 * 1024 );
+                os.write( buf, off, len );
             }
-            fileOutputStream.write( buf, off, len );
         }
         catch ( IOException e )
         {
             throw new RuntimeException( e );
         }
+        finally
+        {
+            lock.unlock();
+        }
+    }
+
+    @SuppressWarnings( "checkstyle:emptyblock" )
+    private void closeNullReportFile( ReportEntry reportEntry )
+    {
+        try
+        {
+            // close null-output.txt report file
+            close( true );
+        }
+        catch ( IOException ignored )
+        {
+        }
+        finally
+        {
+            // prepare <class>-output.txt report file
+            reportEntryName = reportEntry.getName();
+        }
+    }
+
+    @SuppressWarnings( "checkstyle:emptyblock" )
+    private void closeReportFile()
+    {
+        try
+        {
+            close( false );
+        }
+        catch ( IOException ignored )
+        {
+        }
+    }
+
+    private void close( boolean closeReattempt )
+            throws IOException
+    {
+        int[] stamp = new int[1];
+        FilterOutputStream os = fileOutputStream.get( stamp );
+        if ( stamp[0] != 2 )
+        {
+            fileOutputStream.set( null, closeReattempt ? CLOSED_TO_REOPEN : CLOSED );
+            if ( os != null && stamp[0] == OPEN )
+            {
+                os.close();
+            }
+        }
     }
 }

-- 
To stop receiving notification emails like this one, please contact
tibordigana@apache.org.

Mime
View raw message