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 <kevin.rushfo...@oracle.com> wrote:
 
In general, smaller test cases are better, so this the preferred choice.

-- Kevin


On 9/18/2018 9:37 AM, Phil Race wrote:
No. 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.

On 09/18/2018 09:31 AM, jav...@use.startmail.com wrote:
I don't see a way to attach java classes at the bug report form
located here:

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
<kevin.rushfo...@oracle.com> wrote:
 
I am pleased to announce the final release of JavaFX 11 as well as the
launch of a new OpenJFX community site at:

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
 

Reply via email to