JBR-4119 UI freezes at sun.lwawt.macosx.CAccessibility.getChildrenAndRoles

(cherry picked from commit 6ba79774d8)
(cherry picked from commit e1623dc301)
This commit is contained in:
Anton Tarasov
2022-02-18 20:27:06 +03:00
committed by jbrbot
parent f939a89783
commit 080ee524d6
9 changed files with 209 additions and 46 deletions

View File

@@ -177,7 +177,7 @@ public final class LWCToolkit extends LWToolkit {
= !Boolean.parseBoolean(
System.getProperty("javafx.embed.singleThread", "false"));
private static final PlatformLogger log = PlatformLogger.getLogger("sun.lwawt.macosx.LWCToolkit");
private static final PlatformLogger log = PlatformLogger.getLogger(LWCToolkit.class.getName());
public LWCToolkit() {
final String extraButtons = "sun.awt.enableExtraMouseButtons";
@@ -733,6 +733,20 @@ public final class LWCToolkit extends LWToolkit {
log.fine("invokeAndWait started: " + runnable);
}
if (isBlockingEventDispatchThread()) {
String msg = "invokeAndWait is discarded as the EventDispatch thread is currently blocked";
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine(msg, new Throwable());
log.fine(AWTThreading.getInstance(component).printEventDispatchThreadStackTrace().toString());
} else if (log.isLoggable(PlatformLogger.Level.INFO)) {
StackTraceElement[] stack = new Throwable().getStackTrace();
log.info(msg + ". Originated at " + stack[stack.length - 1]);
Thread dispatchThread = AWTThreading.getInstance(component).getEventDispatchThread();
log.info(dispatchThread.getName() + " at: " + dispatchThread.getStackTrace()[0].toString());
}
return;
}
boolean nonBlockingRunLoop;
synchronized (priorityInvocationPending) {
@@ -782,6 +796,8 @@ public final class LWCToolkit extends LWToolkit {
checkException(invocationEvent);
}
private static native boolean isBlockingEventDispatchThread();
public static void invokeLater(Runnable event, Component component)
throws InvocationTargetException {
Objects.requireNonNull(component, "Null component provided to invokeLater");

View File

@@ -623,6 +623,17 @@ JNI_COCOA_EXIT(env);
return result;
}
/*
* Class: sun_lwawt_macosx_LWCToolkit
* Method: isBlockingEventDispatchThread
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_LWCToolkit_isBlockingEventDispatchThread
(JNIEnv *env, jclass clz)
{
return ThreadUtilities.blockingEventDispatchThread;
}
/*
* Class: sun_lwawt_macosx_LWCToolkit
* Method: stopAWTRunLoop

View File

@@ -127,6 +127,13 @@ do { \
__attribute__((visibility("default")))
@interface ThreadUtilities : NSObject { } /* Extend NSObject so can call performSelectorOnMainThread */
/*
* When a blocking performSelectorOnMainThread is executed from the EventDispatch thread,
* and the executed code triggers an opposite blocking a11y call (via LWCToolkit.invokeAndWait)
* this is a deadlock case, and then this property is used to discard LWCToolkit.invokeAndWait.
*/
@property (class, nonatomic, readonly) BOOL blockingEventDispatchThread;
+ (JNIEnv*)getJNIEnv;
+ (JNIEnv*)getJNIEnvUncached;
+ (void)detachCurrentThread;

View File

@@ -51,6 +51,24 @@ static inline void attachCurrentThread(void** env) {
@implementation ThreadUtilities
static BOOL _blockingEventDispatchThread = NO;
static long eventDispatchThreadPtr = (long)nil;
static BOOL isEventDispatchThread() {
return (long)[NSThread currentThread] == eventDispatchThreadPtr;
}
// The [blockingEventDispatchThread] property is readonly, so we implement a private setter
static void setBlockingEventDispatchThread(BOOL value) {
assert([NSThread isMainThread]);
_blockingEventDispatchThread = value;
}
+ (BOOL) blockingEventDispatchThread {
assert([NSThread isMainThread]);
return _blockingEventDispatchThread;
}
+ (void)initialize {
/* All the standard modes plus ours */
javaModes = [[NSArray alloc] initWithObjects:NSDefaultRunLoopMode,
@@ -91,10 +109,10 @@ AWT_ASSERT_APPKIT_THREAD;
}
/* This is needed because we can't directly pass a block to
* performSelectorOnMainThreadWaiting .. since it expects a selector
* performSelectorOnMainThreadWaiting:..waitUntilDone:YES.. since it expects a selector
*/
+ (void)invokeBlock:(void (^)())block {
block();
block();
}
/*
@@ -125,7 +143,19 @@ AWT_ASSERT_APPKIT_THREAD;
if ([NSThread isMainThread] && wait == YES) {
[target performSelector:aSelector withObject:arg];
} else {
[target performSelectorOnMainThread:aSelector withObject:arg waitUntilDone:wait modes:javaModes];
if (wait && isEventDispatchThread()) {
void (^block)(void) = ^{
setBlockingEventDispatchThread(YES);
@try {
[target performSelector:aSelector withObject:arg];
} @finally {
setBlockingEventDispatchThread(NO);
}
};
[self performSelectorOnMainThread:@selector(invokeBlock:) withObject:block waitUntilDone:YES modes:javaModes];
} else {
[target performSelectorOnMainThread:aSelector withObject:arg waitUntilDone:wait modes:javaModes];
}
}
}
@@ -152,3 +182,16 @@ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CThreading_isMainThread
return [NSThread isMainThread];
}
/*
* Class: sun_awt_AWTThreading
* Method: notifyEventDispatchThreadStartedNative
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_sun_awt_AWTThreading_notifyEventDispatchThreadStartedNative
(JNIEnv *env, jclass c)
{
@synchronized([ThreadUtilities class]) {
eventDispatchThreadPtr = (long)[NSThread currentThread];
}
}

View File

@@ -31,6 +31,7 @@ import java.awt.event.WindowEvent;
import java.util.ArrayList;
import sun.awt.AWTThreading;
import sun.util.logging.PlatformLogger;
import sun.awt.dnd.SunDragSourceContextPeer;
@@ -86,6 +87,7 @@ class EventDispatchThread extends Thread {
}
public void run() {
AWTThreading.getInstance(Thread.currentThread()).notifyEventDispatchThreadStarted();
try {
pumpEvents(new Conditional() {
public boolean evaluate() {
@@ -97,6 +99,8 @@ class EventDispatchThread extends Thread {
}
}
private static native void registerEventDispatchThread();
void pumpEvents(Conditional cond) {
pumpEvents(ANY_EVENT, cond);
}

View File

@@ -5,9 +5,14 @@ import sun.util.logging.PlatformLogger;
import java.awt.*;
import java.awt.event.InvocationEvent;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Stack;
import java.util.List;
import java.util.concurrent.*;
@@ -361,6 +366,19 @@ public class AWTThreading {
return eventDispatchThread;
}
public StringWriter printEventDispatchThreadStackTrace() {
return printEventDispatchThreadStackTrace(new StringWriter());
}
public StringWriter printEventDispatchThreadStackTrace(StringWriter writer) {
assert writer != null;
var printer = new PrintWriter(writer);
Thread dispatchThread = getEventDispatchThread();
printer.println(dispatchThread.getName());
Arrays.asList(dispatchThread.getStackTrace()).forEach(frame -> printer.append("\tat ").println(frame));
return writer;
}
/**
* Sets {@code AWTThreading} instance factory.
* WARNING: for testing purpose.
@@ -369,6 +387,15 @@ public class AWTThreading {
theAWTThreadingFactory.set(factory);
}
/**
* Must be called on the EventDispatch thread.
*/
public void notifyEventDispatchThreadStarted() {
if (FontUtilities.isMacOSX) notifyEventDispatchThreadStartedNative();
}
private static native void notifyEventDispatchThreadStartedNative();
public void notifyEventDispatchThreadFree() {
List<CompletableFuture<Void>> notifiers = Collections.emptyList();
synchronized (eventDispatchThreadStateNotifiers) {

View File

@@ -142,16 +142,10 @@ public class AWTThreadingTest {
THREAD.start();
}
static void await(CountDownLatch latch, int seconds) {
if (!tryCall(() -> latch.await(seconds, TimeUnit.SECONDS), false)) {
FUTURE.completeExceptionally(new Throwable("Awaiting has timed out"));
}
}
static void dumpAllThreads() {
Thread.getAllStackTraces().keySet().forEach(t -> {
System.out.printf("%s\t%s\t%d\t%s\n", t.getName(), t.getState(), t.getPriority(), t.isDaemon() ? "Daemon" : "Normal");
Arrays.asList(t.getStackTrace()).forEach(frame -> System.out.println("\t" + frame));
Arrays.asList(t.getStackTrace()).forEach(frame -> System.out.println("\tat " + frame));
});
System.out.println("\n\n");
}

View File

@@ -39,8 +39,8 @@ import static helper.ToolkitTestHelper.*;
* @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
* @run main/othervm -Dsun.lwawt.macosx.LWCToolkit.invokeAndWait.disposeOnEDTFree=true LWCToolkitInvokeAndWaitTest
* @run main/othervm -Dlog.level.FINER=true -Dsun.lwawt.macosx.LWCToolkit.invokeAndWait.disposeOnEDTFree=true LWCToolkitInvokeAndWaitTest
* @author Anton Tarasov
*/
@SuppressWarnings("ConstantConditions")
@@ -81,78 +81,75 @@ public class LWCToolkitInvokeAndWaitTest {
}
public static void main(String[] args) {
loop(); // init the event threads
tryRun(() -> {
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<Class<?>> setLog = cls -> {
Logger log = LogManager.getLogManager().getLogger(cls.getName());
log.setUseParentHandlers(false);
log.addHandler(LOG_HANDLER);
if (Boolean.getBoolean("log.level.FINER")) {
log.setLevel(Level.FINER);
}
};
setLog.accept(AWTThreading.class);
setLog.accept(LWCToolkit.class);
});
Consumer<InvocationEvent> noop = e -> {};
initTest(LWCToolkitInvokeAndWaitTest.class);
testCase("InvocationEvent is normally dispatched", () -> test(
testCase("InvocationEvent is normally dispatched", () -> test1(
"",
noop,
() -> System.out.println("I'm dispatched")));
testCase("InvocationEvent is lost", () -> test(
testCase("InvocationEvent is lost", () -> test1(
"lost",
noop,
CONSUME_DISPATCHING));
EDT_FAST_FREE_LATCH = new CountDownLatch(2);
testCase("InvocationEvent is lost (EDT becomes fast free)", () -> test(
testCase("InvocationEvent is lost (EDT becomes fast free)", () -> test1(
"lost",
// notify the invocationEvent has been dispatched
invocationEvent -> EDT_FAST_FREE_LATCH.countDown(),
CONSUME_DISPATCHING));
testCase("InvocationEvent is disposed", () -> test(
testCase("InvocationEvent is disposed", () -> test1(
"disposed",
invocationEvent -> AWTAccessor.getInvocationEventAccessor().dispose(invocationEvent),
CONSUME_DISPATCHING));
testCase("InvocationEvent is timed out (delayed before dispatching)", () -> test(
testCase("InvocationEvent is timed out (delayed before dispatching)", () -> test1(
"timed out",
invocationEvent -> sleep(INVOKE_TIMEOUT_SECONDS * 4),
CONSUME_DISPATCHING));
testCase("InvocationEvent is timed out (delayed during dispatching)", () -> test(
testCase("InvocationEvent is timed out (delayed during dispatching)", () -> test1(
"timed out",
noop,
() -> sleep(INVOKE_TIMEOUT_SECONDS * 4)));
testCase("invokeAndWait is discarded", () -> test2(
"discarded"));
testCase("invokeAndWait is passed", LWCToolkitInvokeAndWaitTest::test3);
System.out.println("Test PASSED");
}
static void test(String expectedInLog,
Consumer<InvocationEvent> onBeforeDispatching,
Runnable onDispatching)
static void test1(String expectedInLog,
Consumer<InvocationEvent> onBeforeDispatching,
Runnable onDispatching)
{
EventQueue.invokeLater(() -> subTest(onBeforeDispatching, onDispatching));
EventQueue.invokeLater(() -> subTest1(onBeforeDispatching, onDispatching));
tryRun(() -> {
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);
tryRun(latch::await);
tryRun(() -> EventQueue.invokeAndWait(EMPTY_RUNNABLE));
if (!LOG_HANDLER.testContains(expectedInLog)) {
throw new RuntimeException("Test FAILED! (not found in the log: \"" + expectedInLog + "\")");
}
check(expectedInLog);
}
static void subTest(Consumer<InvocationEvent> onBeforeDispatching, Runnable onDispatching) {
static void subTest1(Consumer<InvocationEvent> onBeforeDispatching, Runnable onDispatching) {
Toolkit.getDefaultToolkit().getSystemEventQueue().push(new EventQueue() {
@Override
protected void dispatchEvent(AWTEvent event) {
@@ -180,6 +177,63 @@ public class LWCToolkitInvokeAndWaitTest {
}));
}
static void test2(String expectedInLog) {
EventQueue.invokeLater(() ->
//
// Blocking EDT.
//
LWCToolkit.performOnMainThreadAndWait(() -> {
//
// The invocation from AppKit should be discarded.
//
tryRun(() -> LWCToolkit.invokeAndWait(EMPTY_RUNNABLE, FRAME, INVOKE_TIMEOUT_SECONDS * 4));
FUTURE.complete(true);
}));
check(expectedInLog);
}
static void test3() {
var point = new CountDownLatch(1);
EventQueue.invokeLater(() -> {
// We're on EDT, wait for the second invocation to perform on AppKit.
await(point, INVOKE_TIMEOUT_SECONDS * 2);
// This should be dispatched in the RunLoop started by LWCToolkit.invokeAndWait from the second invocation.
LWCToolkit.performOnMainThreadAndWait(() -> FUTURE.complete(true));
});
LWCToolkit.performOnMainThreadAndWait(() -> {
// Notify we're on AppKit.
point.countDown();
// The LWCToolkit.invokeAndWait call starts a native RunLoop.
tryRun(() -> LWCToolkit.invokeAndWait(EMPTY_RUNNABLE, FRAME));
});
}
static void check(String expectedInLog) {
tryRun(() -> {
if (!FUTURE.get(INVOKE_TIMEOUT_SECONDS * 2L, TimeUnit.SECONDS)) {
throw new RuntimeException("Test FAILED! (negative result)");
}
});
loop(); // wait for the logging to be printed
if (!LOG_HANDLER.testContains(expectedInLog)) {
throw new RuntimeException("Test FAILED! (not found in the log: \"" + expectedInLog + "\")");
}
}
static void loop() {
tryRun(() -> EventQueue.invokeAndWait(EMPTY_RUNNABLE));
var latch = new CountDownLatch(1);
CThreading.executeOnAppKit(latch::countDown);
tryRun(latch::await);
}
static void sleep(int seconds) {
tryRun(() -> Thread.sleep(seconds * 1000L));
}

View File

@@ -28,6 +28,7 @@ import java.util.Optional;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("ConstantConditions")
@@ -123,6 +124,12 @@ public class ToolkitTestHelper {
return defValue;
}
public static void await(CountDownLatch latch, int seconds) {
if (!tryCall(() -> latch.await(seconds, TimeUnit.SECONDS), false)) {
FUTURE.completeExceptionally(new Throwable("Awaiting has timed out"));
}
}
public interface ThrowableRunnable {
void run() throws Exception;
}