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 30702

Summary: Stale reference to node causes code invocation after node.destroy()
Product: platform Reporter: Michael Ottati <ottati>
Component: NodesAssignee: t_h <t_h>
Status: RESOLVED WONTFIX    
Severity: blocker CC: jglick, ttran
Priority: P4 Keywords: API, THREAD
Version: 3.x   
Hardware: PC   
OS: Windows ME/2000   
Issue Type: DEFECT Exception Reporter:
Bug Depends on: 35833    
Bug Blocks:    
Attachments: Tar archive of test module which demonstrates the case.

Description Michael Ottati 2003-02-06 00:05:44 UTC
I am not sure if this is an execution bug or a 
documentation bug. The bug has to do with clarification of 
the contract for the Node.destroy() method.

This bug will provide a module which demonstrates the 
problem with an example. The bug is as follows:

1:  call node.destroy() on a given node.
2:  The result of step 1 is that the node is left in a 
state
    where it can not be called again.
3:  After event 2 has completed the  getIcon() method of 
the node is invoked.

In order to see this behavior, expand and mount the sample 
directory as a file system.
Execute the jar (assumes api support).
Open the node "openme"
Delete any of the inner nodes in openme and you will see 
the stack trace below at the end of this section of the 
report.

The interesting bits of code around this problem are:

	public synchronized void destroy() {
	    isDestroyed = true;
	    parent.dobj.removeChild(getName());
	    parent.addNotify();
	}

	public synchronized Image getIcon(int type) {
	    if (isDestroyed) {
		throw new RuntimeException("This node has 
been destroyed. Why are we asking for this icon?");
	    }
	    return super.getIcon(type);
	}

As can be seen in the code below, getIcon is called after 
the node has been destroyed.

This is obviously a contrived example. In the real code, 
from which this example was created from, the destroy 
method removes something from the data object that the 
getIcon code needs to read in order to provide the correct 
icon.

Questions:

1:  What should the contract be for Node.destroy
2:  Is it reasonable to assume that the node will not 
again be invoked by the infrastructure once destroyed?
3:  If the answer to question 2 is no, can the javadoc be 
updated appropriately to reflect this.

This appears to be a change in behavior since NetBeans 
3.3. The code from which the example was derrived worked 
fine under NetBeans 3.3.

---------------- Stack trace from getIcon called after 
destroy() has been called ---------




Annotation: Root C:\java\j2sdk1.4.1\jre\classes does not 
exist.
java.lang.RuntimeException: This node has been destroyed. 
Why are we asking for this icon?
        at childrentester.MyDataNode$MyChildNode.getIcon
(MyDataNode.java:73)
        at org.openide.nodes.FilterNode.getIcon
(FilterNode.java:362)
        at 
org.openide.explorer.view.NodeRenderer$Tree.getTreeCellRend
ererComponent(NodeRenderer.java:257)
        at 
org.openide.explorer.view.NodeRenderer.getTreeCellRendererC
omponent(NodeRenderer.java:103)
        at javax.swing.plaf.basic.BasicTreeUI.paintRow
(BasicTreeUI.java:1375)
        at javax.swing.plaf.basic.BasicTreeUI.paint
(BasicTreeUI.java:1171)
        at javax.swing.plaf.metal.MetalTreeUI.paint
(MetalTreeUI.java:143)
        at javax.swing.plaf.ComponentUI.update
(ComponentUI.java:142)
        at javax.swing.JComponent.paintComponent
(JComponent.java:541)
        at javax.swing.JComponent.paint
(JComponent.java:808)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JViewport.paint(JViewport.java:707)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at 
org.netbeans.core.windows.frames.CloseButtonTabbedPane.pain
t(CloseButtonTabbedPane.java:161)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JLayeredPane.paint
(JLayeredPane.java:552)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JComponent.paintWithOffscreenBuffer
(JComponent.java:4771)
        at javax.swing.JComponent.paintDoubleBuffered
(JComponent.java:4724)
        at javax.swing.JComponent._paintImmediately
(JComponent.java:4668)
        at javax.swing.JComponent.paintImmediately
(JComponent.java:4477)
        at javax.swing.RepaintManager.paintDirtyRegions
(RepaintManager.java:410)
[catch] at 
javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.
run(SystemEventQueueUtilities.java:117)
        at java.awt.event.InvocationEvent.dispatch
(InvocationEvent.java:178)
        at java.awt.EventQueue.dispatchEvent
(EventQueue.java:448)
        at 
java.awt.EventDispatchThread.pumpOneEventForHierarchy
(EventDispatchThread.java:197)
        at 
java.awt.EventDispatchThread.pumpEventsForHierarchy
(EventDispatchThread.java:150)
        at java.awt.EventDispatchThread.pumpEvents
(EventDispatchThread.java:144)
        at java.awt.EventDispatchThread.pumpEvents
(EventDispatchThread.java:136)
        at java.awt.EventDispatchThread.run
(EventDispatchThread.java:99)
Comment 1 Michael Ottati 2003-02-06 00:10:31 UTC
Created attachment 8818 [details]
Tar archive of test module which demonstrates the case.
Comment 2 Michael Ottati 2003-02-07 15:33:51 UTC
This is causing a bug in S1S. I have created a tracking 
bug in BugTraq 4814837.
Comment 3 Jesse Glick 2003-02-10 19:02:20 UTC
AFAIK this is just a doc bug - your getIcon must be written to
gracefully handle the case that the node has been destroyed. E.g.
return null or something. The effect of calling setKeys with a smaller
set of keys (i.e. of deleting the child node) is not likely to be
synchronous, though it should take effect immediately from the user
perspective. In the meantime, some AWT redraw might happen. If the
Nodes API worked exclusively in the AWT thread and did everything
synchronously then of course this would not happen, but I think it
uses a request processor for handling key changes.
Comment 4 Michael Ottati 2003-02-10 20:25:24 UTC
A couple of comments. 

Node.getIcon() does not provide for the possibility of 
null as a return value. The effect of doing so is a 
predictable null pointer exception elsewhere in the IDE.

For whatever reason, the behavior has changed going 
from "sierra" branch to the trunk. Once a node is 
destroyed, all of the state that it needs to represent 
itself is gone.

It is obvious that I am going to have to go back and redo 
code that worked to accomidate new behavior. Prior to 
doing so it would be nice to know some more things about 
the life cycle of Nodes, mostly pertaining to what is 
synchronus and what is not.

I guess this falls under the more general, and off 
discussed topic of what is the threading model for 
NetBeans.


-----Stack Trace of NPE if null is returned--------


java.lang.NullPointerException
        at javax.swing.ImageIcon.<init>(ImageIcon.java:161)
        at org.openide.explorer.view.NodeRenderer.getIcon
(NodeRenderer.java:191)
        at 
org.openide.explorer.view.NodeRenderer$Tree.getTreeCellRend
ererComponent(NodeRenderer.java:257)
        at 
org.openide.explorer.view.NodeRenderer.getTreeCellRendererC
omponent(NodeRenderer.java:103)
        at javax.swing.plaf.basic.BasicTreeUI.paintRow
(BasicTreeUI.java:1375)
        at javax.swing.plaf.basic.BasicTreeUI.paint
(BasicTreeUI.java:1171)
        at javax.swing.plaf.metal.MetalTreeUI.paint
(MetalTreeUI.java:143)
        at javax.swing.plaf.ComponentUI.update
(ComponentUI.java:142)
        at javax.swing.JComponent.paintComponent
(JComponent.java:541)
        at javax.swing.JComponent.paint
(JComponent.java:808)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JViewport.paint(JViewport.java:707)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at 
org.netbeans.core.windows.frames.CloseButtonTabbedPane.pain
t(CloseButtonTabbedPane.java:161)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JLayeredPane.paint
(JLayeredPane.java:552)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JComponent.paintChildren
(JComponent.java:647)
        at javax.swing.JComponent.paint
(JComponent.java:817)
        at javax.swing.JComponent.paintWithOffscreenBuffer
(JComponent.java:4771)
        at javax.swing.JComponent.paintDoubleBuffered
(JComponent.java:4724)
        at javax.swing.JComponent._paintImmediately
(JComponent.java:4668)
        at javax.swing.JComponent.paintImmediately
(JComponent.java:4477)
        at javax.swing.RepaintManager.paintDirtyRegions
(RepaintManager.java:410)
[catch] at 
javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.
run(SystemEventQueueUtilities.java:117)
        at java.awt.event.InvocationEvent.dispatch
(InvocationEvent.java:178)
        at java.awt.EventQueue.dispatchEvent
(EventQueue.java:448)
        at 
java.awt.EventDispatchThread.pumpOneEventForHierarchy
(EventDispatchThread.java:197)
        at 
java.awt.EventDispatchThread.pumpEventsForHierarchy
(EventDispatchThread.java:150)
        at java.awt.EventDispatchThread.pumpEvents
(EventDispatchThread.java:144)
        at java.awt.EventDispatchThread.pumpEvents
(EventDispatchThread.java:136)
        at java.awt.EventDispatchThread.run
(EventDispatchThread.java:99)
Comment 5 Jesse Glick 2003-02-10 23:24:31 UTC
Sorry, by "return null" I was speaking loosely; "return some arbitrary
dummy value legal according to the method contract". E.g. super.getIcon().

I don't think there is any documented contract as to when a node is
"really" destroyed. It may well be that the implementation behavior
was different in 3.3, but it was never correct to fail in a hard sense
if some method is called on a node after it is removed from a parent.
Code which was not robust in this sense may have worked only by luck
before.

There is no strongly specified threading model for nodes, other than
that node methods might be accessed from various threads, and that
Children.MUTEX can be used to lock hierarchy changes (though in
practice it is not necessary for module code to touch this mutex).
Timing behavior for Children.Keys etc. is not documented so no
assumptions can be made.

addNotify will be called when a Children object is expected to
generate its children (perhaps asynchronously), and removeNotify when
children are no longer needed.
Comment 6 _ ttran 2003-02-18 15:28:58 UTC
what is the status of this issue?  Thx
Comment 7 Michael Ottati 2003-02-19 02:09:19 UTC
For S1S, I have coded around this problem by 
coding "defensively" as was suggested earlier in this 
thread. BugTraq 4814837 has been closed.

If he effect of calling setKeys with a smaller
set of keys (i.e. of deleting the child node) is not 
likely to be synchronous, as Jesse indicates:

<QUOTE>
The effect of calling setKeys with a smaller
set of keys (i.e. of deleting the child node) is not 
likely to be
synchronous, though it should take effect immediately ....
</QUOTE>

then that should be documented as well. It should be safe 
to assume synchronous method execution unless otherwise 
documented.

As to the meaning of destroy, if Nodes are intended to be 
invoked after destroy, that certainly needs to be 
documented. Doing so seems to be analogous to using a 
DataObject after it has been invalidated. 


Comment 8 _ ttran 2003-02-19 10:32:38 UTC
So this is a API doc issue.  I lower prio to P3
Comment 9 Jesse Glick 2003-02-19 16:49:51 UTC
The analogy with invalidated data objects is not exact, but it is
instructive. Should consider whether visualizer impl can be changed in
such a way that it will not look up properties of a node (icon e.g.)
which has been posted for removal via setKeys but not yet removed...
perhaps some kind of better use of the mutex? I don't know.
Comment 10 Petr Hrebejk 2003-02-20 10:30:42 UTC
Will add notice to Node.destory Javadoc that the node needs to respond
with some reasonable temporary values even after the destory method is
called. 

The idea with VisualiserNode enhancement probably deserves own
enhancement in Explorer. However Nodes should be independent on
Explorer so the notice in destroy's Javadoc will still be valid.
Comment 11 Jesse Glick 2003-02-20 12:23:59 UTC
"However Nodes should be independent on Explorer so the notice in
destroy's Javadoc will still be valid." - not necessarily; if we could
in fact guarantee that the Explorer code never called methods on dead
nodes, then it would make sense to document in Node that no one may
call methods on a node after it is destroyed, and that node impls need
not write code to provide temporary dummy values.
Comment 12 Petr Nejedly 2004-01-09 09:44:08 UTC
Taking over the node bugs from phrebejk.
Comment 13 Jesse Glick 2004-01-09 18:07:21 UTC
Probably by fixed by patch in issue #35833.
Comment 14 Jesse Glick 2004-08-17 03:57:08 UTC
Probably P4; don't know of user-visible problems caused by this.
Comment 15 Petr Nejedly 2004-08-24 10:46:34 UTC
Agreed
Comment 16 Antonin Nebuzelsky 2008-02-08 15:01:48 UTC
Reassigning to new module owner Tomas Holy.
Comment 17 Quality Engineering 2009-12-21 04:37:29 UTC
This bug was reported against NetBeans IDE 6.0 or an older release, or against a non-maintained module. NetBeans team does not have enough resources to get to this issue, therefore we are closing the issue as a WONTFIX. If you are interested in providing a patch for this bug, please see our NetFIX guidelines for how to proceed. 

We apologize for any inconvenience.


Thank you.
The NetBeans Team
Comment 18 Jesse Glick 2009-12-21 13:27:45 UTC
Noting in Javadoc for destroy() that other methods may be called too: core-main #9fe397f79548
Comment 19 Quality Engineering 2009-12-22 23:42:37 UTC
Integrated into 'main-golden', will be available in build *200912230201* on http://bits.netbeans.org/dev/nightly/ (upload may still be in progress)
Changeset: http://hg.netbeans.org/main/rev/9fe397f79548
User: Jesse Glick <jglick@netbeans.org>
Log: Improving destroy() Javadoc. See also #30702.