JDK-8054565 : FilterOutputStream.close may throw IOException if called twice and underlying flush or close fails
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 8u11,9
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2014-08-08
  • Updated: 2017-07-25
  • Resolved: 2015-01-04
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9 b46Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :


A DESCRIPTION OF THE PROBLEM :
The change for Bug ID 7015589 included a change to FilterOutputStream so that it does not swallow exceptions when calling flush:

http://hg.openjdk.java.net/hsx/hotspot-rt/jdk/rev/759aa847dcaf

     public void close() throws IOException {
        try (OutputStream ostream = out) {
            flush();
         }
     }

The problem is that if close() is called a second time, flush() will be called on the underlying stream after it has already been closed.  Many underlying streams will throw an exception in this case.

The implementation of FilterOutputStream.close() should be changed so that it follows the contract of Closeable:

"Closes this stream and releases any system resources associated with it. If the stream is already closed then invoking this method has no effect."

One possible implementation idea is to introduce a member variable to track whether the stream has already been closed:

    public void close() throws IOException {
        if (!closed) {
        		closed = true;
            try (OutputStream ostream = out) {
                flush();
            }
        }
    }

See http://stackoverflow.com/questions/25175882/java-8-filteroutputstream-exception/

REGRESSION.  Last worked in version 7u67

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached test program

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No exception is thrown
ACTUAL -
Exception is thrown:

Exception in thread "main" java.io.IOException: Stream closed
	at FilterOutputStreamTest$MockBlobOutputStream.flush(FilterOutputStreamTest.java:24)
	at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:141)
	at java.io.FilterOutputStream.close(FilterOutputStream.java:158)
	at FilterOutputStreamTest.main(FilterOutputStreamTest.java:52)



REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

public class FilterOutputStreamTest
{
    /**
     * This stream simulates a OracleBlobOutputStream which will throw
     * an exception if flush is called after close.
     */
    static class MockBlobOutputStream extends ByteArrayOutputStream
    {
        private boolean closed;

        @Override
        public void flush() throws IOException
        {
            if (closed) {
                throw new IOException("Stream closed");
            }
        }

        @Override
        public void close() throws IOException
        {
            closed = true;
        }
    }

    public static void main( final String[] args ) throws Exception
    {
        try( InputStream bis = new ByteArrayInputStream( "Hello".getBytes() );
             OutputStream outStream = new MockBlobOutputStream();
             BufferedOutputStream bos = new BufferedOutputStream( outStream );
             DeflaterOutputStream deflaterStream = new DeflaterOutputStream(
                 bos, new Deflater( 3 ) ) )
        {
            int len = 0;
            final byte[] buf = new byte[1024];
            while ((len = bis.read(buf)) >= 0)
            {
                if ( len > 0 )
                {
                    deflaterStream.write(buf,0,len);
                }
            }
        }
    }
}
---------- END SOURCE ----------


Comments
java.io.FilterOutputStream.close specifies that it calls the `flush` method before closing the underlying stream. It is ambiguous if an implementation is required to call the `flush` method every time `close` is called. FilterOutputStream is Closeable, and the close method specifies; "If the stream is already closed then invoking this method has no effect." FilterOutputStream.close should clarify that `flush` is only called on the first call.
22-12-2014