Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8211000

Application Thread is recursively re-entrant in some circumstances

    Details

    • Subcomponent:
    • CPU:
      x86_64
    • OS:
      generic

      Description

      ADDITIONAL SYSTEM INFORMATION :
      Windows 7 x64 / JDK 1.8

      A DESCRIPTION OF THE PROBLEM :
      JavaFX Application Thread can be shown to be recursively re-entrant in EventHandler#handle() when a synthetic mouse event is generated in the wake of a Node's removal from the Scene when the mouse is over that Node at the time of its removal and the Pane containing the Node has an EventHandler which processes all mouse events.


      ---------- BEGIN SOURCE ----------

      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 SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      None known.

      FREQUENCY : always


        Attachments

          Activity

            People

            • Assignee:
              arapte Ambarish Rapte
              Reporter:
              webbuggrp Webbug Group
            • Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Created:
                Updated: