Why does PrintStream.close() end up getting called twice?
Asked Answered
V

2

7

Somewhat to my surprise, the following code prints out "Close" twice. Running through the debugger, it seems MyPrintStream.close() calls super.close(), which ends up calling MyPrintStream.close() again.

    
import java.io.*;

public class PrintTest
{
    static class MyPrintStream extends PrintStream
    {
        MyPrintStream(OutputStream os)
        {
            super(os);
        }

        @Override
        public void close()
        {
            System.out.println("Close");
            super.close();
        }
    }

    public static void main(String[] args) throws IOException
    {
        PrintStream ps = new MyPrintStream(new FileOutputStream(File.createTempFile("temp", "file")));
        ps.println("Hello");
        ps.close();
    }
}

Why is this happening? Should I not be extending PrintStream?

Varnish answered 12/5, 2009 at 13:36 Comment(1)
This something a debugger is good for. Place a breakpoint in the close method and you should be able to see why it is called.Incubate
A
1

Take a look at PrintStream's source.

It has two references to the underlying Writer textOut and charOut, one character-base, and one text-based (whatever that means). Also, it inherits a third reference to the byte-based OutputStream, called out.

/**
 * Track both the text- and character-output streams, so that their buffers
 * can be flushed without flushing the entire stream.
 */
private BufferedWriter textOut;
private OutputStreamWriter charOut;

In the close() method it closes all of them (textOut is basically the same as charOut).

 private boolean closing = false; /* To avoid recursive closing */

/**
 * Close the stream.  This is done by flushing the stream and then closing
 * the underlying output stream.
 *
 * @see        java.io.OutputStream#close()
 */
public void close() {
synchronized (this) {
    if (! closing) {
    closing = true;
    try {
        textOut.close();
        out.close();
    }
    catch (IOException x) {
        trouble = true;
    }
    textOut = null;
    charOut = null;
    out = null;
    }
}
}

Now, the interesting part is that charOut contains a (wrapped) referenced to the PrintStream itself (note the init(new OutputStreamWriter(this)) in the constructor )

private void init(OutputStreamWriter osw) {
   this.charOut = osw;
   this.textOut = new BufferedWriter(osw);
}

/**
 * Create a new print stream.
 *
 * @param  out        The output stream to which values and objects will be
 *                    printed
 * @param  autoFlush  A boolean; if true, the output buffer will be flushed
 *                    whenever a byte array is written, one of the
 *                    <code>println</code> methods is invoked, or a newline
 *                    character or byte (<code>'\n'</code>) is written
 *
 * @see java.io.PrintWriter#PrintWriter(java.io.OutputStream, boolean)
 */
public PrintStream(OutputStream out, boolean autoFlush) {
this(autoFlush, out);
init(new OutputStreamWriter(this));
}

So, the call to close() will call charOut.close(), which in turn calls the original close() again, which is why we have the closing flag to cut short the infinite recursion.

Autobahn answered 12/5, 2009 at 22:41 Comment(0)
P
11

If you look at your code in a debugger and set a breakpoint in the close() method, it'll reveal the stacktraces of who is calling your close() method:

  1. your main method
  2. sun.nio.cs.StreamEncoder$CharsetSE.implClose() line 431

the complete stacktrace for the latter looks like:

PrintTest$MyPrintStream.close() line: 20    
sun.nio.cs.StreamEncoder$CharsetSE.implClose() line: 431 [local variables unavailable]  
sun.nio.cs.StreamEncoder$CharsetSE(sun.nio.cs.StreamEncoder).close() line: 160 [local variables unavailable]    
java.io.OutputStreamWriter.close() line: 222 [local variables unavailable]  
java.io.BufferedWriter.close() line: 250 [local variables unavailable]  
PrintTest$MyPrintStream(java.io.PrintStream).close() line: 307  
PrintTest$MyPrintStream.close() line: 20    
PrintTest.main(java.lang.String[]) line: 27 

Sadly though I can't tell why StreamEncoder would call back into your PrintStream though, as my IDE doesn't have a source attachment for sun.nio.cs.StreamEncoder :( This is JDK 6 btw, if that matters.

By the way, if you are asking this question because you noticed that custom code in your close() method is running twice, you should really be checking if this.closing. PrintStream.close() sets this to true (and the class's comments state /* To avoid recursive closing */ ).

Paraffin answered 12/5, 2009 at 13:47 Comment(4)
+1 for educating the user how to solve this by himself in the futureCzarist
The 'closing' instance variable is private to PrintStream, so I can't check it, though of course I can use my own.Varnish
There are a couple of classes in the jdk which do something similar. I believe it is because there are situations where classes A and B reference each other, and the user may have a reference to either A or B, and closing either of them should close the other. As mentioned, you should generally protect your close methods from multiple invocations (although recursive invocation is a more insidious and less expected situation).Copenhagen
Great answer. simonn - I think the suggestion was to add your own closing instance variable if you need it.Yalta
A
1

Take a look at PrintStream's source.

It has two references to the underlying Writer textOut and charOut, one character-base, and one text-based (whatever that means). Also, it inherits a third reference to the byte-based OutputStream, called out.

/**
 * Track both the text- and character-output streams, so that their buffers
 * can be flushed without flushing the entire stream.
 */
private BufferedWriter textOut;
private OutputStreamWriter charOut;

In the close() method it closes all of them (textOut is basically the same as charOut).

 private boolean closing = false; /* To avoid recursive closing */

/**
 * Close the stream.  This is done by flushing the stream and then closing
 * the underlying output stream.
 *
 * @see        java.io.OutputStream#close()
 */
public void close() {
synchronized (this) {
    if (! closing) {
    closing = true;
    try {
        textOut.close();
        out.close();
    }
    catch (IOException x) {
        trouble = true;
    }
    textOut = null;
    charOut = null;
    out = null;
    }
}
}

Now, the interesting part is that charOut contains a (wrapped) referenced to the PrintStream itself (note the init(new OutputStreamWriter(this)) in the constructor )

private void init(OutputStreamWriter osw) {
   this.charOut = osw;
   this.textOut = new BufferedWriter(osw);
}

/**
 * Create a new print stream.
 *
 * @param  out        The output stream to which values and objects will be
 *                    printed
 * @param  autoFlush  A boolean; if true, the output buffer will be flushed
 *                    whenever a byte array is written, one of the
 *                    <code>println</code> methods is invoked, or a newline
 *                    character or byte (<code>'\n'</code>) is written
 *
 * @see java.io.PrintWriter#PrintWriter(java.io.OutputStream, boolean)
 */
public PrintStream(OutputStream out, boolean autoFlush) {
this(autoFlush, out);
init(new OutputStreamWriter(this));
}

So, the call to close() will call charOut.close(), which in turn calls the original close() again, which is why we have the closing flag to cut short the infinite recursion.

Autobahn answered 12/5, 2009 at 22:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.