Reputation: 56
An EventHandler implementation X is attached to a Pane and listening for all MouseEvents. Of course X has a handle()
method which receives MouseEvents from the JavaFX Application Thread.
The Pane contains a Rectangle. When the Pane receives a MouseEvent.MOUSE_CLICK upon the Rectangle, X does two things:
Removes the Rectangle from the Pane, then immediately adds another one (this may cause additional events.
continues with some abitrary processing
Here's the question:
Is the processing in step 2 expected to finish prior to any further events being submitted to X via handle()
by the JavaFX Application Thread? Noting that Step 1 may trigger additional events!
Just looking for a yes or no responses. And the reasoning behind your answer would be good also !
I should add that there are no other threads of any kind at all involved anywhere, including in the "arbitrary processing" .
Edit:
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.
*
* To trigger the bug, run the application, then spend a second mouse over the
* little label in the upper left hand corner of the screen. 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.
*
* 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.
* 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}.
*
* <b>discussion</b><br></br>
* <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>
* Proof:
* 1) <code>debugCounter</code> starts at value 0 (zero).
* 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.
* 3) <code>debugCounter</code> is only decremented once, by 1 (one) and that
* is before the last call to {@link LabelEventHandler#showDebugInformation(String)}.
* 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)}
*
* End proof.
*
* 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.
*
* Some other possibilities other than the JavaFX Application Thread recursing
* into {@code handle()} need to be addressed.
* 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.
*
* So somehow the compiler is reordering the increment/decrement of
* {@code debugCounter} and the calls to {@code showDebugInformation}.
* But this would alter the correctness of the program, so this cannot be the case,
* or the compiler is making an error.
*
* Another is the fact that I/O is not instantaneous and can appear to standard
* output later than it actually was executed.
* 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.
* But this can't account for the value of {@code debugCounter}, so it can't
* be the reason "debugCounter is 1" appears in output.
*
* In fact we can make this recursive behaviour more obviously consequential
* to the correctness of the program. If {@code handle() } is being
* recursively re-entered, then we can force a
* {@link ConcurrentModificationException} on a {@link Collection}.
* 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.
*
* 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}.
*
* 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.
*
* 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>.
*
* 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.
*/
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) {
// debug can only every be 0 (zero) at this point
showDebugInformation("ENTERING");
debugCounter++;
if (mouseEvent.getEventType().equals(MouseEvent.MOUSE_PRESSED)
&& mouseEvent.isPrimaryButtonDown()) {
pane.getChildren().remove(label);
}
debugCounter--;
// debug can only every be 0 (zero) at this point
showDebugInformation("EXITING");
}
/**
* 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();
}
}
Upvotes: 2
Views: 230
Reputation: 1
I lost my previous login, but I am the OP of this thread. Just thought I would update it to report that the issue here is real, confirmed, and it has been given a bug ID at Oracle and you can reference it here:
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8211000
Thanks to everyone who engaged me on this! Hope it helps whoever else comes across it.
Upvotes: 0
Reputation: 11992
Is the processing in step 2 expected to finish prior to any further events being submitted to X via handle() by the JavaFX Application Thread?
Yes. So the JavaFX thread does everything sequentially. For example if you were to add a Thread.sleep
in your handle()
method, then the JavaFX thread will not do anythign till the sleep is finish. It does all it processing sequentially, which I guess is the defining element of a thread. It does not go off and process other events in parallel. This is very important in animations, because all this processing must happen before the JavaFX thread will calculate and display the next frame.
ADDENDUM:
Consider this- the removal of the Rectangle as a result of the MouseEvent posts a MouseEvent.MOUSE_EXIT event if the cursor is over the Rectangle because in the eyes of JavaFX, that is what just happened. That MouseEvent is both being generated on the JavaFX Application Thread and will be processed by it. Now here's the thing to think about. The JavaFX Application Thread can either follow through and that MOUSE_EXIT to X's handle() or continue dcoig arbitary processing,. Which does it do?
The MOUSE_CLICKED event would get processed first. After the thread is done processing all the triggered events, it draws to the screen. Once updating the screen is finished, it will then process any new triggered events, like the MOUSE_EXIT. For example lets say you create a Node that removes the Node on MOUSE_ENTERED then puts it back on MOUSE_EXIT. When you move your mouse over this Node it will flicker at the frame rate -- vs. going into an endless loop before updating the screen.
Upvotes: 3