mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
JBR-4119 UI freezes at sun.lwawt.macosx.CAccessibility.getChildrenAndRoles
(cherry picked from commit6ba79774d8) (cherry picked from commite1623dc301)
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user