shannah / Java-Objective-C-Bridge

A thin bridge that allows for two-way communication from Java to Objective-C.
123 stars 25 forks source link

NSOpenPanel Swing/Cocoa deadlock #4

Closed rednoah closed 8 years ago

rednoah commented 8 years ago

If you're calling dispatch_sync there's a chance that it'll freeze the application, especially on the high-end MBPs with dedicated GPU and automatic graphics switching. I guess the Swing/Cocoa EDTs somehow end up waiting for each other, in certain rare cases.

The problem can be solved by using Cocoa dispatch_async and AWT SecondaryLoop instead: https://docs.oracle.com/javase/8/docs/api/java/awt/SecondaryLoop.html

SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();

dispatch_async(() -> {
    // do cocoa stuff

    secondaryLoop.exit();
});

// wait for cocoa
secondaryLoop.enter();

There goes a few sleepless nights... I hope this helps someone!

shannah commented 8 years ago

I wasn't aware of this issue. Can you post a simple test case that demonstrates the deadlock?

rednoah commented 8 years ago

On specific MBP models (the most expensive ones with dedicated GPU, so few people have those), it'll deadlock every time when you call dispatch_sync running NSOpenPanel.runModal from the Swing EDT, but on most Mac computers (including mine) it'll never happen. It may or may not have something to do with calling NSOpenPanel.runModal in a sandboxed application.

It should be reproducible with your NativeFileDialog code, unless Worker.post somehow works around the issue.

shannah commented 8 years ago

The NativeFileDialog code uses the Foxtrot framework to allow you to block the current event on the EDT without blocking the others. Basically it spawns a new EDT that continues to process the event queue while your particular event is blocked.

So, yes Worker.post() works around the issue.

Calling NSOpenPanel.runModal() should never be called directly on the Swing EDT. It should run on the cocoa main thread (i.e. inside dispatch_async()). Technically wrapping it in dispatch_sync() solves that problem (runs on main thread) but because it causes the EDT to block until it is done, it can lead a lockup.

rednoah commented 8 years ago

Maybe you could add a few comments to your example usage, to make it clear that NativeFileDialog must not be used on the EDT, and that Worker.post() must be used: https://gist.github.com/shannah/65007754c2b0f8add4f7#file-example_directory_dialog-java

shannah commented 8 years ago

OK. I added a comment in the javadocs.

b005t3r commented 7 months ago

@shannah would be excellent if you added a check of sorts if the NativeFileDialog is created the right way. I had some issues with it, because I somehow overlooked your comment in the docs :)

Thanks :)

shannah commented 7 months ago

@b005t3r Even better would be to just implement the Worker inside the setVisible method. I'll update it when I get a chance.

b005t3r commented 7 months ago

@shannah that would be fantastic :)

BTW, there's one more thing needed if you want to use FoxTrot's Worker for this thing. It turns out that in 2024 you can't just access whatever you want via the reflection API, so you need to start your VM with these two params to allow acceess to whatever Worker is accessing:

--add-opens=java.desktop/java.awt=ALL-UNNAMED
--add-opens=java.desktop/sun.awt=ALL-UNNAMED