JDK-6335274 : FilterOutputStream.close() silently ignores flush() exceptions
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 5.0
  • Priority: P3
  • Status: Closed
  • Resolution: Won't Fix
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2005-10-11
  • Updated: 2012-01-11
  • Resolved: 2005-10-14
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
Tested in 1.5.0_02-b09

ADDITIONAL OS VERSION INFORMATION :
Appears to be a problem in Java library, so applicable to all OSs.

A DESCRIPTION OF THE PROBLEM :
FilterOutputStream silently ignores exceptions when doing a flush on close.
This can hide IO problems such a  full disk.  The particular case where it got
me was using a ZipOutputStream over a BufferedOutputStream.


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.*;
import java.util.zip.*;
class  test
{
  static class LimitOutputStream extends OutputStream
  {
    public void write(int n) throws IOException
    {
      num++;
      check();
    }

    public void write(byte[] buf) throws IOException
    {
      num += buf.length;
      check();
    }
    
    public void write(byte[] buf, int off, int len) throws IOException
    {
      num += len;
      check();
    }

    void check() throws IOException
    {
      if (num > 80)
      {
        //System.out.println("Limit.check: " + num);
        //Thread.dumpStack();
        throw new IOException("all full!");
      }
    }
        
    int num;
  }
  
  public static void main(String[] args)
    throws Exception
  {
    ZipOutputStream out =
      new ZipOutputStream(
      new BufferedOutputStream(
      new LimitOutputStream()));
    out.putNextEntry(new ZipEntry("entry"));
    for(int i=0; i<10000; ++i)
      out.write(new byte[100]);
    out.closeEntry();
    // out.flush();  without this line the close appears to silently work
    out.close();
  }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
You must explicitly flush() before close() if want to have an underlying
exception raised.

Release Regression From : 1.4.2_09
The above release value was the last known release where this 
bug was known to work. Since then there has been a regression.

Comments
EVALUATION As I said in my previous evaluation, the implementations of FilterOutputStream.flush() and FOS.close() have not changed in many years. Any changes to them now would introduce potentially unpleasant side-effects in existing customer code. The default buffer size of BufferedOutputStream was modified in Tiger (jdk1.5) as a result of bug 4953311 to optimize for performance in common scenarios such as network writes. It is not possible to change this default back to 512. However there is a simple work-around to get the previous behaviour, simply construct a BufferedOutputStream with an explicit buffer size: ZipOutputStream out = new ZipOutputStream( new BufferedOutputStream( new LimitOutputStream(), 512)); Closing this issue as "will not fix".
2005-10-14

EVALUATION The root cause appears to be that the size of the byte array in BufferedOutputStream was changed between 1.4.2_09 and 1.5.0 from 512 bytes to 8192 bytes. In the example given, many bytes are written, but they are all 0. Using a ZipOutputStream, this compresses to somewhere between 512 and 8192 bytes. So in 1.4.2_09, the exception is caught under the call to ZipOutputStream.closeEntry, which does *not* involve FilterOutputStream.close (nor its flush method). The stack trace is: 1.4.2_09: ZOS.closeEntry DOS.deflate BOS.write BOS.flushBuffer LOS.write But in 1.5.0, with BOS having the larger buffer, BOS.flushBuffer is never called until ZOS.close is invoked, and then the stack trace is: 1.5.0: ZOS.close DOS.close FOS.close BOS.flush BOS.flushBuffer LOS.write Here, FOS.close is involved, and it silently ignores the client's exception. Attached test case can be used to show the problem, without ZipOutputStream being involved. Run it with an arg under 1.4.2_09, and see that check prints a stack trace from main's call to out.write, then run it under 1.5.0 and see that check prints the stack trace due to main's call to close. Rerouting back to I/O folks.
2005-10-13

EVALUATION This is not a problem with the implementation of FilterOutputStream.flush(). The current implementations of flush() and close() have been in place since at least April 1998. I believe that the problem is more likely to be caused by changes in java.util.zip. The reported problem goes away if I modify main() to avoid all zip code. The following is the key line for those changes: Old: ZipOutputStream out = new ZipOutputStream( new BufferedOutputStream( new LimitOutputStream())); New: ZipOutputStream out = new BufferedOutputStream( new LimitOutputStream()); Routing to the zip team for their evaluation.
2005-10-11