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

Style property changed on other Thread

XMLWordPrintable

    • x86_64
    • windows

      FULL PRODUCT VERSION :
      java version "1.8.0_131"
      Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
      Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

      java version "1.8.0_73"
      Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
      Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Windows 7 Pro SP1
      Windows 10 Pro

      A DESCRIPTION OF THE PROBLEM :
      I found out that on JavaFX, if you modify the style property of a Node on other Thread that the Java FX Thread, you don't get any "java.lang.IllegalStateException: Not on FX application thread".

      However, modifying the style property outside the JavaFX Application Thread create random error (like NullPointerException or ConcurrentModificationException).

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Add node to a scenegraph and modify their style property outside the JavaFX Thread. It's more likely to happen if the style changes are happening to high frequency.

      You can just launch my demo code and wait for an exception to happen.
      The exception may vary, sometimes it's a NPE, sometimes a ConcurrentModificationException.
      If you scroll in the list view, it seems that the exception happen quicker.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Any change on style property (setStyle, or property binding) outside the FX Application Thread should throw a java.lang.IllegalStateException: Not on FX application thread if the node is in a scenegraph.

      OR

      Changes on style property outside FX Application Thread shouldn't create random errors.
      ACTUAL -
      Exceptions are thrown on JavaFX Application Thread

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      Exception in thread "JavaFX Application Thread" java.util.ConcurrentModificationException
      at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
      at java.util.HashMap$EntryIterator.next(HashMap.java:1471)
      at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
      at java.util.AbstractCollection.addAll(AbstractCollection.java:343)
      at java.util.HashSet.<init>(HashSet.java:119)
      at javafx.scene.CssStyleHelper.resetToInitialValues(CssStyleHelper.java:441)
      at javafx.scene.CssStyleHelper.createStyleHelper(CssStyleHelper.java:180)
      at javafx.scene.Node.reapplyCss(Node.java:8985)
      at javafx.scene.Node.reapplyCss(Node.java:9014)
      at javafx.scene.Node.impl_processCSS(Node.java:9182)
      at javafx.scene.Parent.impl_processCSS(Parent.java:1249)
      at javafx.scene.control.Control.impl_processCSS(Control.java:868)
      at javafx.scene.Node.processCSS(Node.java:9058)
      at javafx.scene.Node.processCSS(Node.java:9051)
      at javafx.scene.Node.processCSS(Node.java:9051)
      at javafx.scene.Node.processCSS(Node.java:9051)
      at javafx.scene.Node.processCSS(Node.java:9051)
      at javafx.scene.Node.processCSS(Node.java:9051)
      at javafx.scene.Scene.doCSSPass(Scene.java:545)
      at javafx.scene.Scene.access$3600(Scene.java:159)
      at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2392)
      at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:355)
      at java.security.AccessController.doPrivileged(Native Method)
      at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
      at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
      at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
      at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
      at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$404(QuantumToolkit.java:319)
      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$148(WinApplication.java:191)
      at java.lang.Thread.run(Thread.java:748)


      Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
      at javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Unknown Source)
      at javafx.scene.Scene$ScenePulseListener.pulse(Unknown Source)
      at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Unknown Source)
      at java.security.AccessController.doPrivileged(Native Method)
      at com.sun.javafx.tk.Toolkit.runPulse(Unknown Source)
      at com.sun.javafx.tk.Toolkit.firePulse(Unknown Source)
      at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(Unknown Source)
      at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(Unknown Source)
      at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$404(Unknown Source)
      at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
      at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
      at com.sun.glass.ui.win.WinApplication.lambda$null$148(Unknown Source)
      at java.lang.Thread.run(Unknown Source)



      Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
      at com.sun.javafx.text.PrismTextLayout.layout(PrismTextLayout.java:1267)
      at com.sun.javafx.text.PrismTextLayout.ensureLayout(PrismTextLayout.java:223)
      at com.sun.javafx.text.PrismTextLayout.getBounds(PrismTextLayout.java:246)
      at javafx.scene.text.Text.getLogicalBounds(Text.java:358)
      at javafx.scene.text.Text.impl_computeLayoutBounds(Text.java:1115)
      at javafx.scene.Node$12.computeBounds(Node.java:3225)
      at javafx.scene.Node$LazyBoundsProperty.get(Node.java:9308)
      at javafx.scene.Node$LazyBoundsProperty.get(Node.java:9278)
      at javafx.scene.Node.getLayoutBounds(Node.java:3240)
      at com.sun.javafx.scene.control.skin.LabeledSkinBase.layoutLabelInArea(LabeledSkinBase.java:933)
      at com.sun.javafx.scene.control.skin.LabeledSkinBase.layoutLabelInArea(LabeledSkinBase.java:857)
      at com.sun.javafx.scene.control.skin.LabeledSkinBase.layoutChildren(LabeledSkinBase.java:841)
      at javafx.scene.control.Control.layoutChildren(Control.java:578)
      at javafx.scene.Parent.layout(Parent.java:1087)
      at javafx.scene.Parent.layout(Parent.java:1093)
      at javafx.scene.Parent.layout(Parent.java:1093)
      at javafx.scene.Parent.layout(Parent.java:1093)
      at javafx.scene.Parent.layout(Parent.java:1093)
      at javafx.scene.Parent.layout(Parent.java:1093)
      at javafx.scene.Scene.doLayoutPass(Scene.java:552)
      at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2397)
      at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:355)
      at java.security.AccessController.doPrivileged(Native Method)
      at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
      at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
      at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
      at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
      at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$404(QuantumToolkit.java:319)
      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$148(WinApplication.java:191)
      at java.lang.Thread.run(Thread.java:748)


      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      package test;

      import java.util.Random;

      import javafx.application.Application;
      import javafx.beans.binding.Bindings;
      import javafx.beans.property.BooleanProperty;
      import javafx.beans.property.SimpleBooleanProperty;
      import javafx.collections.FXCollections;
      import javafx.collections.ObservableList;
      import javafx.scene.Scene;
      import javafx.scene.control.ListCell;
      import javafx.scene.control.ListView;
      import javafx.scene.layout.BorderPane;
      import javafx.stage.Stage;

      /**
       * Tested with : jdk1.8.0_73 / jdk1.8.0_131
       */
      public class ConcurrentStyleDemo extends Application {
      private final static long PAUSE = 1;
      private final static Random RANDOM = new Random();

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

      @Override
      public void start(Stage stage) throws Exception {
      //Scene
      stage.setWidth(200);
      stage.setHeight(200);
      BorderPane borderPane = new BorderPane();
      Scene scene = new Scene(borderPane);
      stage.setScene(scene);

      //List view
      ObservableList<Item> itemList = createList();
      ListView<Item> listView = new ListView<>(itemList);
      listView.setCellFactory(l -> new ItemCell());
      borderPane.setCenter(listView);
      stage.show();

      launchChangingThread(itemList);
      }

      private void launchChangingThread(ObservableList<Item> itemList) {
      Thread thread = new Thread(() -> {
      while (true) {
      try {
      int nextInt = RANDOM.nextInt(itemList.size());
      Item item = itemList.get(nextInt);
      item.selected.set(!item.selected.get());
      Thread.sleep(PAUSE);
      } catch (Throwable t) {
      t.printStackTrace();
      }
      }
      }, "Change Item Property Thread");
      thread.setDaemon(true);
      thread.start();
      }

      private ObservableList<Item> createList() {
      ObservableList<Item> list = FXCollections.observableArrayList();
      for (int i = 0; i < 100; i++)
      list.add(new Item());
      return list;
      }

      private static class ItemCell extends ListCell<Item> {

      @Override
      protected void updateItem(Item item, boolean empty) {
      super.updateItem(item, empty);
      if (item != null && !empty) {
      this.setText(item.val);
      this.styleProperty().bind(Bindings.createStringBinding(() -> {
      return item.selected.get() ? "-fx-background-color:red;" : null;
      }, item.selected));
      } else {
      this.styleProperty().unbind();
      this.setStyle(null);
      this.setText(null);
      }
      }
      }

      private static class Item {
      static int c;
      private String val = "Item " + c++;
      private BooleanProperty selected = new SimpleBooleanProperty(false);
      }
      }

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

      CUSTOMER SUBMITTED WORKAROUND :
      Wrap all the style modifications to a Platform.runLater(...) call.

      But I definitly think that the developper should be warned if he tries to modify the style outside the JavaFX Application Thread

            kcr Kevin Rushforth
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: