8054565: FilterOutputStream.close may throw IOException if called twice and underlying flush or close fails

Co-authored-by: Nathan Clement <nathan.a.clement@hotmail.com>
Reviewed-by: alanb, prappo
This commit is contained in:
Chris Hegarty 2015-01-04 19:32:46 +00:00
parent d708838bee
commit 916c818ec2
2 changed files with 63 additions and 16 deletions

View File

@ -48,6 +48,8 @@ class FilterOutputStream extends OutputStream {
*/ */
protected OutputStream out; protected OutputStream out;
private boolean closed;
/** /**
* Creates an output stream filter built on top of the specified * Creates an output stream filter built on top of the specified
* underlying output stream. * underlying output stream.
@ -144,9 +146,9 @@ class FilterOutputStream extends OutputStream {
* Closes this output stream and releases any system resources * Closes this output stream and releases any system resources
* associated with the stream. * associated with the stream.
* <p> * <p>
* The <code>close</code> method of <code>FilterOutputStream</code> * When not already closed, the {@code close} method of {@code
* calls its <code>flush</code> method, and then calls the * FilterOutputStream} calls its {@code flush} method, and then
* <code>close</code> method of its underlying output stream. * calls the {@code close} method of its underlying output stream.
* *
* @exception IOException if an I/O error occurs. * @exception IOException if an I/O error occurs.
* @see java.io.FilterOutputStream#flush() * @see java.io.FilterOutputStream#flush()
@ -154,6 +156,9 @@ class FilterOutputStream extends OutputStream {
*/ */
@SuppressWarnings("try") @SuppressWarnings("try")
public void close() throws IOException { public void close() throws IOException {
if (closed)
return;
closed = true;
try (OutputStream ostream = out) { try (OutputStream ostream = out) {
flush(); flush();
} }

View File

@ -25,7 +25,7 @@ import java.io.*;
/** /**
* @test * @test
* @bug 7015589 * @bug 7015589 8054565
* @summary Test that buffering streams are considered closed even when the * @summary Test that buffering streams are considered closed even when the
* close or flush from the underlying stream fails. * close or flush from the underlying stream fails.
*/ */
@ -165,7 +165,7 @@ public class FailingFlushAndClose {
} }
} }
static void testFailingClose(InputStream in) throws IOException { static InputStream testFailingClose(InputStream in) throws IOException {
System.out.println(in.getClass()); System.out.println(in.getClass());
in.read(new byte[100]); in.read(new byte[100]);
try { try {
@ -176,9 +176,10 @@ public class FailingFlushAndClose {
in.read(new byte[100]); in.read(new byte[100]);
fail("read did not fail"); fail("read did not fail");
} catch (IOException expected) { } } catch (IOException expected) { }
return in;
} }
static void testFailingClose(OutputStream out) throws IOException { static OutputStream testFailingClose(OutputStream out) throws IOException {
System.out.println(out.getClass()); System.out.println(out.getClass());
out.write(1); out.write(1);
try { try {
@ -190,9 +191,10 @@ public class FailingFlushAndClose {
if (!(out instanceof BufferedOutputStream)) if (!(out instanceof BufferedOutputStream))
fail("write did not fail"); fail("write did not fail");
} catch (IOException expected) { } } catch (IOException expected) { }
return out;
} }
static void testFailingFlush(OutputStream out) throws IOException { static OutputStream testFailingFlush(OutputStream out) throws IOException {
System.out.println(out.getClass()); System.out.println(out.getClass());
out.write(1); out.write(1);
try { try {
@ -206,9 +208,27 @@ public class FailingFlushAndClose {
fail("close did not fail"); fail("close did not fail");
} catch (IOException expected) { } } catch (IOException expected) { }
} }
return out;
} }
static void testFailingClose(Reader r) throws IOException { static void closeAgain(InputStream in) throws IOException {
// assert the given stream should already be closed.
try {
in.close();
} catch (IOException expected) {
fail("unexpected IOException from subsequent close");
}
}
static void closeAgain(OutputStream out) throws IOException {
// assert the given stream should already be closed.
try {
out.close();
} catch (IOException expected) {
fail("unexpected IOException from subsequent close");
}
}
static Reader testFailingClose(Reader r) throws IOException {
System.out.println(r.getClass()); System.out.println(r.getClass());
r.read(new char[100]); r.read(new char[100]);
try { try {
@ -219,9 +239,10 @@ public class FailingFlushAndClose {
r.read(new char[100]); r.read(new char[100]);
fail("read did not fail"); fail("read did not fail");
} catch (IOException expected) { } } catch (IOException expected) { }
return r;
} }
static void testFailingClose(Writer w) throws IOException { static Writer testFailingClose(Writer w) throws IOException {
System.out.println(w.getClass()); System.out.println(w.getClass());
w.write("message"); w.write("message");
try { try {
@ -232,9 +253,10 @@ public class FailingFlushAndClose {
w.write("another message"); w.write("another message");
fail("write did not fail"); fail("write did not fail");
} catch (IOException expected) { } } catch (IOException expected) { }
return w;
} }
static void testFailingFlush(Writer w) throws IOException { static Writer testFailingFlush(Writer w) throws IOException {
System.out.println(w.getClass()); System.out.println(w.getClass());
w.write("message"); w.write("message");
try { try {
@ -249,18 +271,38 @@ public class FailingFlushAndClose {
fail("close did not fail"); fail("close did not fail");
} catch (IOException expected) { } } catch (IOException expected) { }
} }
return w;
}
static Reader closeAgain(Reader r) throws IOException {
// assert the given stream should already be closed.
try {
r.close();
} catch (IOException expected) {
fail("unexpected IOException from subsequent close");
}
return r;
}
static Writer closeAgain(Writer w) throws IOException {
// assert the given stream should already be closed.
try {
w.close();
} catch (IOException expected) {
fail("unexpected IOException from subsequent close");
}
return w;
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
testFailingClose(new BufferedInputStream(new FailingCloseInputStream())); closeAgain(testFailingClose(new BufferedInputStream(new FailingCloseInputStream())));
testFailingClose(new BufferedOutputStream(new FailingCloseOutputStream())); closeAgain(testFailingClose(new BufferedOutputStream(new FailingCloseOutputStream())));
testFailingClose(new BufferedReader(new FailingCloseReader())); closeAgain(testFailingClose(new BufferedReader(new FailingCloseReader())));
testFailingClose(new BufferedWriter(new FailingCloseWriter())); closeAgain(testFailingClose(new BufferedWriter(new FailingCloseWriter())));
testFailingFlush(new BufferedOutputStream(new FailingFlushOutputStream())); closeAgain(testFailingFlush(new BufferedOutputStream(new FailingFlushOutputStream())));
testFailingFlush(new BufferedWriter(new FailingFlushWriter())); closeAgain(testFailingFlush(new BufferedWriter(new FailingFlushWriter())));
if (failed > 0) if (failed > 0)
throw new RuntimeException(failed + " test(s) failed - see log for details"); throw new RuntimeException(failed + " test(s) failed - see log for details");