◐ Shell
clean mode source ↗

Fix JavaFX + AWT EDT macOS deadlocks by elevans · Pull Request #489 · scijava/scijava-common

@elevans

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.