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