JDK-8303736 : Incomplete mechanism for preventing changing JavaFX component from non-FX thread
  • Type: Bug
  • Component: javafx
  • Sub-Component: scenegraph
  • Affected Version: 8,jfx11,jfx17,jfx19,jfx20,jfx21
  • Priority: P3
  • Status: New
  • Resolution: Unresolved
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2023-03-05
  • Updated: 2023-03-07
Related Reports
Relates :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Windows 10 21H2, jdk-8u251, IntelliJ 2020.2 Ultimate version as the IDE

A DESCRIPTION OF THE PROBLEM :
There are some report (JDK-8095034) (JDK-8298104) points this issue, but there are no simple examples to reproduce this results. I would like to provide an example to show how non-FX thread modify the FX components without triggering the IllegalStateException ("Not on FX application thread").


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Use the example source code with Java 1.8.0_251. (I am using IntelliJ 2020.2 Ultimate version as the IDE)

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The IllegalStateException ("Not on FX application thread") is expected to be triggered (or if this is safe, the program should run without any exceptions)
ACTUAL -
Nullpointerexception is triggered after waiting for several seconds:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
	at javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2289)
	at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2419)
	at com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:398)
	at java.security.AccessController.doPrivileged(Native Method)
	at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:397)
	at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:424)
	at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:561)
	at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:541)
	at com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:534)
	at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:340)
	at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
	at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
	at com.sun.glass.ui.win.WinApplication.lambda$null$4(WinApplication.java:186)
	at java.lang.Thread.run(Thread.java:748)


Or in some cases, ArrayIndexOutofBoundsException is triggered:

Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException: -1
	at java.util.ArrayList.elementData(ArrayList.java:422)
	at java.util.ArrayList.get(ArrayList.java:435)
	at com.sun.javafx.collections.ObservableListWrapper.get(ObservableListWrapper.java:89)
	at com.sun.javafx.collections.VetoableListDecorator.get(VetoableListDecorator.java:306)
	at javafx.scene.Parent.updateCachedBounds(Parent.java:1591)
	at javafx.scene.Parent.recomputeBounds(Parent.java:1535)
	at javafx.scene.Parent.impl_computeGeomBounds(Parent.java:1388)
	at javafx.scene.layout.Region.impl_computeGeomBounds(Region.java:3078)
	at javafx.scene.Node.updateGeomBounds(Node.java:3577)
	at javafx.scene.Node.getGeomBounds(Node.java:3530)
	at javafx.scene.Node.getLocalBounds(Node.java:3478)
	at javafx.scene.Node.updateTxBounds(Node.java:3641)
	at javafx.scene.Node.getTransformedBounds(Node.java:3424)
	at javafx.scene.Node.updateBounds(Node.java:559)
	at javafx.scene.Parent.updateBounds(Parent.java:1719)
	at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2404)
	at com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:398)
	at java.security.AccessController.doPrivileged(Native Method)
	at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:397)
	at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:424)
	at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:561)
	at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:541)
	at com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:534)
	at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:340)
	at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
	at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
	at com.sun.glass.ui.win.WinApplication.lambda$null$4(WinApplication.java:186)
	at java.lang.Thread.run(Thread.java:748)



---------- BEGIN SOURCE ----------
Main File:

package sample;

import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {
    FXMLLoader fxmlLoader;

    @Override
    public void start(Stage primaryStage) throws Exception{
        fxmlLoader = new FXMLLoader(getClass().getResource("sample.fxml"));
        Parent root = fxmlLoader.load();
        Controller controller = fxmlLoader.getController();
        controller.configureBinding();
        controller.setupTimer();

        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}

Controller File:
package sample;

import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

import java.util.Timer;
import java.util.TimerTask;

public class Controller {
    @FXML
    private Label label_test;

    @FXML
    private Label label_test1;

    @FXML
    private Label label_test2;

    @FXML
    private Label label_test3;

    @FXML
    private Label label_test4;

    @FXML
    private Label label_test5;

    @FXML
    private Label label_test6;

    @FXML
    private Label label_test7;

    @FXML
    private Label label_test8;

    @FXML
    private Label label_test9;


    private Timer timer;
    private Timer timer2;

    private int test = 0;
    private int test2 = 0;

    private final SimpleStringProperty simpleStringProperty = new SimpleStringProperty();

    public void configureBinding(){
        simpleStringProperty.addListener((observable, oldValue, newValue) -> {
            if (simpleStringProperty.get().equals("1")){
                label_test.setVisible(true);
                label_test.setDisable(false);

                label_test1.setVisible(true);
                label_test1.setDisable(false);

                label_test2.setVisible(true);
                label_test2.setDisable(false);

                label_test3.setVisible(true);
                label_test3.setDisable(false);

                label_test4.setVisible(true);
                label_test4.setDisable(false);

                label_test5.setVisible(true);
                label_test5.setDisable(false);

                label_test6.setVisible(true);
                label_test6.setDisable(false);

                label_test7.setVisible(true);
                label_test7.setDisable(false);

                label_test8.setVisible(true);
                label_test8.setDisable(false);

                label_test9.setVisible(true);
                label_test9.setDisable(false);
            }else{
                label_test.setVisible(false);
                label_test.setDisable(true);

                label_test1.setVisible(false);
                label_test1.setDisable(true);

                label_test2.setVisible(false);
                label_test2.setDisable(true);

                label_test3.setVisible(false);
                label_test3.setDisable(true);

                label_test4.setVisible(false);
                label_test4.setDisable(true);

                label_test5.setVisible(false);
                label_test5.setDisable(true);

                label_test6.setVisible(false);
                label_test6.setDisable(true);

                label_test7.setVisible(false);
                label_test7.setDisable(true);

                label_test8.setVisible(false);
                label_test8.setDisable(true);

                label_test9.setVisible(false);
                label_test9.setDisable(true);
            }
        });
    }

    public void setupTimer(){
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                simpleStringProperty.set(test + "");
                test = (test + 1) % 2;
            }
        }, 0, 1);


    }

}

FXML file:
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>

<GridPane alignment="center" hgap="10" vgap="10" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
    <children>
        <Label fx:id="label_test" text="Label" />
      <Label fx:id="label_test1" text="Label" />
      <Label fx:id="label_test2" text="Label" />
      <Label fx:id="label_test3" text="Label" />
      <Label fx:id="label_test4" text="Label" />
      <Label fx:id="label_test5" text="Label" />
      <Label fx:id="label_test6" text="Label" />
      <Label fx:id="label_test7" text="Label" />
      <Label fx:id="label_test8" text="Label" />
      <Label fx:id="label_test9" text="Label" />
    </children>
   <columnConstraints>
      <ColumnConstraints />
   </columnConstraints>
   <rowConstraints>
      <RowConstraints />
   </rowConstraints>
</GridPane>



---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Move the code that may modify JavaFX components into Platform.runLater().

FREQUENCY : occasionally



Comments
Checked with attached testcase in Windows10, Issue is reproducible, As mouse is moved, Exception observed in console Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 10 Test Result ========= 8u361: Fail jfx11: Fail jfx17: Fail jfx19: Fail jfx20ea16: Fail jfx21ea2: Fail
07-03-2023