[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openjdk-openjfx-dev
Subject:    Re: JavaFX Application Thread is recursively re-entrant into Eventhandler handle() method under some
From:       javafx () use ! startmail ! com
Date:       2018-09-09 21:58:16
Message-ID: e649e9e663092fdc1437b8cb403679c0.startmail () startmail ! com
[Download RAW message or body]

I am reading your stack trace but I defintely never mentioned the 
double invocation to handle that I see there are evidencing anything. 
The issue is the value of debugCounter at a two certain moments in the 
application - in the two calls to showDebugInformation.  

Although the proof that I am right is contained in the value 
debugCounter in the method showDebugInformation in the current program, 
if you go to the link in my previous post and download   and run the 
Complex version of this, you can more easily see the departure of the 
JavaApplicationThread from the instance of handle which is on the stack 
- and at a method which is not handle (as it is in this one) followed 
by   it's recursive re-entry to handle , completion of that 
recursive-handle's execution and re-entry and then completion into the 
previous invocation of handle. It's is completely obvious in the output 
of that Applcation.  

That application also throws the ConcurrentModificationException which 
is no way an artifact or misreading of a stacktrace.  

However, what is at issue in this program is the value of debugCounter 
in  showDebugInformation.

Cheers.  
  
On Sunday, September 9, 2018 at 4:04 PM, John Hendrikx 
<hjohn@xs4all.nl> wrote:
  
> I see nothing special in the stack trace.
> 
> When you remove the component, a new MouseEvent *must* trigger
> (MouseEvent.EXITED) as it always needs to match with 
> MouseEvent.ENTERED.
> 
> So, the call to 'remove' triggers a new event, which gets handled by 
> the
> same handler.   It is indeed entered recursively, but in a normal
> fashion.   This has nothing to do with another thread or compiler 
> bugs.
> 
> Don't be confused by the double "handle" lines in the stacktrace. 
> This
> happens when methods are overriden (the line number is 1).
> 
> There are two relevant lines in this trace:
> 
> LabelEventHandler.handle(LabelEventHandler.java:95)
> 
> ...where Remove is called, which triggers the recursive call later 
> on:
> 
> LabelEventHandler.handle(LabelEventHandler.java:88)
> 
> ... for the MouseEvent.EXITED event.
> 
> The full stack trace is this:
> 
> at
> bareBonesJavaFXBugExample.LabelEventHandler.handle(LabelEventHandler.java:88)
> at
> bareBonesJavaFXBugExample.LabelEventHandler.handle(LabelEventHandler.java:1)
> at
> javafx.base/com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
>  at
> javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
>  at
> javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
>  at
> javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
>  at
> javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
>  at
> javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
>  at
> javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
>  at
> javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
>  at
> javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
>  at
> javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
> at 
> javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
> at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
> at 
> javafx.base/com.sun.javafx.event.EventQueue.fire(EventQueue.java:48)
> at
> javafx.graphics/javafx.scene.Scene$MouseHandler.handleNodeRemoval(Scene.java:3717)
> at
> javafx.graphics/javafx.scene.Scene$MouseHandler.access$7800(Scene.java:3604)
> at 
> javafx.graphics/javafx.scene.Scene.generateMouseExited(Scene.java:3601)
> at 
> javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:613)
> at
> javafx.base/com.sun.javafx.collections.VetoableListDecorator.remove(VetoableListDecorator.java:329)
>  at
> javafx.base/com.sun.javafx.collections.VetoableListDecorator.remove(VetoableListDecorator.java:221)
>  at
> bareBonesJavaFXBugExample.LabelEventHandler.handle(LabelEventHandler.java:95)
> at
> bareBonesJavaFXBugExample.LabelEventHandler.handle(LabelEventHandler.java:1)
> at
> javafx.base/com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
>  
> (... rest cut off as it is not needed ... )
> 
> --John
> 
> On 09/09/2018 19:05, javafx@use.startmail.com wrote:
> > Hi All,
> > I spent some time refactoring the program which displays this bug. 
> > It's
> > now small enough to share the source in an email, but it is very 
> > very
> > dense and the proof of bug, one   specific line to standard I/O, 
> > requires
> > the source code to be read and understood in order to see the bug. 
> > As I
> > said previously, more comprehensible and user-friendly versions of 
> > this
> > program are   available at . The javadoc is more copious, the bug is
> > manifested as an exception and the side effect of the bug are more
> > consequential.
> > 
> > This brief version cnosists of just two classes. Here is the JavaFX
> > Application class:
> > 
> > ***************************************************************************
> > 
> > package bareBonesJavaFXBugExample;
> > 
> > 
> > import javafx.application.Application;
> > import javafx.scene.Scene;
> > import javafx.scene.control.Label;
> > import javafx.scene.input.MouseEvent;
> > import javafx.scene.layout.Pane;
> > import javafx.stage.Stage;
> > 
> > 
> > 
> > /**
> > * An {@link Application} with one {@link Pane} containing one 
> > {@link
> > Label}. The {@link Label} has a single {@link
> > javafx.event.EventHandler}, {@link LabelEventHandler} which 
> > processes
> > all {@link MouseEvent}s the {@link Label} receives.
> > * <p></p>
> > * To trigger the bug, run the application, then spend a second 
> > mouse
> > over the little label in the upper left hand corner of the scrren. 
> > You
> > will see output to standard I/O. Then, click the label, which will 
> > then
> > disppear. Check the I/O for Strings ending in debugCounter is 1. 
> > <p></p>
> > * What that String means and how it proves that the JavaFX 
> > Application
> > Thread has become reentrant is explained in the javadoc of {@link
> > LabelEventHandler}.
> > */
> > public class JavaFXAnomalyBareBonesApplication extends Application
> > {
> > public void start(Stage primaryStage)
> > {
> > 
> > Pane mainPane = new Pane();
> > mainPane.setMinHeight(800);
> > mainPane.setMinWidth(800);
> > 
> > Label label = new Label(" this is quite a bug !!!!");
> > 
> > LabelEventHandler labelEventHandler = new
> > LabelEventHandler(mainPane, label);
> > label.addEventHandler(MouseEvent.ANY, labelEventHandler);
> > 
> > mainPane.getChildren().add(label);
> > 
> > 
> > Scene scene = new Scene(mainPane);
> > primaryStage.setScene(scene);
> > primaryStage.show();
> > 
> > }
> > 
> > 
> > 
> > /**
> > * The entry point of application.
> > *
> > * @param args
> > *             the input arguments
> > */
> > public static void main(String[] args)
> > {
> > 
> > launch(args);
> > }
> > 
> > 
> > 
> > }
> > 
> > 
> > ***************************************************************************
> > 
> > and here is its only dependency, the EventListener class. I included
> > enough javadoc to have the program makes sense. :
> > 
> > ***************************************************************************
> > 
> > package bareBonesJavaFXBugExample;
> > 
> > 
> > 
> > import javafx.event.Event;
> > import javafx.event.EventHandler;
> > import javafx.scene.control.Label;
> > import javafx.scene.input.MouseEvent;
> > import javafx.scene.layout.Pane;
> > 
> > import java.util.Collection;
> > import java.util.ConcurrentModificationException;
> > 
> > /**
> > * An {@link EventHandler} implementation for {@link MouseEvent}s.
> > <p></p> This implementation's {@link EventHandler#handle(Event)} 
> > shows
> > the relevant debug information to standard output before and after
> > removing the member {@link #label} from the {@link #pane}.
> > * <p></p>
> > * <b>discussion</b><br></br>
> > * <P></P>
> > * Users should first satisfy themselves that the value of {@link
> > LabelEventHandler#debugCounter} can only be non-zero, in fact 1 
> > (one) in
> > the method {@link LabelEventHandler#showDebugInformation(String)} if 
> > the
> > method {@link LabelEventHandler#handle(MouseEvent)}   has been 
> > re-entered
> > recursively, that is, before a previous invocation of {@link
> > LabelEventHandler#handle(MouseEvent)} has returned.
> > * <p></p>
> > * Proof: <p></p> 1) <code>debugCounter</code> starts at value 0 
> > (zero).
> > <p></p> 2) <code>debugCounter</code> is only incremented once, by 1
> > (one), and that is after the first call to {@link
> > LabelEventHandler#showDebugInformation(String)} has returned.<p></p> 
> > 3)
> > <code>debugCounter</code> is only decremented once, by 1 (one) and 
> > that
> > is before the last call to {@link
> > LabelEventHandler#showDebugInformation(String)}.<p></p> 4) however, 
> > because
> > * <code>debugCounter</code> is a class variable ( it's static), if
> > handle() is recurvsively re-entered then it's value can be 1 (one) 
> > when
> > the re-entrant Thread executes {@link
> > LabelEventHandler#showDebugInformation(String)}
> > * <p></p>
> > * End proof.
> > * <p></p>
> > * <p></p>
> > * <p>
> > * The output of this method to standard I/O is volumnious but 
> > searching
> > the output for the exact String "debugCounter is 1" will immediately
> > show the {@link LabelEventHandler#handle(MouseEvent)} method to have
> > been recursively entered. <p></p>
> > * Some other possibilities other than the JavaFX Application Thread
> > recursing into {@code handle()} need to be addressed. <p></p> One is 
> > the
> > fact that the compiler is free to reorder statements if it can
> > * prove that such a reordering would have no effect on the 
> > program's
> > correctness.
> > * <br></br>
> > * So somehow the compiler is reordering the increment/decrement of
> > {@code   debugCounter} and the calls to {@code    
> > showDebugInformation}.
> > <br></br> But this would alter the correctness of the program, so 
> > this
> > cannot be the case, or the compiler is making an error.<P></P>
> > * <p>
> > * <p>
> > * Another is the fact that I/O is not instantaneous and can appear 
> > to
> > standard output later than it actually was executed. <br></br> This 
> > is
> > something often seen in debug stack traces, where the output is 
> > broken
> > up   or interleaved by the output of the stack trace even though the 
> > two
> > sets of statments, i/o and stack trace i/o, were strictly ordered in
> > execution. <br></br> But this can't account for the value of {@code
> > debugCounter}, so it can't
> > * be the reason "debugCounter is 1" appears in output.<p></p> In 
> > fact
> > we can make this recursive behaviour more obviously consequential to 
> > the
> > correctness of the program. <p></p> If {@code    handle() } is being
> > recursively re-entered, then we can force a {@link
> > ConcurrentModificationException} on a {@link Collection}.   <br></br> 
> > If
> > we try to invoke {@link Collection#add(Object)} to a {@link 
> > Collection}
> > while it is being iterated through, then a
> > * {@link ConcurrentModificationException} will be thrown.<p></p> If 
> > we
> > re-write this program slightly to first add or remove to or from a
> > {@link Collection} then iterate through that {@link Collection} 
> > within
> > the scope of   execution of {@code    handle()}, <em>and</em> {@code
> > handle()} is being recursively invoked, then we may see a {@link
> > ConcurrentModificationException}.
> > * <p></p>
> > * Two other instances of this same basic program exist at the link
> > provided. They are named {@link 
> > JavaFXAnomalySimpleVersionApplication}
> > and {@link JavaFXAnomalyComplexVersionApplication} which is written 
> > to
> > throw a {@link ConcurrentModificationException} when the JavaFX
> > Application Thread becomes reentrant.
> > * <p></p>
> > * I also have a screen grab (not included here) of the stack trace 
> > at a
> > specific moment <code>handle()/code> is being invoked, and it can
> > clearly be seen that the previous executing line was within the 
> > scope of
> > execution of the previous invocation of <code>handle()</code>.
> > * <p></p>
> > * In the .zip file at the link there is a readme.txt. In that file. 
> > I
> > present the two lines of code which need to be added, and where they
> > need to be added,   so as to generate the same stack trace showing 
> > the
> > same thing.
> > *
> > *
> > * </p>
> > */
> > public class LabelEventHandler implements EventHandler<MouseEvent>
> > {
> > /**
> > * a counter which acts as a recursion detector. If {@link
> > #handle(MouseEvent)} is never recursively invoked by the JavaFX
> > Application Thread, then it's value will never be other than 0 
> > (zero) in
> > {@link #showDebugInformation(String)}.
> > */
> > private static int debugCounter;
> > 
> > /**
> > * The {@link Label} which will disappear when clicked. This 
> > causes a
> > MOUSE_EXITED_TARGET event top be fired and that in turn causes the
> > JavaFX Event Dispatch Thread to recurse into this class's {@link
> > #handle(MouseEvent)}
> > */
> > private Label label;
> > /**
> > * The {@link Pane} which contains the {@link Label}. The {@link
> > Label} is removed from this {@link Pane}.
> > */
> > private final Pane pane;
> > 
> > 
> > 
> > /**
> > * Assign the values to the members {@link Pane} and {@link Label}
> > */
> > public LabelEventHandler(Pane pane, Label label)
> > {
> > 
> > this.pane = pane;
> > this.label = label;
> > }
> > 
> > 
> > 
> > /**
> > * Causes the member {@link #label} to be removed as a child of 
> > the
> > member {@link #pane}.
> > *
> > *
> > * @param mouseEvent
> > *             the {@link MouseEvent} received from the JavaFX 
> > Application
> > Thread from the {@link Label} which this {@link EventHandler} is
> > listening to.
> > */
> > @Override
> > public void handle(MouseEvent mouseEvent)
> > {
> > 
> > showDebugInformation("ENTERING");// debug can only every be 0 
> > (zero)
> > at this point
> > debugCounter++;
> > 
> > 
> > if (mouseEvent.getEventType().equals(MouseEvent.MOUSE_PRESSED) 
> > &&
> > mouseEvent.isPrimaryButtonDown())
> > {
> > pane.getChildren().remove(label);
> > }
> > 
> > debugCounter--;
> > showDebugInformation("EXITING");// debug can only every be 0 
> > (zero)
> > at this point
> > }
> > 
> > 
> > 
> > /**
> > * Displays two values to standard output. The first is a {@link
> > String}   indicating whether the {@link
> > LabelEventHandler#handle(MouseEvent)} method is being entered or 
> > exited
> > and the second is the value of {@link 
> > LabelEventHandler#debugCounter} at
> > the time this method is executed.
> > *
> > * @param enterOrExit
> > *             the string ENTERING or EXITING reflecting the point   at
> > which this method was invoked by {@link
> > LabelEventHandler#handle(MouseEvent)}.
> > */
> > private void showDebugInformation(String enterOrExit)
> > {
> > 
> > System.out.println();
> > System.out.print(enterOrExit + " method handle");
> > System.out.print(" and debugCounter is " + debugCounter);
> > System.out.println();
> > }
> > 
> > 
> > 
> > 
> > 
> > }
> > 
> > 
> > 
> > *******************************************************************
> > 
> > Just cut and pasting these two into files named by   their 
> > respective
> > Java class names, then   placing those   files into a folder
> > named bareBonesJavaFXBugExample is all it should   take to make this 
> > work.
> > 
> > Unless I get contrary   feedback, I will file this as a bug after I 
> > run
> > it against the most recent releases of the JavaFX.
> > 
> > Either way I am interested in feedback from the community.
> > 
> > Cheers !
> > 
> > 
> > 
> > 
> > On Saturday, September 8, 2018 at 8:02 AM, Kevin Rushforth
> > <kevin.rushforth@oracle.com> wrote:
> > 
> > > I am not aware of such a bug. If you have a test program, then you 
> > > can
> > > file a bug here:
> > > 
> > > https://bugreport.java.com/
> > > 
> > > -- Kevin
> > > 
> > > 
> > > On 9/7/2018 5:37 PM, javafx@use.startmail.com wrote:
> > > > Hi,
> > > > 
> > > > I have a couple of very small apps (3 small classes in one case 
> > > > and 5
> > > > in another)   which demonstrate that, under some circumstances, the
> > > > JavaFX Application Thread will recursively re-enter
> > > > EventHandler#handle().
> > > > 
> > > > So this means that it is already in handle (and calls therefrom) 
> > > > and
> > > > will, in some situations not complete that   processing (thus 
> > > > exiting
> > > > handle) before it reappears in the same instance of EventHandler's
> > > > handle method again. So this is true recursion.
> > > > 
> > > > I actually don't know if this is expected behavior or not. No one 
> > > > I've
> > > > talked to expected it; the general understanding is the JavaFX
> > > > Application Thread (processing) is specifically single-threaded 
> > > > and
> > > > also that it will defintily complete one invocation of handle() 
> > > > before
> > > > beginning another one.
> > > > 
> > > > I have to say that there is NO other Thread   in play here, at 
> > > > least no
> > > > other Thread my applications create (what's going on 
> > > > QuantumToolKit
> > > > may be a different story.)
> > > > 
> > > > The material upshot of this is it can lead to apparent   program
> > > > incorrectness if the dev believes that it's not the case, and 100% 
> > > > of
> > > > devs I've talked to think it's not possible.
> > > > 
> > > > I am happy to post or attach the classes or modules as requested 
> > > > but
> > > > first I wanted to check to see if in fact this is already known to 
> > > > be
> > > > true and is in fact   expected behavior, in which case it's a 
> > > > non-issue
> > > > and just a subtlety people are not aware of.
> > > > 
> > > > Thank you so much !


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic