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:
Anton Tarasov
2022-01-28 21:06:08 +03:00
committed by jbrbot
parent 40ee1cc29d
commit db70fa521c
7 changed files with 478 additions and 87 deletions

View File

@@ -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;

View File

@@ -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)) {

View File

@@ -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];

View File

@@ -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

View File

@@ -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 {

View File

@@ -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) {

View 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;
}
}
}