This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

Bug 197504 - StreamPool.NotifyOutputStream doesn't close on exception in wrapped output stream
Summary: StreamPool.NotifyOutputStream doesn't close on exception in wrapped output st...
Status: RESOLVED FIXED
Alias: None
Product: platform
Classification: Unclassified
Component: Filesystems (show other bugs)
Version: 7.0
Hardware: All All
: P3 normal (vote)
Assignee: Jaroslav Tulach
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-04-06 18:42 UTC by jsmith5and5
Modified: 2011-04-15 08:40 UTC (History)
0 users

See Also:
Issue Type: DEFECT
Exception Reporter:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description jsmith5and5 2011-04-06 18:42:45 UTC
Overview: When the editor is saving a file if the outputstream to the file system throws an IOException on close, the wrapping outstream doesn't close itself. The next save attempt fails because there is an existing "unclosed" outputstream.

Steps to Reproduce: I had implemented my own AbstractFilesystem but it may be easier to briefly hack the existing implementation of one of the main filesystems to throw an IOException on the close method of the OutputStream returned from AbstractFileSystem.Info.outputStream, for example in LocalFileSystem:

    protected OutputStream outputStream(final String name) throws java.io.IOException {
        boolean returnHacked = false;
        if (returnHacked) { // put breakpoint here and change value in debugger when ready
            return new OutputStream() {

                @Override
                public void write(int b) throws IOException {
                }
                
                @Override
                public void close() throws IOException {
                    throw new IOException("Always thrown.");
                }
                
            };
        }
        File f = getFile(name);
        if (!f.exists()) {
            f.getParentFile().mkdirs();
        }
        OutputStream retVal = new BufferedOutputStream(new FileOutputStream(f));

        // workaround for #42624
        if (Utilities.isMac()) {
            retVal = getOutputStreamForMac42624(retVal, name);
        }

        return retVal;
    }

Now load a file into the editor, make a change, and attempt to save it. You'll see the exception thrown and handled as expected. Everything is good. Then try to save the file a second time. Now you'll get a java.lang.InterruptedException thrown from org.openide.filesystems.StreamPool.waitForOutputStreamsClosed because of the outputstream from the first attempt.


Actual results: Second save doesn't even attempt to write because it sees that an existing outstream is not closed.

Expected results: Expected the second attempt to actually try to save the file anew.

Build Date & Platform: NB 7.0RC1, Ubuntu 10.04 though this isn't really applicable for this bug.

Additional Information:

Relevant code points are:

org.openide.filesystems.StreamPool.NotifyOutputStream.close
org.openide.text.CloneableEditorSupport.SaveAsReader.run

Here is another description (longer) of the problem from the message I sent to the mailing list:

http://forums.netbeans.org/topic37952.html

Here are the relevant stack traces.

First attempt stack trace:


SEVERE [org.openide.actions.SaveAction]

msg
Caused: java.io.IOException
   at mycompany.MyOutputStream.close(MyOutputStream.java:87)
   at java.io.FilterOutputStream.close(FilterOutputStream.java:143)
   at org.openide.filesystems.StreamPool$NotifyOutputStream.close(StreamPool.java:382)
   at java.io.FilterOutputStream.close(FilterOutputStream.java:143)
   at org.openide.text.CloneableEditorSupport$1SaveAsReader.run(CloneableEditorSupport.java:1044)
   at org.netbeans.editor.BaseDocument.render(BaseDocument.java:1422)
   at org.openide.text.CloneableEditorSupport.saveDocument(CloneableEditorSupport.java:1097)
   at org.openide.text.DataEditorSupport.superSaveDoc(DataEditorSupport.java:571)
   at org.openide.text.DataEditorSupport$SaveImpl.run(DataEditorSupport.java:1270)
   at org.openide.filesystems.EventControl.runAtomicAction(EventControl.java:125)
   at org.openide.filesystems.FileSystem.runAtomicAction(FileSystem.java:566)
   at org.openide.filesystems.FileUtil.runAtomicAction(FileUtil.java:572)
   at org.openide.text.DataEditorSupport.saveDocument(DataEditorSupport.java:567)
   at org.openide.text.SimpleES$1.save(SimpleES.java:74)
[catch] at org.openide.actions.SaveAction.performAction(SaveAction.java:130)
   at org.openide.actions.SaveAction.performAction(SaveAction.java:100)
   at org.openide.actions.SaveAction$Delegate.actionPerformed(SaveAction.java:247)
   at org.openide.awt.ContextAction$Performer.actionPerformed(ContextAction.java:231)
   at org.openide.awt.ContextManager.actionPerformed(ContextManager.java:240)
   at org.openide.awt.ContextAction.actionPerformed(ContextAction.java:109)
   at org.openide.util.actions.ActionInvoker$1.run(ActionInvoker.java:93)
   at org.openide.util.actions.ActionInvoker.doPerformAction(ActionInvoker.java:116)
   at org.openide.util.actions.ActionInvoker.invokeAction(ActionInvoker.java:99)
   at org.openide.awt.GeneralAction$DelegateAction.actionPerformed(GeneralAction.java:219)
   at org.openide.windows.TopComponent.processKeyBinding(TopComponent.java:1141)
   at javax.swing.JComponent.processKeyBindings(JComponent.java:2897)
   at javax.swing.JComponent.processKeyEvent(JComponent.java:2814)
   at java.awt.Component.processEvent(Component.java:6040)
   at java.awt.Container.processEvent(Container.java:2041)
   at java.awt.Component.dispatchEventImpl(Component.java:4630)
   at java.awt.Container.dispatchEventImpl(Container.java:2099)
   at java.awt.Component.dispatchEvent(Component.java:4460)
   at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1850)
   at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:712)
   at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:990)
   at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:855)
   at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:676)
   at java.awt.Component.dispatchEventImpl(Component.java:4502)
   at java.awt.Container.dispatchEventImpl(Container.java:2099)
   at java.awt.Window.dispatchEventImpl(Window.java:2478)
   at java.awt.Component.dispatchEvent(Component.java:4460)
   at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
   at org.netbeans.core.TimableEventQueue.dispatchEvent(TimableEventQueue.java:148)
   at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
   at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
   at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
   at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
   at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
   at java.awt.EventDispatchThread.run(EventDispatchThread.java:122) 


Second attempt stack trace:

WARNING [org.netbeans.core.TimableEventQueue]: too much time in AWT thread null
INFO [org.openide.filesystems.StreamPool]
java.lang.InterruptedException: 25a43bb604aba8a9e3a1edc2eb3825956d31e63b/83cd85ed3a4e34ac16dc373b8d44a15ab2a6b8a2
   at org.openide.filesystems.StreamPool.waitForOutputStreamsClosed(StreamPool.java:249)
[catch] at org.openide.filesystems.StreamPool.createOutputStream(StreamPool.java:138)
   at org.openide.filesystems.AbstractFileObject.getOutputStream(AbstractFileObject.java:244)
   at org.openide.filesystems.AbstractFileObject.getOutputStream(AbstractFileObject.java:226)
   at org.openide.text.DataEditorSupport$Env.outputStream(DataEditorSupport.java:861)
   at org.openide.text.CloneableEditorSupport$1SaveAsReader.run(CloneableEditorSupport.java:1041)
   at org.netbeans.editor.BaseDocument.render(BaseDocument.java:1422)
   at org.openide.text.CloneableEditorSupport.saveDocument(CloneableEditorSupport.java:1097)
   at org.openide.text.DataEditorSupport.superSaveDoc(DataEditorSupport.java:571)
   at org.openide.text.DataEditorSupport$SaveImpl.run(DataEditorSupport.java:1270)
   at org.openide.filesystems.EventControl.runAtomicAction(EventControl.java:125)
   at org.openide.filesystems.FileSystem.runAtomicAction(FileSystem.java:566)
   at org.openide.filesystems.FileUtil.runAtomicAction(FileUtil.java:572)
   at org.openide.text.DataEditorSupport.saveDocument(DataEditorSupport.java:567)
   at org.openide.text.SimpleES$1.save(SimpleES.java:74)
   at org.openide.actions.SaveAction.performAction(SaveAction.java:130)
   at org.openide.actions.SaveAction.performAction(SaveAction.java:100)
   at org.openide.actions.SaveAction$Delegate.actionPerformed(SaveAction.java:247)
   at org.openide.awt.ContextAction$Performer.actionPerformed(ContextAction.java:231)
   at org.openide.awt.ContextManager.actionPerformed(ContextManager.java:240)
   at org.openide.awt.ContextAction.actionPerformed(ContextAction.java:109)
   at org.openide.util.actions.ActionInvoker$1.run(ActionInvoker.java:93)
   at org.openide.util.actions.ActionInvoker.doPerformAction(ActionInvoker.java:116)
   at org.openide.util.actions.ActionInvoker.invokeAction(ActionInvoker.java:99)
   at org.openide.awt.GeneralAction$DelegateAction.actionPerformed(GeneralAction.java:219)
   at org.openide.windows.TopComponent.processKeyBinding(TopComponent.java:1141)
   at javax.swing.JComponent.processKeyBindings(JComponent.java:2897)
   at javax.swing.JComponent.processKeyEvent(JComponent.java:2814)
   at java.awt.Component.processEvent(Component.java:6040)
   at java.awt.Container.processEvent(Container.java:2041)
   at java.awt.Component.dispatchEventImpl(Component.java:4630)
   at java.awt.Container.dispatchEventImpl(Container.java:2099)
   at java.awt.Component.dispatchEvent(Component.java:4460)
   at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1850)
   at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:712)
   at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:990)
   at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:855)
   at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:676)
   at java.awt.Component.dispatchEventImpl(Component.java:4502)
   at java.awt.Container.dispatchEventImpl(Container.java:2099)
   at java.awt.Window.dispatchEventImpl(Window.java:2478)
   at java.awt.Component.dispatchEvent(Component.java:4460)
   at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
   at org.netbeans.core.TimableEventQueue.dispatchEvent(TimableEventQueue.java:148)
   at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
   at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
   at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
   at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
   at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
   at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
SEVERE [org.openide.actions.SaveAction]

msg
Caused: org.openide.filesystems.FileAlreadyLockedException: 25a43bb604aba8a9e3a1edc2eb3825956d31e63b/83cd85ed3a4e34ac16dc373b8d44a15ab2a6b8a2
   at org.openide.filesystems.StreamPool$2.write(StreamPool.java:154)
   at java.io.OutputStream.write(OutputStream.java:99)
   at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
   at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
   at java.io.FilterOutputStream.flush(FilterOutputStream.java:123)
   at org.openide.text.DataEditorSupport$2.close(DataEditorSupport.java:532)
   at sun.nio.cs.StreamEncoder.implClose(StreamEncoder.java:301)
   at sun.nio.cs.StreamEncoder.close(StreamEncoder.java:130)
   at java.io.OutputStreamWriter.close(OutputStreamWriter.java:216)
   at org.openide.text.DataEditorSupport.saveFromKitToStream(DataEditorSupport.java:539)
   at org.openide.text.CloneableEditorSupport$1SaveAsReader.run(CloneableEditorSupport.java:1042)
   at org.netbeans.editor.BaseDocument.render(BaseDocument.java:1422)
   at org.openide.text.CloneableEditorSupport.saveDocument(CloneableEditorSupport.java:1097)
   at org.openide.text.DataEditorSupport.superSaveDoc(DataEditorSupport.java:571)
   at org.openide.text.DataEditorSupport$SaveImpl.run(DataEditorSupport.java:1270)
   at org.openide.filesystems.EventControl.runAtomicAction(EventControl.java:125)
   at org.openide.filesystems.FileSystem.runAtomicAction(FileSystem.java:566)
   at org.openide.filesystems.FileUtil.runAtomicAction(FileUtil.java:572)
   at org.openide.text.DataEditorSupport.saveDocument(DataEditorSupport.java:567)
   at org.openide.text.SimpleES$1.save(SimpleES.java:74)
[catch] at org.openide.actions.SaveAction.performAction(SaveAction.java:130)
   at org.openide.actions.SaveAction.performAction(SaveAction.java:100)
   at org.openide.actions.SaveAction$Delegate.actionPerformed(SaveAction.java:247)
   at org.openide.awt.ContextAction$Performer.actionPerformed(ContextAction.java:231)
   at org.openide.awt.ContextManager.actionPerformed(ContextManager.java:240)
   at org.openide.awt.ContextAction.actionPerformed(ContextAction.java:109)
   at org.openide.util.actions.ActionInvoker$1.run(ActionInvoker.java:93)
   at org.openide.util.actions.ActionInvoker.doPerformAction(ActionInvoker.java:116)
   at org.openide.util.actions.ActionInvoker.invokeAction(ActionInvoker.java:99)
   at org.openide.awt.GeneralAction$DelegateAction.actionPerformed(GeneralAction.java:219)
   at org.openide.windows.TopComponent.processKeyBinding(TopComponent.java:1141)
   at javax.swing.JComponent.processKeyBindings(JComponent.java:2897)
   at javax.swing.JComponent.processKeyEvent(JComponent.java:2814)
   at java.awt.Component.processEvent(Component.java:6040)
   at java.awt.Container.processEvent(Container.java:2041)
   at java.awt.Component.dispatchEventImpl(Component.java:4630)
   at java.awt.Container.dispatchEventImpl(Container.java:2099)
   at java.awt.Component.dispatchEvent(Component.java:4460)
   at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1850)
   at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:712)
   at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:990)
   at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:855)
   at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:676)
   at java.awt.Component.dispatchEventImpl(Component.java:4502)
   at java.awt.Container.dispatchEventImpl(Container.java:2099)
   at java.awt.Window.dispatchEventImpl(Window.java:2478)
   at java.awt.Component.dispatchEvent(Component.java:4460)
   at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
   at org.netbeans.core.TimableEventQueue.dispatchEvent(TimableEventQueue.java:148)
   at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
   at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
   at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
   at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
   at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
   at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
There were some problems while saving myFile
Cause: 25a43bb604aba8a9e3a1edc2eb3825956d31e63b/83cd85ed3a4e34ac16dc373b8d44a15ab2a6b8a2
Comment 1 Jaroslav Tulach 2011-04-07 06:52:05 UTC
Can we assume the file is closed when wrapped close() throws an exception? That sounds like a philosophical question.
Comment 2 Jaroslav Tulach 2011-04-07 08:16:04 UTC
ergonomics#d87620969501
Comment 3 jsmith5and5 2011-04-07 13:40:21 UTC
Right. In general I don't think you can assume an OutputStream is closed if an exception is thrown. Unfortunately the java.io.OutputStream.close doesn't explicitly say that the contract is nullified if an IOException is thrown but I think this is to be assumed. It's reasonable to have an OutputStream which fails once to close due to a "brief" (brief being anything longer than the timeout in close) communication outage but that is able to successfully close the next try (I know most people don't code to this level of robustness, but that's another thing).

However, I would say for these purposes that you can assume the outputstream is "closed". As I understand the NetBeans Platform code around this (which I've only looked at briefly) is trying to be extra helpful by not writing to resources which perhaps are already being read from or written to. This is nice for it to do but ultimately it is really the job of the wrapped OutputStream to check this. In other words, I saw it as nice-to-have code that protects bad implementations from themselves.

Let's say the NPB code assumed the wrapped outputstream was closed even if it wasn't. I know that at least in the case of my implementation of filesystem (and in the case of writing to the OS filesystem because of the locks it keeps, etc.) that if a previous inputstream or outputstream to a file was currently in use, then the new outputstream would fail with an exception at least by the time of the call to read/write (if that was desired).

Sorry if that's a little long-winded. Just wanted to be clear about it.


Appendix: Javadoc for java.io.OutputStream.close

close

public void close()
           throws IOException

    Closes this output stream and releases any system resources associated with this stream. The general contract of close is that it closes the output stream. A closed stream cannot perform output operations and cannot be reopened.

    The close method of OutputStream does nothing.

    Specified by:
        close in interface Closeable

    Throws:
        IOException - if an I/O error occurs.
Comment 4 Quality Engineering 2011-04-15 08:40:41 UTC
Integrated into 'main-golden', will be available in build *201104150401* on http://bits.netbeans.org/dev/nightly/ (upload may still be in progress)
Changeset: http://hg.netbeans.org/main/rev/d87620969501
User: Jaroslav Tulach <jtulach@netbeans.org>
Log: #197504: Release the mutual exclusion lock when close is attempted, not only successfully finished