JDK-8065890 : Two calls of close method on an output stream cause errors with some VFS (JimFS)
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 8u20
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: windows_7
  • CPU: x86
  • Submitted: 2014-06-29
  • Updated: 2018-09-11
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.
Other
tbdUnresolved
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_20-ea"
Java(TM) SE Runtime Environment (build 1.8.0_20-ea-b17)
Java HotSpot(TM) Client VM (build 25.20-b17, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
When you call javadoc with a com.sun.tools.javac.nio.JavacPathFileManager
for sending generating documentation to a Path and not a File (JimFS is a virtual filesystem in memory implementing NIO.2 provider API), you have a bug because JimFS is stricter than java.io and some copied files (text files) can be closed twice.

After the first close, the second close begin by calling flush on the OutputStream, but the OutputStream is already closed by first close then you have an exception on the flush call with JimFS.

Javadoc of java.io.OutputStream is not really clear on throwing or not an IOException when calling flush on an already closed stream, but it is not even clear for write: javadoc of some write methods throw clearly in this case, but not all e.g. void write(byte[] b).
 
In this case, source of bug is then not really clear (JimFS excessively strict or javadoc calling close twice on same OutputStream).

But only one of the two methods (binary or text) for copying is really used, then only one close would be logic (in my opinion), then I send a bug report.


ERROR MESSAGES/STACK TRACES THAT OCCUR :
	at com.google.common.jimfs.JimfsOutputStream.checkNotClosed(JimfsOutputStream.java:100)
	at com.google.common.jimfs.JimfsOutputStream.flush(JimfsOutputStream.java:94)
	at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:141)
	at java.io.FilterOutputStream.close(FilterOutputStream.java:158)
	at com.sun.tools.doclets.internal.toolkit.util.DocFile.copyResource(DocFile.java:196)
	at com.sun.tools.doclets.formats.html.HtmlDoclet.generateOtherFiles(HtmlDoclet.java:156)
	at com.sun.tools.doclets.internal.toolkit.AbstractDoclet.startGeneration(AbstractDoclet.java:144)
	at com.sun.tools.doclets.internal.toolkit.AbstractDoclet.start(AbstractDoclet.java:82)
	at com.sun.tools.doclets.formats.html.HtmlDoclet.start(HtmlDoclet.java:80)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at com.sun.tools.javadoc.DocletInvoker.invoke(DocletInvoker.java:310)
	at com.sun.tools.javadoc.DocletInvoker.start(DocletInvoker.java:189)
	at com.sun.tools.javadoc.Start.parseAndExecute(Start.java:366)
	at com.sun.tools.javadoc.Start.begin(Start.java:219)
	at com.sun.tools.javadoc.Start.begin(Start.java:212)
	at com.sun.tools.javadoc.api.JavadocTaskImpl.call(JavadocTaskImpl.java:79)
[...]

REPRODUCIBILITY :
This bug can be reproduced always.

CUSTOMER SUBMITTED WORKAROUND :
There is two workarounds possible for solving the problem: remove the throwing of IOException in JimFS from flush method when the stream is already closed or invoke close method only one time in javadoc (detailed under):

Change in code of com.sun.tools.doclets.internal.toolkit.util.DocFile.copyResource method to have try {} finally{} around each copy method (bytes or String) and not nested try{} finally{}.

// From http://hg.openjdk.java.net/jdk8u/jdk8u/langtools/file/20bab46f4db6/src/share/classes/com/sun/tools/doclets/internal/toolkit/util/DocFile.java

    public void copyResource(DocPath resource, boolean overwrite, boolean replaceNewLine) {
        if (exists() && !overwrite)
            return;

        try {
            InputStream in = Configuration.class.getResourceAsStream(resource.getPath());
            if (in == null)
                return;

            OutputStream out = openOutputStream();
            try {
                if (!replaceNewLine) {
                    byte[] buf = new byte[2048];
                    int n;
                    while((n = in.read(buf))>0) out.write(buf,0,n);
                } else {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    BufferedWriter writer;
                    if (configuration.docencoding == null) {
                        writer = new BufferedWriter(new OutputStreamWriter(out));
                    } else {
                        writer = new BufferedWriter(new OutputStreamWriter(out,
                                configuration.docencoding));
                    }
                    try {
                        String line;
                        while ((line = reader.readLine()) != null) {
                            writer.write(line);
                            writer.write(DocletConstants.NL);
                        }
                    } finally {
                        reader.close();
                        writer.close();
                    }
                }
            } finally {
                in.close();
                out.close();
            }
        } catch (IOException e) {
            e.printStackTrace(System.err);
            throw new DocletAbortException(e);
        }
    }

// This method need to be replaced by following code
	public void copyResource(DocPath resource, boolean overwrite,
			boolean replaceNewLine) {
		if (exists() && !overwrite) {
			return;
		}

		try {
			InputStream in = Configuration.class.getResourceAsStream(resource
					.getPath());
			if (in == null) {
				return;
			}

			OutputStream out = openOutputStream();
			if (!replaceNewLine) {
				try {
					byte[] buf = new byte[2048];
					int n;
					while ((n = in.read(buf)) > 0) {
						out.write(buf, 0, n);
					}
				} finally {
					in.close();
					out.close();
				}
			} else {
				BufferedReader reader = new BufferedReader(new InputStreamReader(in));
				BufferedWriter writer;
				if (this.configuration.docencoding == null) {
					writer = new BufferedWriter(new OutputStreamWriter(out));
				} else {
					writer = new BufferedWriter(new OutputStreamWriter(out,
							this.configuration.docencoding));
				}
				try {
					String line;
					while ((line = reader.readLine()) != null) {
						writer.write(line);
						writer.write(DocletConstants.NL);
					}
				} finally {
					reader.close();
					writer.close();
				}
			}
		} catch (IOException e) {
			e.printStackTrace(System.err);
			throw new DocletAbortException(e);
		}
	}

// And it works.


Comments
Is this a duplicate of JDK-8054565?
03-12-2014

The second call to close() should not hurt, as specified in the doc: http://docs.oracle.com/javase/8/docs/api/java/io/Closeable.html "If the stream is already closed then invoking this method has no effect."
03-07-2014