diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java index 3daeaf13dde3..a73077f182c3 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java @@ -64,6 +64,7 @@ import com.apple.laf.ClientPropertyApplicator.Property; import sun.awt.AWTAccessor; import sun.awt.AWTAccessor.ComponentAccessor; import sun.awt.AWTAccessor.WindowAccessor; +import sun.awt.InvokeOnToolkitHelper; import sun.java2d.SurfaceData; import sun.lwawt.LWKeyboardFocusManagerPeer; import sun.lwawt.LWLightweightFramePeer; @@ -345,7 +346,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo long nativeWindowPtr = java.security.AccessController.doPrivileged( (PrivilegedAction) () -> { try { - return LWCToolkit.SelectorPerformer.perform(() -> { + return InvokeOnToolkitHelper.invokeAndBlock(() -> { AtomicLong ref = new AtomicLong(); contentView.execute(viewPtr -> { boolean hasOwnerPtr = false; diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java index 97127ed72c8c..8def19e0fbe9 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java @@ -85,23 +85,14 @@ import java.awt.peer.TaskbarPeer; import java.awt.peer.TrayIconPeer; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; -import java.net.MalformedURLException; import java.net.URL; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import java.net.MalformedURLException; import javax.swing.UIManager; import com.apple.laf.AquaMenuBarUI; -import sun.awt.AWTAccessor; -import sun.awt.AppContext; -import sun.awt.CGraphicsDevice; -import sun.awt.LightweightFrame; -import sun.awt.PlatformGraphicsInfo; -import sun.awt.SunToolkit; +import sun.awt.*; import sun.awt.datatransfer.DataTransferer; import sun.awt.dnd.SunDragSourceContextPeer; import sun.awt.util.ThreadGroupUtils; @@ -472,9 +463,9 @@ public final class LWCToolkit extends LWToolkit { public Insets getScreenInsets(final GraphicsConfiguration gc) { GraphicsDevice gd = gc.getDevice(); if (!(gd instanceof CGraphicsDevice)) { - return LWCToolkit.SelectorPerformer.perform(() -> super.getScreenInsets(gc)); + return InvokeOnToolkitHelper.invokeAndBlock(() -> super.getScreenInsets(gc)); } - return LWCToolkit.SelectorPerformer.perform(() -> ((CGraphicsDevice)gd).getScreenInsets()); + return InvokeOnToolkitHelper.invokeAndBlock(() -> ((CGraphicsDevice)gd).getScreenInsets()); } @Override @@ -677,150 +668,6 @@ public final class LWCToolkit extends LWToolkit { } } - /** - * Performs a wrapped native selector on the main thread, waiting on EDT, preventing the threads from a deadlock. - */ - public static class SelectorPerformer { - private ExecutorService executor; - // every perform() pushes a queue of invocations - private Stack> invocations = new Stack<>(); - - // invocations should be dispatched on proper EDT (per AppContext) - private static final Map edt2performer = new ConcurrentHashMap<>(); - - private static final int WAIT_LIMIT_SECONDS = 5; - - private SelectorPerformer() {} - - /** - * Performs the selector wrapped in the callable. The selector should be executed via [JNFRunLoop performOnMainThreadWaiting:YES ...] - * on the native side so that the native doAWTRunLoop, which is run in [JNFRunLoop javaRunLoopMode], accepts it. - * The callable wrapper should not call any Java code which would normally be called on EDT. - *

- * If the main thread posts invocation events caused by the selector, those events are intercepted and dispatched on EDT out of order. - *

- * When called on non-EDT, the method performs the selector in place. The method is reentrant. - * - * @param selector the native selector wrapper - * @param the selector return type - * @return the selector result - */ - public static T perform(Callable selector) { - if (selector == null) return null; - - if (EventQueue.isDispatchThread()) { - SelectorPerformer performer = getInstance(Thread.currentThread()); - if (performer != null) { - return performer.performImpl(selector); - } - } - // fallback to default - try { - return selector.call(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - private T performImpl(Callable selector) { - assert EventQueue.isDispatchThread(); - if (executor == null) { - // init on EDT - AccessController.doPrivileged((PrivilegedAction)() -> - executor = new ThreadPoolExecutor(1, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - new SynchronousQueue<>(), - new ThreadFactory() { - private ThreadFactory factory = Executors.privilegedThreadFactory(); - @Override - public Thread newThread(Runnable r) { - Thread t = factory.newThread(r); - t.setDaemon(true); - t.setName("AWT-SelectorPerformer " + t.getName()); - return t; - } - }) - ); - } - LinkedBlockingQueue currentQueue; - synchronized (invocations) { - invocations.push(currentQueue = new LinkedBlockingQueue<>()); - } - - FutureTask task = new FutureTask(selector) { - @Override - protected void done() { - synchronized (invocations) { - // Done with the current queue, wake it up. - invocations.pop().add(new InvocationEvent(executor, () -> {})); - } - } - }; - executor.execute(task); - - try { - while (!task.isDone() || !currentQueue.isEmpty()) { - InvocationEvent event = currentQueue.poll(WAIT_LIMIT_SECONDS, TimeUnit.SECONDS); - if (event == null) { - new RuntimeException("Waiting for the invocation event timed out").printStackTrace(); - break; - } - event.dispatch(); - } - return task.isDone() ? task.get() : null; - - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - return null; - } - - /** - * Checks if there's an active SelectorPerformer corresponding to the invocation's AppContext, - * adds the invocation to the SelectorPerformer's queue and returns true. - * Otherwise does nothing and returns false. - */ - public static boolean offer(InvocationEvent invocation) { - Object source = invocation.getSource(); - - SelectorPerformer performer = (source instanceof Component) ? - getInstance((Component)source) : - getInstance(Toolkit.getDefaultToolkit().getSystemEventQueue()); - - if (performer == null) return false; - - synchronized (performer.invocations) { - if (!performer.invocations.isEmpty()) { - performer.invocations.peek().add(invocation); - return true; - } - } - return false; - } - - private static SelectorPerformer getInstance(Component comp) { - if (comp == null) return null; - - AppContext appContext = SunToolkit.targetToAppContext(comp); - if (appContext == null) return null; - - return getInstance((EventQueue)appContext.get(AppContext.EVENT_QUEUE_KEY)); - } - - private static SelectorPerformer getInstance(EventQueue eq) { - if (eq == null) return null; - - return getInstance(AWTAccessor.getEventQueueAccessor().getDispatchThread(eq)); - } - - private static SelectorPerformer getInstance(Thread edt) { - if (edt == null) return null; - - return edt2performer.computeIfAbsent(edt, key -> new SelectorPerformer()); - } - } - /** * Kicks an event over to the appropriate event queue and waits for it to * finish To avoid deadlocking, we manually run the NSRunLoop while waiting @@ -844,7 +691,7 @@ public final class LWCToolkit extends LWToolkit { }, true); - if (!SelectorPerformer.offer(invocationEvent)) { + if (!InvokeOnToolkitHelper.offer(invocationEvent)) { if (component != null) { AppContext appContext = SunToolkit.targetToAppContext(component); SunToolkit.postEvent(appContext, invocationEvent); @@ -853,7 +700,7 @@ public final class LWCToolkit extends LWToolkit { SunToolkit.flushPendingEvents(appContext); } else { // This should be the equivalent to EventQueue.invokeAndWait - ((LWCToolkit) Toolkit.getDefaultToolkit()).getSystemEventQueueForInvokeAndWait().postEvent(invocationEvent); + ((LWCToolkit) Toolkit.getDefaultToolkit()).getSystemEventQueueImpl().postEvent(invocationEvent); } } doAWTRunLoop(mediator, false); diff --git a/src/java.desktop/share/classes/sun/awt/InvokeOnToolkitHelper.java b/src/java.desktop/share/classes/sun/awt/InvokeOnToolkitHelper.java new file mode 100644 index 000000000000..eb84f299a4ec --- /dev/null +++ b/src/java.desktop/share/classes/sun/awt/InvokeOnToolkitHelper.java @@ -0,0 +1,158 @@ +package sun.awt; + +import java.awt.*; +import java.awt.event.InvocationEvent; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Map; +import java.util.Stack; +import java.util.concurrent.*; + +/** + * Used to perform a blocking invocation on Toolkit thread from EDT, preventing a deadlock. + * The deadlock can happen when EDT and Toolkit perform blocking invocations at the same time. + * The following is performed to resolve it: + * 1) The invoker spins a nested event loop on EDT while waiting for the invocation to complete. + * 2) A separate pool thread is used to perform the invocation. + */ +public class InvokeOnToolkitHelper { + private ExecutorService executor; + // every invokeAndWait() pushes a queue of invocations + private final Stack> invocations = new Stack<>(); + + // invocations should be dispatched on proper EDT (per AppContext) + private static final Map edt2invokerMap = new ConcurrentHashMap<>(); + + private static final int WAIT_LIMIT_SECONDS = 5; + + private InvokeOnToolkitHelper() {} + + /** + * Invokes the callable on Toolkit thread and blocks until the completion. The method is re-entrant. + * + * On macOS it is assumed the callable wraps a native selector. The selector should be executed via [JNFRunLoop performOnMainThreadWaiting:YES ...] + * so that the doAWTRunLoop on AppKit (which is run in [JNFRunLoop javaRunLoopMode]) accepts it. The callable wrapper should not call any Java code + * which would normally be called on EDT. + *

+ * If Toolkit posts invocation events caused by the callable, those events are intercepted and dispatched on EDT out of order. + *

+ * When called on non-EDT, the method invokes the callable in place. + */ + public static T invokeAndBlock(Callable callable) { + if (callable == null) return null; + + if (EventQueue.isDispatchThread()) { + InvokeOnToolkitHelper invoker = getInstance(Thread.currentThread()); + if (invoker != null) { + return invoker.invoke(callable); + } + } + // fallback to default + try { + return callable.call(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @SuppressWarnings("removal") + private T invoke(Callable callable) { + assert EventQueue.isDispatchThread(); + if (executor == null) { + // init on EDT + AccessController.doPrivileged((PrivilegedAction)() -> + executor = new ThreadPoolExecutor(1, Integer.MAX_VALUE, + 60L, TimeUnit.SECONDS, + new SynchronousQueue<>(), + new ThreadFactory() { + private final ThreadFactory factory = Executors.privilegedThreadFactory(); + @Override + public Thread newThread(Runnable r) { + Thread t = factory.newThread(r); + t.setDaemon(true); + t.setName("AWT-InvokeOnToolkitHelper " + t.getName()); + return t; + } + }) + ); + } + LinkedBlockingQueue currentQueue; + synchronized (invocations) { + invocations.push(currentQueue = new LinkedBlockingQueue<>()); + } + + FutureTask task = new FutureTask(callable) { + @Override + protected void done() { + synchronized (invocations) { + // Done with the current queue, wake it up. + invocations.pop().add(new InvocationEvent(executor, () -> {})); + } + } + }; + executor.execute(task); + + try { + while (!task.isDone() || !currentQueue.isEmpty()) { + InvocationEvent event = currentQueue.poll(WAIT_LIMIT_SECONDS, TimeUnit.SECONDS); + if (event == null) { + new RuntimeException("Waiting for the invocation event timed out").printStackTrace(); + break; + } + event.dispatch(); + } + return task.isDone() ? task.get() : null; + + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Warning: the method is used by the implementation and should not be used by a client. + * + * Checks if there's an active InvokeOnToolkitHelper corresponding to the invocation's AppContext, + * adds the invocation to the InvokeOnToolkitHelper's queue and returns true. + * Otherwise does nothing and returns false. + */ + public static boolean offer(InvocationEvent invocation) { + Object source = invocation.getSource(); + + InvokeOnToolkitHelper invoker = (source instanceof Component) ? + getInstance((Component)source) : + getInstance(Toolkit.getDefaultToolkit().getSystemEventQueue()); + + if (invoker == null) return false; + + synchronized (invoker.invocations) { + if (!invoker.invocations.isEmpty()) { + invoker.invocations.peek().add(invocation); + return true; + } + } + return false; + } + + private static InvokeOnToolkitHelper getInstance(Component comp) { + if (comp == null) return null; + + AppContext appContext = SunToolkit.targetToAppContext(comp); + if (appContext == null) return null; + + return getInstance((EventQueue)appContext.get(AppContext.EVENT_QUEUE_KEY)); + } + + private static InvokeOnToolkitHelper getInstance(EventQueue eq) { + if (eq == null) return null; + + return getInstance(AWTAccessor.getEventQueueAccessor().getDispatchThread(eq)); + } + + private static InvokeOnToolkitHelper getInstance(Thread edt) { + if (edt == null) return null; + + return edt2invokerMap.computeIfAbsent(edt, key -> new InvokeOnToolkitHelper()); + } +}