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.
This PR "fixes" deadlocks on macOS where the AWT EDT and JavaFX threading models clash. 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:
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.
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.
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.
Tested on: