mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
JBR-4208 LWCToolkit.invokeAndWait should not stuck on invocation loss
including JBR-4543 (NPE: IdeEventQueue.lambda$getNextEvent$0)
(cherry picked from commit b16d847947)
This commit is contained in:
@@ -37,13 +37,12 @@ import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.lang.annotation.Native;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
@@ -76,8 +75,7 @@ import sun.swing.SwingAccessor;
|
||||
|
||||
class CAccessibility implements PropertyChangeListener {
|
||||
private static Set<String> ignoredRoles;
|
||||
private static final int INVOKE_TIMEOUT_SECONDS_DEFAULT = 1;
|
||||
private static /*final*/ int INVOKE_TIMEOUT_SECONDS;
|
||||
private static final int INVOKE_TIMEOUT_SECONDS = Integer.getInteger("sun.lwawt.macosx.CAccessibility.invokeTimeoutSeconds", 1);
|
||||
|
||||
static {
|
||||
loadAWTLibrary();
|
||||
@@ -87,8 +85,6 @@ class CAccessibility implements PropertyChangeListener {
|
||||
private static void loadAWTLibrary() {
|
||||
// Need to load the native library for this code.
|
||||
System.loadLibrary("awt");
|
||||
INVOKE_TIMEOUT_SECONDS = Integer.getInteger("sun.lwawt.macosx.CAccessibility.invokeTimeoutSeconds",
|
||||
INVOKE_TIMEOUT_SECONDS_DEFAULT);
|
||||
}
|
||||
|
||||
static CAccessibility sAccessibility;
|
||||
|
||||
@@ -88,6 +88,7 @@ import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -651,30 +652,12 @@ public final class LWCToolkit extends LWToolkit {
|
||||
return invokeAndWait(callable, component, -1);
|
||||
}
|
||||
|
||||
static <T> T invokeAndWait(final Callable<T> callable, Component component, int timeoutSeconds) throws Exception {
|
||||
public static <T> T invokeAndWait(final Callable<T> callable, Component component, int timeoutSeconds) throws Exception {
|
||||
final CallableWrapper<T> wrapper = new CallableWrapper<>(callable);
|
||||
invokeAndWait(wrapper, component, timeoutSeconds);
|
||||
return wrapper.getResult();
|
||||
}
|
||||
|
||||
static final class CancelableRunnable implements Runnable {
|
||||
volatile Runnable runnable;
|
||||
|
||||
public CancelableRunnable(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Runnable r = runnable;
|
||||
if (r != null) r.run();
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
runnable = null;
|
||||
}
|
||||
}
|
||||
|
||||
static final class CallableWrapper<T> implements Runnable {
|
||||
final Callable<T> callable;
|
||||
T object;
|
||||
@@ -742,7 +725,7 @@ public final class LWCToolkit extends LWToolkit {
|
||||
invokeAndWait(runnable, component, -1);
|
||||
}
|
||||
|
||||
static void invokeAndWait(Runnable runnable, Component component, int timeoutSeconds)
|
||||
public static void invokeAndWait(Runnable runnable, Component component, int timeoutSeconds)
|
||||
throws InvocationTargetException
|
||||
{
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
@@ -750,24 +733,17 @@ public final class LWCToolkit extends LWToolkit {
|
||||
}
|
||||
|
||||
boolean nonBlockingRunLoop;
|
||||
CancelableRunnable cancelableRunnable = new CancelableRunnable(runnable);
|
||||
|
||||
synchronized (priorityInvocationPending) {
|
||||
nonBlockingRunLoop = priorityInvocationPending.get();
|
||||
if (!nonBlockingRunLoop) blockingRunLoopCounter.incrementAndGet();
|
||||
}
|
||||
|
||||
final long mediator = createAWTRunLoopMediator();
|
||||
AWTThreading.TrackedInvocationEvent invocationEvent =
|
||||
AWTThreading.createAndTrackInvocationEvent(component, runnable, true);
|
||||
|
||||
InvocationEvent invocationEvent =
|
||||
AWTThreading.createAndTrackInvocationEvent(component,
|
||||
cancelableRunnable,
|
||||
() -> {
|
||||
if (mediator != 0) {
|
||||
stopAWTRunLoop(mediator);
|
||||
}
|
||||
},
|
||||
true);
|
||||
long mediator = createAWTRunLoopMediator();
|
||||
invocationEvent.onDone(() -> stopAWTRunLoop(mediator));
|
||||
|
||||
if (component != null) {
|
||||
AppContext appContext = SunToolkit.targetToAppContext(component);
|
||||
@@ -781,10 +757,21 @@ public final class LWCToolkit extends LWToolkit {
|
||||
((LWCToolkit)Toolkit.getDefaultToolkit()).getSystemEventQueueForInvokeAndWait().postEvent(invocationEvent);
|
||||
}
|
||||
|
||||
CompletableFuture<Void> eventDispatchThreadFreeFuture =
|
||||
AWTThreading.getInstance(component).onEventDispatchThreadFree(() -> {
|
||||
if (!invocationEvent.isDone()) {
|
||||
// EventQueue is now empty but the posted InvocationEvent is still not dispatched,
|
||||
// consider it lost then.
|
||||
invocationEvent.dispose("InvocationEvent was lost");
|
||||
}
|
||||
});
|
||||
|
||||
invocationEvent.onDone(() -> eventDispatchThreadFreeFuture.cancel(false));
|
||||
|
||||
if (!doAWTRunLoop(mediator, nonBlockingRunLoop, timeoutSeconds)) {
|
||||
new Throwable("Invocation timed out (" + timeoutSeconds + "sec)").printStackTrace();
|
||||
cancelableRunnable.cancel();
|
||||
invocationEvent.dispose("InvocationEvent has timed out");
|
||||
}
|
||||
|
||||
if (!nonBlockingRunLoop) blockingRunLoopCounter.decrementAndGet();
|
||||
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
|
||||
@@ -635,6 +635,8 @@ JNI_COCOA_ENTER(env);
|
||||
|
||||
AWTRunLoopObject* mediatorObject = (AWTRunLoopObject*)jlong_to_ptr(mediator);
|
||||
|
||||
if (mediatorObject == nil) return;
|
||||
|
||||
[ThreadUtilities performOnMainThread:@selector(endRunLoop) on:mediatorObject withObject:nil waitUntilDone:NO];
|
||||
|
||||
[mediatorObject release];
|
||||
|
||||
@@ -344,6 +344,7 @@ public class EventQueue {
|
||||
if (shouldNotify) {
|
||||
if (theEvent.getSource() != AWTAutoShutdown.getInstance()) {
|
||||
AWTAutoShutdown.getInstance().notifyThreadBusy(dispatchThread);
|
||||
AWTThreading.getInstance(dispatchThread).notifyEventDispatchThreadBusy();
|
||||
}
|
||||
pushPopCond.signalAll();
|
||||
} else if (notifyID) {
|
||||
@@ -556,6 +557,7 @@ public class EventQueue {
|
||||
return event;
|
||||
}
|
||||
AWTAutoShutdown.getInstance().notifyThreadFree(dispatchThread);
|
||||
AWTThreading.getInstance(dispatchThread).notifyEventDispatchThreadFree();
|
||||
pushPopCond.await();
|
||||
} finally {
|
||||
pushPopLock.unlock();
|
||||
@@ -1070,11 +1072,15 @@ public class EventQueue {
|
||||
pushPopLock.lock();
|
||||
try {
|
||||
if (dispatchThread == null && !threadGroup.isDestroyed() && !appContext.isDisposed()) {
|
||||
EventDispatchThread t = new EventDispatchThread(threadGroup, name, EventQueue.this);
|
||||
EventDispatchThread t =
|
||||
new EventDispatchThread(threadGroup,
|
||||
name,
|
||||
EventQueue.this);
|
||||
t.setContextClassLoader(classLoader);
|
||||
t.setPriority(Thread.NORM_PRIORITY + 1);
|
||||
t.setDaemon(false);
|
||||
AWTAutoShutdown.getInstance().notifyThreadBusy(t);
|
||||
AWTThreading.getInstance(t).notifyEventDispatchThreadBusy();
|
||||
dispatchThread = t;
|
||||
dispatchThread.start();
|
||||
}
|
||||
@@ -1102,6 +1108,7 @@ public class EventQueue {
|
||||
dispatchThread = null;
|
||||
}
|
||||
AWTAutoShutdown.getInstance().notifyThreadFree(edt);
|
||||
AWTThreading.getInstance(edt).notifyEventDispatchThreadFree();
|
||||
/*
|
||||
* Event was posted after EDT events pumping had stopped, so start
|
||||
* another EDT to handle this event
|
||||
|
||||
@@ -9,13 +9,26 @@ import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Used to perform a cross threads (EventDispatch, Toolkit) execution so that the execution does not cause a deadlock.
|
||||
*
|
||||
* Note: the log messages are tested by jdk/jb/java/awt/Toolkit/LWCToolkitInvokeAndWaitTest.java
|
||||
*/
|
||||
public class AWTThreading {
|
||||
private static final PlatformLogger logger = PlatformLogger.getLogger("sun.awt.AWTThreading");
|
||||
private static final PlatformLogger logger = PlatformLogger.getLogger(AWTThreading.class.getName());
|
||||
|
||||
private static final Runnable EMPTY_RUNNABLE = () -> {};
|
||||
|
||||
private static final AtomicReference<Function<Thread, AWTThreading>> theAWTThreadingFactory =
|
||||
new AtomicReference<>(AWTThreading::new);
|
||||
|
||||
private final Thread eventDispatchThread;
|
||||
|
||||
private ExecutorService executor;
|
||||
// every invokeAndWait() pushes a queue of invocations
|
||||
@@ -23,13 +36,21 @@ public class AWTThreading {
|
||||
|
||||
private int level; // re-entrance level
|
||||
|
||||
private final List<CompletableFuture<Void>> eventDispatchThreadStateNotifiers = new ArrayList<>();
|
||||
private volatile boolean isEventDispatchThreadFree;
|
||||
|
||||
// invocations should be dispatched on proper EDT (per AppContext)
|
||||
private static final Map<Thread, AWTThreading> EDT_TO_INSTANCE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class TrackingQueue extends LinkedBlockingQueue<InvocationEvent> {}
|
||||
|
||||
private AWTThreading() {}
|
||||
/**
|
||||
* WARNING: for testing purpose, use {@link AWTThreading#getInstance(Thread)} instead.
|
||||
*/
|
||||
public AWTThreading(Thread edt) {
|
||||
eventDispatchThread = edt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a callable from EventDispatch thread (EDT). It's assumed the callable either performs a blocking execution on Toolkit
|
||||
@@ -175,8 +196,15 @@ public class AWTThreading {
|
||||
* <li>If the event is first dispatched from EventQueue - it gets removed from the tracking queue.
|
||||
* <li>If the event is first dispatched from the tracking queue - its dispatching on EventQueue will be noop.
|
||||
* <ul>
|
||||
*
|
||||
* @param source the source of the event
|
||||
* @param onDispatched called back on event dispatching
|
||||
* @param catchThrowables should catch Throwable's or propagate to the EventDispatch thread's loop
|
||||
*/
|
||||
public static InvocationEvent createAndTrackInvocationEvent(Object source, Runnable runnable, Runnable listener, boolean catchThrowables) {
|
||||
public static TrackedInvocationEvent createAndTrackInvocationEvent(Object source,
|
||||
Runnable onDispatched,
|
||||
boolean catchThrowables)
|
||||
{
|
||||
AWTThreading instance = getInstance(source);
|
||||
if (instance != null) {
|
||||
synchronized (instance.invocations) {
|
||||
@@ -184,65 +212,208 @@ public class AWTThreading {
|
||||
instance.invocations.push(new TrackingQueue());
|
||||
}
|
||||
final TrackingQueue queue = instance.invocations.peek();
|
||||
final InvocationEvent[] eventRef = new InvocationEvent[1];
|
||||
final AtomicReference<TrackedInvocationEvent> eventRef = new AtomicReference<>();
|
||||
|
||||
queue.add(eventRef[0] = new InvocationEvent(
|
||||
source,
|
||||
runnable,
|
||||
// Wrap the original completion listener so that it:
|
||||
// - guarantees a single run either from dispatch or dispose
|
||||
// - removes the invocation event from the tracking queue
|
||||
new Runnable() {
|
||||
WeakReference<TrackingQueue> queueRef = new WeakReference<>(queue);
|
||||
eventRef.set(TrackedInvocationEvent.create(
|
||||
source,
|
||||
onDispatched,
|
||||
new Runnable() {
|
||||
final WeakReference<TrackingQueue> queueRef = new WeakReference<>(queue);
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (queueRef != null) {
|
||||
if (listener != null) {
|
||||
listener.run();
|
||||
}
|
||||
TrackingQueue q = queueRef.get();
|
||||
if (q != null) {
|
||||
q.remove(eventRef[0]);
|
||||
}
|
||||
queueRef = null;
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
TrackingQueue queue = queueRef.get();
|
||||
queueRef.clear();
|
||||
if (queue != null) {
|
||||
queue.remove(eventRef.get());
|
||||
}
|
||||
},
|
||||
catchThrowables)
|
||||
{
|
||||
@Override
|
||||
public void dispatch() {
|
||||
if (!isDispatched()) {
|
||||
super.dispatch();
|
||||
}
|
||||
}
|
||||
});
|
||||
return eventRef[0];
|
||||
},
|
||||
catchThrowables));
|
||||
|
||||
queue.add(eventRef.get());
|
||||
return eventRef.get();
|
||||
}
|
||||
}
|
||||
return new InvocationEvent(source, runnable, listener, catchThrowables);
|
||||
return TrackedInvocationEvent.create(source, onDispatched, () -> {}, catchThrowables);
|
||||
}
|
||||
|
||||
private static AWTThreading getInstance(Object obj) {
|
||||
if (obj == null) return null;
|
||||
@SuppressWarnings("serial")
|
||||
public static class TrackedInvocationEvent extends InvocationEvent {
|
||||
private final long creationTime = System.currentTimeMillis();
|
||||
private final Throwable throwable = new Throwable();
|
||||
private final CompletableFuture<Void> futureResult = new CompletableFuture<>();
|
||||
|
||||
// dispatched or disposed
|
||||
private final AtomicBoolean isFinished = new AtomicBoolean(false);
|
||||
|
||||
static TrackedInvocationEvent create(Object source,
|
||||
Runnable onDispatch,
|
||||
Runnable onDone,
|
||||
boolean catchThrowables)
|
||||
{
|
||||
final AtomicReference<TrackedInvocationEvent> eventRef = new AtomicReference<>();
|
||||
eventRef.set(new TrackedInvocationEvent(
|
||||
source,
|
||||
onDispatch,
|
||||
() -> {
|
||||
if (onDone != null) {
|
||||
onDone.run();
|
||||
}
|
||||
TrackedInvocationEvent thisEvent = eventRef.get();
|
||||
if (!thisEvent.isDispatched()) {
|
||||
// If we're here - this {onDone} is being disposed.
|
||||
thisEvent.finishIfNotYet(() ->
|
||||
// If we're here - this {onDone} is called by the outer AWTAccessor.getInvocationEventAccessor().dispose()
|
||||
// which we do not control, so complete here.
|
||||
thisEvent.futureResult.completeExceptionally(new Throwable("InvocationEvent was disposed"))
|
||||
);
|
||||
}
|
||||
},
|
||||
catchThrowables));
|
||||
return eventRef.get();
|
||||
}
|
||||
|
||||
protected TrackedInvocationEvent(Object source, Runnable onDispatched, Runnable onDone, boolean catchThrowables) {
|
||||
super(source,
|
||||
Optional.of(onDispatched).orElse(EMPTY_RUNNABLE),
|
||||
Optional.of(onDone).orElse(EMPTY_RUNNABLE),
|
||||
catchThrowables);
|
||||
|
||||
futureResult.whenComplete((r, ex) -> {
|
||||
if (ex != null) {
|
||||
String message = ex.getMessage() + " (awaiting " + (System.currentTimeMillis() - creationTime) + " ms)";
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
ex.initCause(throwable);
|
||||
logger.fine(message, ex);
|
||||
} else if (logger.isLoggable(PlatformLogger.Level.INFO)) {
|
||||
StackTraceElement[] stack = throwable.getStackTrace();
|
||||
logger.info(message + ". Originated at " + stack[stack.length - 1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch() {
|
||||
finishIfNotYet(super::dispatch);
|
||||
futureResult.complete(null);
|
||||
}
|
||||
|
||||
public void dispose(String reason) {
|
||||
finishIfNotYet(() -> AWTAccessor.getInvocationEventAccessor().dispose(this));
|
||||
futureResult.completeExceptionally(new Throwable(reason));
|
||||
}
|
||||
|
||||
private void finishIfNotYet(Runnable finish) {
|
||||
if (!isFinished.getAndSet(true)) {
|
||||
finish.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the event is dispatched or disposed.
|
||||
*/
|
||||
public boolean isDone() {
|
||||
return futureResult.isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the runnable when it's done (immediately if it's done).
|
||||
*/
|
||||
public void onDone(Runnable runnable) {
|
||||
futureResult.whenComplete((r, ex) -> Optional.of(runnable).orElse(EMPTY_RUNNABLE).run());
|
||||
}
|
||||
}
|
||||
|
||||
public static AWTThreading getInstance(Object obj) {
|
||||
if (obj == null) {
|
||||
return getInstance(Toolkit.getDefaultToolkit().getSystemEventQueue());
|
||||
}
|
||||
|
||||
AppContext appContext = SunToolkit.targetToAppContext(obj);
|
||||
if (appContext == null) return null;
|
||||
if (appContext == null) {
|
||||
return getInstance(Toolkit.getDefaultToolkit().getSystemEventQueue());
|
||||
}
|
||||
|
||||
return getInstance((EventQueue)appContext.get(AppContext.EVENT_QUEUE_KEY));
|
||||
}
|
||||
|
||||
private static AWTThreading getInstance(EventQueue eq) {
|
||||
public static AWTThreading getInstance(EventQueue eq) {
|
||||
if (eq == null) return null;
|
||||
|
||||
return getInstance(AWTAccessor.getEventQueueAccessor().getDispatchThread(eq));
|
||||
}
|
||||
|
||||
private static AWTThreading getInstance(Thread edt) {
|
||||
if (edt == null) return null;
|
||||
public static AWTThreading getInstance(Thread edt) {
|
||||
assert edt != null;
|
||||
|
||||
return EDT_TO_INSTANCE_MAP.computeIfAbsent(edt, key -> new AWTThreading());
|
||||
return EDT_TO_INSTANCE_MAP.computeIfAbsent(edt, key -> theAWTThreadingFactory.get().apply(edt));
|
||||
}
|
||||
|
||||
public Thread getEventDispatchThread() {
|
||||
return eventDispatchThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@code AWTThreading} instance factory.
|
||||
* WARNING: for testing purpose.
|
||||
*/
|
||||
public static void setAWTThreadingFactory(Function<Thread, AWTThreading> factory) {
|
||||
theAWTThreadingFactory.set(factory);
|
||||
}
|
||||
|
||||
public void notifyEventDispatchThreadFree() {
|
||||
List<CompletableFuture<Void>> notifiers = Collections.emptyList();
|
||||
synchronized (eventDispatchThreadStateNotifiers) {
|
||||
isEventDispatchThreadFree = true;
|
||||
if (eventDispatchThreadStateNotifiers.size() > 0) {
|
||||
notifiers = List.copyOf(eventDispatchThreadStateNotifiers);
|
||||
}
|
||||
}
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINER)) {
|
||||
logger.finer("notifyEventDispatchThreadFree");
|
||||
}
|
||||
// notify callbacks out of the synchronization block
|
||||
notifiers.forEach(f -> f.complete(null));
|
||||
}
|
||||
|
||||
public void notifyEventDispatchThreadBusy() {
|
||||
synchronized (eventDispatchThreadStateNotifiers) {
|
||||
isEventDispatchThreadFree = false;
|
||||
}
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINER)) {
|
||||
logger.finer("notifyEventDispatchThreadBusy");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a callback and returns a {@code CompletableFuture} reporting the case when the associated EventDispatch thread
|
||||
* has gone sleeping and stopped dispatching events because of empty EventQueue. If the EventDispatch thread is free
|
||||
* at the moment then the callback is called immediately on the caller's thread and the future completes.
|
||||
*/
|
||||
public CompletableFuture<Void> onEventDispatchThreadFree(Runnable runnable) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
future.thenRun(Optional.of(runnable).orElse(EMPTY_RUNNABLE));
|
||||
|
||||
if (!isEventDispatchThreadFree) {
|
||||
synchronized (eventDispatchThreadStateNotifiers) {
|
||||
if (!isEventDispatchThreadFree) {
|
||||
eventDispatchThreadStateNotifiers.add(future);
|
||||
future.whenComplete((r, ex) -> {
|
||||
synchronized (eventDispatchThreadStateNotifiers) {
|
||||
eventDispatchThreadStateNotifiers.remove(future);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isLoggable(PlatformLogger.Level.FINER)) {
|
||||
logger.finer("onEventDispatchThreadFree: free at the moment");
|
||||
}
|
||||
future.complete(null);
|
||||
return future;
|
||||
}
|
||||
|
||||
public interface Task {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import java.awt.*;
|
||||
import javax.swing.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -9,11 +9,9 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import sun.awt.InvokeOnToolkitHelper;
|
||||
import sun.lwawt.macosx.CThreading;
|
||||
import sun.lwawt.macosx.LWCToolkit;
|
||||
|
||||
import javax.swing.*;
|
||||
import sun.awt.AWTThreading;
|
||||
|
||||
/*
|
||||
* @test
|
||||
@@ -50,6 +48,7 @@ public class AWTThreadingTest {
|
||||
|
||||
static void runGui() {
|
||||
frame = new JFrame("frame");
|
||||
frame.getContentPane().setBackground(Color.green);
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setSize(200, 200);
|
||||
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
@@ -88,7 +87,7 @@ public class AWTThreadingTest {
|
||||
EventQueue.invokeLater(() -> {
|
||||
passed.set(false);
|
||||
|
||||
Boolean success = InvokeOnToolkitHelper.invokeAndBlock(() -> {
|
||||
Boolean success = AWTThreading.executeWaitToolkit(() -> {
|
||||
try {
|
||||
return CThreading.executeOnAppKit(() -> Boolean.TRUE);
|
||||
} catch (Throwable e) {
|
||||
|
||||
229
test/jdk/jb/java/awt/Toolkit/LWCToolkitInvokeAndWaitTest.java
Normal file
229
test/jdk/jb/java/awt/Toolkit/LWCToolkitInvokeAndWaitTest.java
Normal file
@@ -0,0 +1,229 @@
|
||||
import java.awt.*;
|
||||
import javax.swing.*;
|
||||
import java.awt.event.InvocationEvent;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.*;
|
||||
|
||||
import sun.awt.AWTAccessor;
|
||||
import sun.awt.AWTThreading;
|
||||
import sun.lwawt.macosx.CThreading;
|
||||
import sun.lwawt.macosx.LWCToolkit;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Tests different scenarios for LWCToolkit.invokeAndWait().
|
||||
* @requires (os.family == "mac")
|
||||
* @modules java.desktop/sun.lwawt.macosx java.desktop/sun.awt
|
||||
* @run main LWCToolkitInvokeAndWaitTest
|
||||
* @run main/othervm -DAWTThreading.level.FINER=true LWCToolkitInvokeAndWaitTest
|
||||
* @author Anton Tarasov
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public class LWCToolkitInvokeAndWaitTest {
|
||||
// This property is used in {CAccessibility}
|
||||
private static final int INVOKE_TIMEOUT_SECONDS = Integer.getInteger("sun.lwawt.macosx.CAccessibility.invokeTimeoutSeconds", 1);
|
||||
|
||||
static TestLogHandler LOG_HANDLER = new TestLogHandler();
|
||||
static volatile CompletableFuture<Boolean> FUTURE;
|
||||
static volatile CountDownLatch EDT_FAST_FREE_LATCH;
|
||||
static volatile JFrame FRAME;
|
||||
static volatile Thread MAIN_THREAD;
|
||||
static int TEST_COUNTER;
|
||||
|
||||
static final Runnable CONSUME_DISPATCHING = () -> FUTURE.completeExceptionally(new Throwable("Unexpected dispatching!"));
|
||||
|
||||
static {
|
||||
AWTThreading.setAWTThreadingFactory(edt -> new AWTThreading(edt) {
|
||||
@Override
|
||||
public CompletableFuture<Void> onEventDispatchThreadFree(Runnable runnable) {
|
||||
if (EDT_FAST_FREE_LATCH != null) {
|
||||
// 1. wait for the invocation event to be dispatched
|
||||
// 2. wait for EDT to become free
|
||||
trycatch(() -> EDT_FAST_FREE_LATCH.await());
|
||||
|
||||
EDT_FAST_FREE_LATCH = null;
|
||||
}
|
||||
// if EDT is free the runnable should be called immediately
|
||||
return super.onEventDispatchThreadFree(runnable);
|
||||
}
|
||||
@Override
|
||||
public void notifyEventDispatchThreadFree() {
|
||||
if (EDT_FAST_FREE_LATCH != null &&
|
||||
// if the invocation event is dispatched by now
|
||||
EDT_FAST_FREE_LATCH.getCount() == 1)
|
||||
{
|
||||
EDT_FAST_FREE_LATCH.countDown();
|
||||
}
|
||||
super.notifyEventDispatchThreadFree();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
MAIN_THREAD = Thread.currentThread();
|
||||
|
||||
trycatch(() -> {
|
||||
Logger log = LogManager.getLogManager().getLogger(AWTThreading.class.getName());
|
||||
log.setUseParentHandlers(false);
|
||||
log.addHandler(LOG_HANDLER);
|
||||
if (Boolean.getBoolean("AWTThreading.level.FINER")) {
|
||||
log.setLevel(Level.FINER);
|
||||
}
|
||||
});
|
||||
|
||||
Consumer<InvocationEvent> noop = e -> {};
|
||||
|
||||
try {
|
||||
trycatch(() -> EventQueue.invokeAndWait(LWCToolkitInvokeAndWaitTest::runGui));
|
||||
|
||||
test("InvocationEvent is normally dispatched",
|
||||
"",
|
||||
noop,
|
||||
() -> System.out.println("I'm dispatched"));
|
||||
|
||||
test("InvocationEvent is lost",
|
||||
"lost",
|
||||
noop,
|
||||
CONSUME_DISPATCHING);
|
||||
|
||||
EDT_FAST_FREE_LATCH = new CountDownLatch(2);
|
||||
test("InvocationEvent is lost (EDT becomes fast free)",
|
||||
"lost",
|
||||
// notify the invocationEvent has been dispatched
|
||||
invocationEvent -> EDT_FAST_FREE_LATCH.countDown(),
|
||||
CONSUME_DISPATCHING);
|
||||
|
||||
test("InvocationEvent is disposed",
|
||||
"disposed",
|
||||
invocationEvent -> AWTAccessor.getInvocationEventAccessor().dispose(invocationEvent),
|
||||
CONSUME_DISPATCHING);
|
||||
|
||||
test("InvocationEvent is timed out (delayed before dispatching)",
|
||||
"timed out",
|
||||
invocationEvent -> sleep(INVOKE_TIMEOUT_SECONDS * 4),
|
||||
CONSUME_DISPATCHING);
|
||||
|
||||
test("InvocationEvent is timed out (delayed during dispatching)",
|
||||
"timed out",
|
||||
noop,
|
||||
() -> sleep(INVOKE_TIMEOUT_SECONDS * 4));
|
||||
|
||||
} finally {
|
||||
FRAME.dispose();
|
||||
}
|
||||
System.out.println("Test PASSED");
|
||||
}
|
||||
|
||||
static void runGui() {
|
||||
FRAME = new JFrame(LWCToolkitInvokeAndWaitTest.class.getSimpleName());
|
||||
FRAME.getContentPane().setBackground(Color.green);
|
||||
FRAME.setLocationRelativeTo(null);
|
||||
FRAME.setSize(200, 200);
|
||||
FRAME.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
FRAME.setVisible(true);
|
||||
}
|
||||
|
||||
static void test(String testCaption,
|
||||
String expectedInLog,
|
||||
Consumer<InvocationEvent> onBeforeDispatching,
|
||||
Runnable onDispatching)
|
||||
{
|
||||
System.out.println("\n(" + (++TEST_COUNTER) + ") TEST: " + testCaption);
|
||||
|
||||
FUTURE = new CompletableFuture<>();
|
||||
FUTURE.whenComplete((r, ex) -> Optional.of(ex).ifPresent(Throwable::printStackTrace));
|
||||
|
||||
EventQueue.invokeLater(() -> subTest(onBeforeDispatching, onDispatching));
|
||||
|
||||
trycatch(() -> {
|
||||
if (!FUTURE.get(INVOKE_TIMEOUT_SECONDS * 2L, TimeUnit.SECONDS)) {
|
||||
throw new RuntimeException("Test FAILED! (negative result)");
|
||||
}
|
||||
});
|
||||
|
||||
// let AppKit and EDT print all the logging
|
||||
var latch = new CountDownLatch(1);
|
||||
CThreading.executeOnAppKit(latch::countDown);
|
||||
trycatch(latch::await);
|
||||
trycatch(() -> EventQueue.invokeAndWait(() -> {}));
|
||||
|
||||
if (!LOG_HANDLER.testContains(expectedInLog)) {
|
||||
throw new RuntimeException("Test FAILED! (not found in the log: \"" + expectedInLog + "\")");
|
||||
}
|
||||
|
||||
System.out.println("(" + TEST_COUNTER + ") SUCCEEDED\n");
|
||||
}
|
||||
|
||||
static void subTest(Consumer<InvocationEvent> onBeforeDispatching, Runnable onDispatching) {
|
||||
Toolkit.getDefaultToolkit().getSystemEventQueue().push(new EventQueue() {
|
||||
@Override
|
||||
protected void dispatchEvent(AWTEvent event) {
|
||||
//
|
||||
// Intercept the invocation posted from Appkit.
|
||||
//
|
||||
if (event instanceof AWTThreading.TrackedInvocationEvent) {
|
||||
System.out.println("Before dispatching: " + event);
|
||||
onBeforeDispatching.accept((InvocationEvent)event);
|
||||
|
||||
if (onDispatching == CONSUME_DISPATCHING) {
|
||||
System.out.println("Consuming: " + event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
CThreading.executeOnAppKit(() -> trycatch(() -> {
|
||||
//
|
||||
// Post an invocation from AppKit.
|
||||
//
|
||||
LWCToolkit.invokeAndWait(onDispatching, FRAME, INVOKE_TIMEOUT_SECONDS);
|
||||
FUTURE.complete(true);
|
||||
}));
|
||||
}
|
||||
|
||||
static void sleep(int seconds) {
|
||||
trycatch(() -> Thread.sleep(seconds * 1000L));
|
||||
}
|
||||
|
||||
static void trycatch(ThrowableRunnable runnable) {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
if (Thread.currentThread() == MAIN_THREAD) {
|
||||
throw new RuntimeException("Test FAILED!", e);
|
||||
} else {
|
||||
FUTURE.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ThrowableRunnable {
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
static class TestLogHandler extends StreamHandler {
|
||||
public StringBuilder buffer = new StringBuilder();
|
||||
|
||||
public TestLogHandler() {
|
||||
// Use System.out to merge with the test printing.
|
||||
super(System.out, new SimpleFormatter());
|
||||
setLevel(Level.ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
buffer.append(record.getMessage());
|
||||
super.publish(record);
|
||||
flush();
|
||||
}
|
||||
|
||||
public boolean testContains(String str) {
|
||||
boolean contains = buffer.toString().contains(str);
|
||||
buffer.setLength(0);
|
||||
return contains;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user