[Native Image][Darwin] Swing GUI hangs: pthread main does not run AppKit CFRunLoop (Metal/CGL deadlock)
Summary
On GraalVM CE nightly (Darwin aarch64), building a Swing GUI app with -H:Preserve=module=java.desktop successfully copies AWT dylibs and creates libjvm/libjava shims (past the link failure in #13272). At runtime the process still hangs forever with no window: the EDT blocks in Metal (or CGL) init waiting on the AppKit main thread, while pthread main is parked in SubstrateVM and never runs a CFRunLoop / [NSApplication run].
HotSpot runs the same bytecode without hanging (main stays in AppKit’s run loop via libjli).
Working repository
A full working macOS setup (GraalVM CE nightly build script, MacSwingLauncher AppKit CFRunLoop workaround, reachability metadata, and build/ runtime layout) is here:
https://github.com/The-Alchemist/swing-graalvm-demo
See especially:
src/MacSwingLauncher.java—doAWTRunLooppump + quit/halt handling + bundledjava.homestubbuild.sh— nightly Prefer +-H:Preserve=module=java.desktopREADME.md— build/run instructions (./build/demo)
Environment
- GraalVM:
native-image 25.0.3/ GraalVM CE 25.2.4-dev+9.1 (build 25.0.3+9-jvmci-25.1-b19) - OS: macOS 26.5.2 (25F84), arm64
- App: FlatLaf demo (
SwingUtilities.invokeLater→ showJFrame), also reproducible with a minimal frame
Build (succeeds; artifacts present)
native-image \
--add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED \
-H:+UnlockExperimentalVMOptions \
-H:Preserve=module=java.desktop \
-Djava.awt.headless=false \
-jar app.jar demoBuild artifacts include e.g. libawt.dylib, libawt_lwawt.dylib, libjvm.dylib (shim), libjava.dylib (shim) next to the executable.
Run without workaround
./demo
# process stays alive, ~0% CPU, no windowStacks (sample)
AWT-EventQueue-0 (abbreviated):
Java_sun_java2d_metal_MTLGraphicsConfig_tryLoadMetalLibrary (libawt_lwawt.dylib)
-[NSObject performSelector:onThread:withObject:waitUntilDone:modes:]
-[_NSThreadPerformInfo wait]
_pthread_cond_wait(With -Dsun.java2d.metal=false, the same pattern appears on Java_sun_java2d_opengl_CGLGraphicsConfig_getCGLConfigInfo.)
pthread main / com.apple.main-thread:
(demo) …
_pthread_cond_wait
__psynch_cvwaitMain is not in CFRunLoop / [NSApplication run].
HotSpot contrast
Same jar under the same JDK: main samples in [NSApplication run] / CFRunLoop; UI appears.
Related
- #4124 — @petermz noted Cocoa requires the run loop on pthread main and that
libjliparks main in the RunLoop while Java runs elsewhere; a prior SVMJavaMainWrapperport was mentioned - #13272 / #13282 — Darwin AWT library copy / shims (behavior present in CE nightlies that include that path); this report assumes that part already works
- Non-headless coverage added around #13282 does not appear to cover the common Swing pattern:
invokeLater+ return frommain
Workaround
After scheduling the UI, keep pthread main inside AWT’s nested run loop:
// needs --add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED
Class<?> lwc = Class.forName("sun.lwawt.macosx.LWCToolkit");
var create = lwc.getDeclaredMethod("createAWTRunLoopMediator");
create.setAccessible(true);
var loop = lwc.getDeclaredMethod("doAWTRunLoop", long.class, boolean.class);
loop.setAccessible(true);
while (true) {
long mediator = (Long) create.invoke(null);
loop.invoke(null, mediator, true);
}With that, Metal init completes and the window shows. Note: System.exit can stall while main is inside NSApp run; Runtime.halt(0) was needed for clean quit in our testing.
Minimal repro sketch
import javax.swing.*;
public class SwingHang {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame f = new JFrame("native-image darwin");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(400, 300);
f.setVisible(true);
});
// return: SVM parks main without AppKit CFRunLoop → hang
}
}Questions
- Should SubstrateVM mirror
libjlion Darwin (RunLoop on pthread main, Java work on a side thread)? - Or should Darwin GUI native-image docs require apps to pump
LWCToolkit.doAWTRunLoop(or equivalent) on main after startup?
Happy to attach fuller sample output or a small public repro if useful.
Source: oracle/graal