A DESCRIPTION OF THE PROBLEM :
Our application uses JavaFX and loads its classes in parallel to improve startup performance. Because of that, the JavaFX classes are loaded in parallel too. Some of these classes create EventType instances in their static initializers. Unfortunately, the WeakHashMap used in EventType::register is not thread-safe, and using it concurrently sometimes triggers the ConcurrentModificationException.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test case code. You might need to do it several times to trigger the exception.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No exception is thrown: static initializers of JavaFX classes are thread-safe
ACTUAL -
The ConcurrentModificationException exception is thrown.
---------- BEGIN SOURCE ----------
package org.example;
import javafx.application.Platform;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Platform.startup(() -> {
try (var executor = Executors.newCachedThreadPool()) {
try {
List<Future<Class<?>>> futures = executor.invokeAll(Stream.of(
"com.sun.javafx.event.RedirectedEvent",
"javafx.concurrent.WorkerStateEvent",
"javafx.css.TransitionEvent",
"javafx.event.ActionEvent",
"javafx.event.EventType",
"javafx.scene.control.CheckBoxTreeItem",
"javafx.scene.control.ChoiceBox",
"javafx.scene.control.ComboBoxBase",
"javafx.scene.control.DialogEvent",
"javafx.scene.control.ListView",
"javafx.scene.control.Menu",
"javafx.scene.control.MenuButton",
"javafx.scene.control.MenuItem",
"javafx.scene.control.ScrollToEvent",
"javafx.scene.control.SortEvent",
"javafx.scene.control.Tab",
"javafx.scene.control.TableColumn",
"javafx.scene.control.TreeItem",
"javafx.scene.control.TreeTableColumn",
"javafx.scene.control.TreeTableView",
"javafx.scene.control.TreeView",
"javafx.scene.input.ContextMenuEvent",
"javafx.scene.input.DragEvent",
"javafx.scene.input.GestureEvent",
"javafx.scene.input.InputMethodEvent",
"javafx.scene.input.KeyEvent",
"javafx.scene.input.MouseDragEvent",
"javafx.scene.input.MouseEvent",
"javafx.scene.input.RotateEvent",
"javafx.scene.input.ScrollEvent",
"javafx.scene.input.SwipeEvent",
"javafx.scene.input.TouchEvent",
"javafx.scene.input.ZoomEvent",
"javafx.scene.media.MediaErrorEvent",
"javafx.scene.transform.TransformChangedEvent",
"javafx.scene.web.WebErrorEvent",
"javafx.scene.web.WebEvent",
"javafx.stage.WindowEvent"
).map(className -> (Callable<Class<?>>) () -> Class.forName(className)).toList());
for (Future<Class<?>> future : futures) {
future.get();
}
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
System.exit(0);
});
}
}
---------- END SOURCE ----------
Our application uses JavaFX and loads its classes in parallel to improve startup performance. Because of that, the JavaFX classes are loaded in parallel too. Some of these classes create EventType instances in their static initializers. Unfortunately, the WeakHashMap used in EventType::register is not thread-safe, and using it concurrently sometimes triggers the ConcurrentModificationException.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test case code. You might need to do it several times to trigger the exception.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No exception is thrown: static initializers of JavaFX classes are thread-safe
ACTUAL -
The ConcurrentModificationException exception is thrown.
---------- BEGIN SOURCE ----------
package org.example;
import javafx.application.Platform;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Platform.startup(() -> {
try (var executor = Executors.newCachedThreadPool()) {
try {
List<Future<Class<?>>> futures = executor.invokeAll(Stream.of(
"com.sun.javafx.event.RedirectedEvent",
"javafx.concurrent.WorkerStateEvent",
"javafx.css.TransitionEvent",
"javafx.event.ActionEvent",
"javafx.event.EventType",
"javafx.scene.control.CheckBoxTreeItem",
"javafx.scene.control.ChoiceBox",
"javafx.scene.control.ComboBoxBase",
"javafx.scene.control.DialogEvent",
"javafx.scene.control.ListView",
"javafx.scene.control.Menu",
"javafx.scene.control.MenuButton",
"javafx.scene.control.MenuItem",
"javafx.scene.control.ScrollToEvent",
"javafx.scene.control.SortEvent",
"javafx.scene.control.Tab",
"javafx.scene.control.TableColumn",
"javafx.scene.control.TreeItem",
"javafx.scene.control.TreeTableColumn",
"javafx.scene.control.TreeTableView",
"javafx.scene.control.TreeView",
"javafx.scene.input.ContextMenuEvent",
"javafx.scene.input.DragEvent",
"javafx.scene.input.GestureEvent",
"javafx.scene.input.InputMethodEvent",
"javafx.scene.input.KeyEvent",
"javafx.scene.input.MouseDragEvent",
"javafx.scene.input.MouseEvent",
"javafx.scene.input.RotateEvent",
"javafx.scene.input.ScrollEvent",
"javafx.scene.input.SwipeEvent",
"javafx.scene.input.TouchEvent",
"javafx.scene.input.ZoomEvent",
"javafx.scene.media.MediaErrorEvent",
"javafx.scene.transform.TransformChangedEvent",
"javafx.scene.web.WebErrorEvent",
"javafx.scene.web.WebEvent",
"javafx.stage.WindowEvent"
).map(className -> (Callable<Class<?>>) () -> Class.forName(className)).toList());
for (Future<Class<?>> future : futures) {
future.get();
}
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
System.exit(0);
});
}
}
---------- END SOURCE ----------
- blocks
-
JDK-8348987 ☂ Thread safety in Node initialization
-
- In Progress
-
- links to
-
Review(master) openjdk/jfx/1729