Thanks Kevin.
I will paste it all in this email. I have essentially three versions.
Two are so compressed it's hard for people to get what the issue is.
Since in those programs the proof only takes the form of the value of a
class variable, and that proof is demonstrated only by
System.out.printlns of that variable's value, it's understandable that
the significance of what's being output could easily be missed or
misinterpreted/ dismissed etc.
The other one is a proper demo application which throws a
ConcurrentModificationException which can't be so easily misunderstood
or dismissed, but it has multiple *very small , very well documented*
classes. You can't read the documentation and not get at what's being
shown (and still call yourself a developer LOL...), but you have to
read the javadoc.
Note: don't shoot from the hip based on a cursory examination of the
output or stack trace (like I did LOL). I have probably already
considered your alternate explanation, things from overridden methods
to the confusion about how and why ConcurrentModificationExceptions
are thrown to the (non) presence of multiple class loaders etc etc. It
took me real time to even entertain the idea that this was not a subtle
programming mistake but instead a bug in JavaFX. I can't avoid writing
these so that you have to read the javadoc - you just have to read the
javadoc.
My experience tells me the brief versions were not easy to understand
so I'll post the bigger version here. All but one or two of these
classes should be easy, one-glance classes for most everyone here and
the others are also very easy, with brief methods and anyway
thoroughly javadoced.:
OK:
1) A Receiver receives a mouse event.
***********************************************************************
package javaApplicationThreadCuriosityComplex;
import javafx.scene.input.MouseEvent;
public interface Receiver
{
void receiveEvent(MouseEvent event);
}
**************************************************************************
A do-nothing receiver receives the mouse event and does nothing
**************************************************************************
package javaApplicationThreadCuriosityComplex;
import javafx.scene.input.MouseEvent;
/**
* A {@link Receiver} implementation which literally does nothing
except receive the {@link MouseEvent} in {@link
#receiveEvent(MouseEvent)}, as defined in {@link Receiver}.<p></p>
* Exists in order that we can create many instances of a {@link
Receiver} implementation, where the mere existence and not the
functionality of the implementation is of any consequence to the
program. <p></p>
* See {@link PaneEventHandlerExceptionThrower} for details.
* <p></p>
* To satisfy yourself that these objects are being invoked,
uncomment-out the line in {@link #receiveEvent(MouseEvent)}, which will
print "Hello" to standard output.
*/
public class DoNothingReceiver implements Receiver
{
@Override
public void receiveEvent(MouseEvent event)
{
//System.out.println("Hello");
}
}
****************************************************************************************************
A rectangle drawing Receiver implementation. Very simple class; long
only because it's written to be transparent in its behavior.
If the received mouse event is a certain type (arbitrarily selected for
ease of use in the application - mouse pressed on the primary button)
then it just removes the sole Rectangle from the application's sole
Pane, if such a Rectangle is there.
In any case, it next immediately creates a new Rectangle, sizes it,
changes its color, positions it and adds it to the same Pane.
The effect is the Rectangle either appears for the first time, or
appears to change color.
That's it.
****************************************************************************************************
package javaApplicationThreadCuriosityComplex;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import java.util.List;
/**
*
* This object receives {@link MouseEvent}s from the {@link
javafx.event.EventHandler}.<p></p>
* If the event is the primary button of the mouse being pressed, this
object removes the member {@link RectangleDrawingReceiver#rectangle}
from the list of the {@link Pane}'s children if that {@link Rectangle}
is present in that list. <p></p>
* It then assigns a new instance of a {@link Rectangle} to the member
{@link RectangleDrawingReceiver#rectangle}.<p></p>
* Finally it adds that member to the list of the {@link Pane}'s
children.
* */
public class RectangleDrawingReceiver implements Receiver
{
/**
* The {@link Rectangle} which will appear after the Mouse is pressed
for the first time and be replaced on subsequent MOUSE_PRESSED events.
*/
private Rectangle rectangle;
/**
* boolean value which is reversed each time the primamry button of
the mouse is pressed and a new {@link Rectangle } appears.
*/
private boolean doDrawRectangleRed =true;
/**
* No-arg constrcutor added for clarity.
*/
public RectangleDrawingReceiver()
{
}
/**
* If the signal to add and remove the {@link Rectangle} is received,
then this method invokes {@link
RectangleDrawingReceiver#addAndRemoveRectangle(Pane)}. Otherwise
returns without doing anything.
* @param mouseEvent the {@link MouseEvent} received from the {@link
PaneEventHandlerExceptionThrower}
*/
@Override
public void receiveEvent(MouseEvent mouseEvent)
{
if (isAddAndRemoveRectangleSignal(mouseEvent))
{
Pane pane = (Pane) mouseEvent.getSource();
addAndRemoveRectangle(pane);
}
}
/**
* Define the specific Mouse gesture, MOUSE_PRESSED and primary
button down, which will cause this object to possibly remove, then
defintely add a {@link Rectangle} to its list of children.
* @param mouseEvent the {@link MouseEvent} which may or may not be
the trigger to remove and add a {@link Rectangle}
* @return true iff the primary button of the mouse is pressed.
*/
private boolean isAddAndRemoveRectangleSignal(MouseEvent mouseEvent)
{
return mouseEvent.getEventType().equals(MouseEvent.MOUSE_PRESSED)
&& mouseEvent.isPrimaryButtonDown();
}
/**
* Remove the {@link Rectangle} from the {@link Pane}, if it is
there. In either case, add a new {@link Rectangle} of pre-determined
size to the {@link Pane} at a pre-determined location.
* @param pane the {@link Pane} which may or may not currently have a
{@link Rectangle} as a child.
*/
private void addAndRemoveRectangle(Pane pane)
{
pane.getChildren().remove(rectangle);
reassignRectangle();
pane.getChildren().add(rectangle);
}
/**
* Create a completely new instance of the {@link Rectangle} with the
same size and location but with the opposite {@link Color} either
{@link Color#RED} or {@link Color#BLUE}.
*/
private void reassignRectangle()
{
rectangle = new Rectangle();
sizeAndPositionRectangle();
setRectangleColor();
}
/**
* Establish the size and position of the {@link Rectangle}
*/
private void sizeAndPositionRectangle()
{
rectangle.setX(200);
rectangle.setY(200);
rectangle.setWidth(200);
rectangle.setHeight(200);
}
/**
* To make it apparent that the rectangle is being changed, we change
its color every other time
*/
private void setRectangleColor()
{
if (doDrawRectangleRed)
{
rectangle.setFill(Color.RED);
doDrawRectangleRed= false;
}
else
{
rectangle.setFill(Color.BLUE);
doDrawRectangleRed= true;
}
}
}
**********************************************************************************************************************
The meat of the processing loop. This generates and displays the bug.
This is an EventHandler which gets attached to a the Application's sole
Pane . It handles all MouseEvents which occur on the Pane.
In its constructor, creates a List of 100 do nothing receivers and one
rectangle drawing receiver.
For each received mouse event, it iterates through a List of Receivers
reserveReceiver and transfers them into a Set of Receivers,
activeReceivers.
Then goes through that Set and invokes each one's receive method.
This ensures this method first adds to activeReceivers and then when
that is completely done, iterates over activeReceivers. If there
aren't two threads *then given the way this is written*, there will
be no problem. A ConcurrentModicationException can be created on one
Thread, I am aware.
That's it.
Because this method is entered into recursively, as it goes through its
member activeReceivers in the method sendEvent it throws a
ConcurrentModificationException.
As a secondary proof, this class keeps a static int , recursiveDepth,
whose value can only be 1 *in the debug output methods*. (elsewhere it
does assume a value of 1) in the event the JavaFX Application Thread
has recursed into this class's handle().
****You should absolutely satisfy yourself that under no circumstances
should the output of the debug methods, as this program is written,
show recursiveDepth to be other than 0 (zero) . You should convince
yourself of this before running this program****
In fact, if you search the resultant (copious LOL) output for the
words stackTraceElement to find the point at which the exception is
thrown, you will see just above it the output produced from the debug
methods which are invoked just upon entering and just before exiting
of handle itself , reporting the impossible event that the value of
recursiveDepth is 1. This is also when the
ConcurrentModificationException is thrown.
Those two independent output events are always paired because they
are not, in fact independent. The Application Thread is recursively
re-entrant at that point.
********************************************************************************************************************
package javaApplicationThreadCuriosityComplex;
import com.sun.javafx.tk.quantum.QuantumToolkit;
import
javaApplicationThreadCuriositySimple.JavaFXAnomalySimpleVersionApplication;
import javaApplicationThreadCuriositySimple.PaneEventHandler;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import java.util.*;
import java.util.function.Supplier;
/**
*
* This has two {@link List}s of {@link Receiver}s and transfers the
contents of one {@link List} to the other for the sole purpose of
invoking {@link Collection#add(Object)} within the scope of this
method's {@link #handle(MouseEvent)}<p></p>
* This invocation demonstrates the bug by throwing a {@link
ConcurrentModificationException}.<p></p>
*
*
* With enough elements in {@link #activeReceivers}, a {@link
ConcurrentModificationException} is reliably generated, proving that
the recursive re-entry of the JavaFX Application Thread and that this
re-entry is deterimentally consequential to program correctness.
<p></p>
* We catch any such {@link Exception} and display its
details.<p></p>
* </p>
*
*/
public class PaneEventExceptionGeneratingHandler implements
EventHandler<MouseEvent>
{
/**
* Class level variable which will only ever be 0 (zero) in the
methods {@link #showEnterDebugInformation(int, String, boolean)} and
{@link #showExitDebugInformation(int, String, boolean)} if the JavaFX
Application Thread does not invoke {@link #handle(MouseEvent)}
recursively and will be 1 (one) otherwise.
*/
private static int recursiveDepth;
/**
* a {@link List} of {@link Receiver}s which will have its contents
transferred into {@link #activeReceivers} in order to provoke a {@link
ConcurrentModificationException}.
*/
private Set<Receiver> reserveReceivers = new HashSet<>();
/**
* a {@link Set} of {@link Receiver}s which will have the contents of
{@link #reserveReceivers} transferred into it order to provoke a {@link
ConcurrentModificationException}.
*/
private List<Receiver> activeReceivers = new ArrayList<>();
/**
* No-arg constructor which populates a {@link List } of {@link
Receiver}s 101 elements long. The first 100 elements are {@link
DoNothingReceiver}s. The final element is an instance of {@link
RectangleDrawingReceiver}.
*/
public PaneEventExceptionGeneratingHandler()
{
DoNothingReceiver doNothingReceiver = null;
int numberOfDonthingReceivers=100;// increasing this to a large
value such as here where it is 100, makes the
ConcurrentModificationException more likely or even certain. Lowering
the value to 1 prevents the Exception. In between values may or may not
cause the Exception to be thrown. Any specific in-between value ***
RESULTS IN NON_DETERMINISITIC BEHAVIOR WRT TO EXCEPTION GENERATION FROM
RUN TO RUN OF THE PROGRAM***
for (int i=0; i < numberOfDonthingReceivers; i++)
{
doNothingReceiver = new DoNothingReceiver();
reserveReceivers.add(new DoNothingReceiver());
}
RectangleDrawingReceiver rectangleDrawingReceiver = new
RectangleDrawingReceiver();
reserveReceivers.add(new RectangleDrawingReceiver());
}
/**
* First invokes {@link #showEnterDebugInformation(int, String,
boolean)} to show the depth of recursion. Then clears the elements in
{@link #activeReceivers}. Then transfers the elements in {@link
#reserveReceivers} into {@link #activeReceivers}. Sends the {@link
MouseEvent} to each element in {@link #activeReceivers}. Finally,
invokes {@link #showExitDebugInformation(int, String, boolean)} to show
the depth of recursion.<p></p>
* If an {@link Exception} is raised, catches that {@link Exception}
and prints its relevant information to standard I?O.
*
* @param mouseEvent the {@link MouseEvent} which was generated on
the {@link Pane} and passed to this object by the JavaFX Application
Thread.
*/
@Override
public void handle(MouseEvent mouseEvent)
{
showEnterDebugInformation(0, "handle", true);
recursiveDepth++;
activeReceivers.clear();
transferReserveReceiversToActiveReceivers();
try
{
sendEvent(mouseEvent);
}
catch (Exception ex)
{
showExceptionTrace(ex);
}
recursiveDepth--;
showExitDebugInformation(0, "handle", true);
}
/**
* Sends the {@link MouseEvent} received from the {@link Pane} to all
the {@link Receiver}s in {@link #activeReceivers}.
*
* @param mouseEvent
* the {@link MouseEvent} which was delivered from the {@link
Pane} by the JavaFX Application Thread.
*/
public void sendEvent(MouseEvent mouseEvent)
{
showEnterDebugInformation(2, "sendEvent", false);
try
{
for (Receiver activeReceiver : activeReceivers)
{
activeReceiver.receiveEvent(mouseEvent);
}
}
catch (Exception ex)
{
showExceptionTrace(ex);
}
showExitDebugInformation(2, "sendEvent", false);
}
/**
* Prints spacesLength number of spaces to standard I/O. Used by
debug statements to indent output statements from the same method the
same amount.
*
* @param spacesLength
* the number of spaces to append to standard I/O
*/
private void printSpaces(int spacesLength)
{
for (int i = 0; i <= spacesLength; i++)
System.out.print(" ");
}
/**
* Prints "ENTERING" then the method name and optionally the value
of {@link #recursiveDepth} at the time this method is invoked.
*
* @param prettyPrintOffset
* the amount of pretty printing indenting to be written to
standard I/O for this method
* @param method
* the name of the method which invoked this method
* @param showCounter
* true iff {@link #recursiveDepth} should also be appended
to standard I/O.
*/
private void showEnterDebugInformation(int prettyPrintOffset, String
method, boolean showCounter)
{
System.out.println();
printSpaces(prettyPrintOffset);
System.out.print("ENTERING " + method);
if (showCounter)
{
System.out.print(" and recursiveDepth is " + recursiveDepth);
}
System.out.println();
}
/**
* Print to standard I/O some relevant information of the exception
passed to this method, including the {@link StackTraceElement}
elements.
*
* @param ex
* the {@link Exception} passed to this method whose
information should be printedc to standard I/O.
*/
private void showExceptionTrace(Exception ex)
{
System.out.println("ex = " + ex);
StackTraceElement[] stackTrace = ex.getStackTrace();
for (int i = 0; i < stackTrace.length; i++)
{
StackTraceElement stackTraceElement = stackTrace[i];
System.out.println("stackTraceElement = " + stackTraceElement);
}
}
/**
* Prints "EXITING" then the method name and optionally the value of
{@link #recursiveDepth} at the time this method is invoked.
*
* @param prettyPrintOffset
* the amount of pretty printing indenting to be written to
standard I/O for this method
* @param method
* the name of the method which invoked this method
* @param showCounter
* true iff {@link #recursiveDepth} should also be appended
to standard I/O.
*/
private void showExitDebugInformation(int prettyPrintOffset, String
method, boolean showCounter)
{
System.out.println();
printSpaces(prettyPrintOffset);
System.out.print("EXITING " + method);
if (showCounter)
{
System.out.print(" and recursiveDepth is " + recursiveDepth);
}
System.out.println();
}
/**
* Transfer the contents of {@link #reserveReceivers} into {@link
#activeReceivers} as a means of invoking {@link Collection#add(Object)}
and thereby provoking a {@link ConcurrentModificationException} in the
event the JavaFX Application Thread recurses into {@link
#handle(MouseEvent)} .
*/
private void transferReserveReceiversToActiveReceivers()
{
for (Receiver reserveReceiver : reserveReceivers)
{
activeReceivers.add(reserveReceiver);
}
}
} // class
package javaApplicationThreadCuriosityComplex;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.ConcurrentModificationException;
/**
* An {@link Application} with one {@link Pane}. The {@link Pane} has a
single {@link javafx.event.EventHandler}, {@link
PaneEventExceptionGeneratingHandler} which processes all {@link
MouseEvent}s the {@link Pane} receives.
* <p></p>
* To use this program, launch it and move the mouse. A stream of
messages will appear in standard IO which you can ignore for now.
Click once anywhere in the {@link Pane}. A {@link Rectangle} will
appear. Move the mouse over the {@link Rectangle} and click again. The
Rectangle will change color and a {@link
ConcurrentModificationException} (unrelated to the change in color)
will be thrown, caught and its stack trace will be printed to the
screen.
* <p></p>
* The messages to IO are are sent as the application enters into and
later exits each method. They can help you understand the bug. When the
exception is thrown, its stack trace elements are printed also.
* <p></p>
* It's not enough to look at the stack trace to understand the bug.
You have to read the the javadoc in {@link
PaneEventExceptionGeneratingHandler} for an explanation of how this
program demonstrates the bug.
*/
public class JavaFXAnomalyComplexVersionApplication extends Application
{
public void start(Stage primaryStage)
{
Pane mainPane = new Pane();
mainPane.setMinHeight(800);
mainPane.setMinWidth(800);
PaneEventExceptionGeneratingHandler paneEventSender = new
PaneEventExceptionGeneratingHandler();
mainPane.addEventHandler(MouseEvent.ANY, paneEventSender);
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);
}
}
***************************END
******************************************
On Tuesday, September 18, 2018 at 12:49 PM, Kevin Rushforth
<***@oracle.com> wrote:
Post by Kevin RushforthIn general, smaller test cases are better, so this the preferred choice.
-- Kevin
Post by Phil RaceNo. If you have a test case, include it in the body.
if its too big for that .. then maybe it needs to be trimmed down anyway.
-phil.
Post by j***@use.startmail.comI don't see a way to attach java classes at the bug report form
https://bugreport.java.com/bugreport/start_form.do
Is there some way to do this?
Thank you.
On Tuesday, September 18, 2018 at 9:02 AM, Kevin Rushforth
Post by Kevin RushforthI am pleased to announce the final release of JavaFX 11 as well as the
http://openjfx.io/
The GA version of JavaFX 11 is now live and can be downloaded by going
to the openjfx.io site or by accessing javafx modules from maven central
at openjfx:javafx-COMPONENT:11 (where COMPONENT is one of base,
graphics, controls, and so forth).
This is the first standalone release of JavaFX 11. It runs with JDK 11,
which is available as a release candidate now and will be shipped as a
GA version next week, or on JDK 10 (OpenJDK build only).
A big thank you to all who have contributed to OpenJFX make this release
possible! I especially thank Johan Vos, both for taking on the role as
Co-Lead of the OpenJFX Project and for the work that Gluon as done to
build and host the JavaFX 11 release.
I look forward to working with you all on JavaFX 12 and beyond.
-- Kevin