JavaFX Application Thread is recursively re-entrant into Eventhandler handle() method under some circumstances

javafx at use.startmail.com javafx at use.startmail.com
Sun Sep 9 21:38:53 UTC 2018


I did not say it was another Thread. In fact, I said it was the Java 
Application Thread itself- being recursively re-entrant.

Specifically, at the time the MOUSE_EXITED_TARGET event is generated, 
the Java Application Thread has a choice- continue proocessing the 
EventHandler#handle method which it is in, or respond to 
the MOUSE_EXITED_TARGET event which was just generated. Suprisingly, it 
does the second , completes that call to handle, then the stack pops 
and it nfinishes the previous invocation of handle. It's not about 
another Thread.


I don't   refer to the stack trace as proof   that the thread is 
reentrant. I do say the contrapositve of that, which is, if the thread 
trace didn't show the bug  then the bug  wouldn't  exist. But I don't 
assert the stack trace as a proof.

The proof is  the proof  I gave in the javadoc, and that is the value 
of one the static int debugCounter acheives in the debug statements can 
only be explained if  recursive re-entrancy has cocurred, which  is 
true. You need to address this. 

Also, this is merely the most barebones implementation which reveals 
this bug. This is why I didn't want to post it, because you have to 
work out what cannot be the case before evaluating the argument. What 
cannot be the case is  debugCounter cannot have value 1 in the debug 
output method. 

The link provides a more consequential /  more easily understood bug / 
less dismissable bug- a ConcurrentModificationException  against a Set 
which the EventHandler is iterating through. That CME happens not 
because two threads are operating on the Set but because one thread , 
the JAva Application Thread is modifying the Set while it is also 
iterating through it. 

 


 
On Sunday, September 9, 2018 at 4:04 PM, John Hendrikx 
<hjohn at 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 at 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 at 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 at 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 !


More information about the openjfx-dev mailing list