Skip to content

Commit 52f2057

Browse files
committed
Fix JavaFX + AWT EDT macOS deadlocks
This commit "fixes" deadlocks on macOS where the AWT EDT and JavaFX threading models clash. On macOS, but not Windows or Linux, all GUI things happen with AppKit and the Cocoa event loop. Additionally on macOS only **one** event dispatch thread (EDT) is allowed per event loop which means that JavaFX and AWT effectively share the same thread resources. The specific case that this addresses is flimlib/flimj-ui#35. FLIMJ (i.e. flimj-ui) is a JavaFX application that needs to interact with AWT UI elements. Briefly when a user attempts to export images back to Fiji by clicking "Export", the application hangs both the plugin and Fiji in general. From *my* understanding this is what is happening: 1. The user clicks "Export" which triggers a GUI event which ultimately gets run with `invokeAndWait()` which is a blocking call until the export function returns. Because the event is tied to the GUI, the JavaFX now has a lock on the Cocoa event loop until export is complete. 2. What the "Export" button does is show the selected images in Fiji, which means we need to use AWT now. The `show()` call is made and tiggers its necessary AWT code which ultimately asks for a lock on the Cocoa event loop to acess the GUI screen. 3. The AWT thread never gets the Cocoa event loop because JavaFX has it and wont give it up until AWT shows the images and returns. But AWT **cant** becaues JavaFX has the lock. This is our threading dead lock and also why we see hangs here in the thread dumps: at sun.awt.CGraphicsDevice.nativeGetScreenInsets (Native Method) at org.scijava.thread.DefaultThreadService.invoke (line 114) This doesn't happen on Windows or Linux becauase, according to the bug discussion linked below, its possible to have more than one EDT on those systems. Just not macOS. What this commit does is instead of using the blocking `invoke()` call via the ThreadService which gets us in this trouble, we instead detect a JavaFX thread and do an async queue instead. This means that JavaFX doesn't lock the Cocoa event loop, apperently, and AWT is safe to do its thing and return. See https://bugs.openjdk.org/browse/JDK-8087465 for more details on this JavaFX + AWT bug.
1 parent d14ad9b commit 52f2057

File tree

1 file changed

+16
-0
lines changed

1 file changed

+16
-0
lines changed

src/main/java/org/scijava/event/DefaultEventBus.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,22 @@ private void publishNow(final Object event, final String topic,
150150
final StackTraceElement[] callingStack)
151151
{
152152
if (subscribers == null || subscribers.isEmpty()) return;
153+
154+
// queue JavaFX threads to avoid native Cocoa thread deadlocks on macOS
155+
final String threadName = Thread.currentThread().getName();
156+
if (threadName != null && threadName.startsWith("JavaFX Application Thread")) {
157+
threadService.queue(new Runnable() {
158+
@Override
159+
public void run() {
160+
log.debug("publish(" + event + "," + topic + "," + eventObj +
161+
"), called from JavaFX Thread:" + Arrays.toString(callingStack));
162+
DefaultEventBus.super.publish(event, topic, eventObj, subscribers,
163+
vetoSubscribers, callingStack);
164+
}
165+
});
166+
return;
167+
}
168+
153169
try {
154170
threadService.invoke(new Runnable() {
155171

0 commit comments

Comments
 (0)