Bug 506090 - [jobs] UILockListener should report error if interrupting
UI thread
UILockListener will now report a multi-status ERROR containing 2
children providing stack traces for the current and UI thread.
Change-Id: Ic81485738ee64ed05abaaa77834dcdb4c13cb6e2
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/UILockListener.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/UILockListener.java
index 47e5084..f06226c 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/UILockListener.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/UILockListener.java
@@ -12,6 +12,11 @@
*******************************************************************************/
package org.eclipse.ui.internal;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.LockListener;
import org.eclipse.swt.widgets.Display;
@@ -169,7 +174,8 @@
}
}
- void interruptUI() {
+ void interruptUI(Runnable runnable) {
+ reportInterruption(runnable);
display.getThread().interrupt();
}
@@ -185,4 +191,35 @@
boolean isUIWaiting() {
return (ui != null) && (Thread.currentThread() != ui);
}
+
+ /**
+ * Adds a 'UI thread interrupted' message to the log with extra lock state
+ * and thread stack information.
+ */
+ private void reportInterruption(Runnable runnable) {
+ Thread nonUiThread = Thread.currentThread();
+
+ String msg = "To avoid deadlock while executing Display.syncExec() with argument: " //$NON-NLS-1$
+ + runnable + ", thread " + nonUiThread.getName() //$NON-NLS-1$
+ + " will interrupt UI thread."; //$NON-NLS-1$
+ MultiStatus main = new MultiStatus(WorkbenchPlugin.PI_WORKBENCH, IStatus.ERROR, msg,
+ new IllegalStateException());
+
+ ThreadInfo[] threads = ManagementFactory.getThreadMXBean().getThreadInfo(new long[] { nonUiThread.getId(), display.getThread().getId() }, true, true);
+
+ for (ThreadInfo info : threads) {
+ String childMsg;
+ if (info.getThreadId() == nonUiThread.getId()) {
+ // see org.eclipse.core.internal.jobs.LockManager.isLockOwner()
+ childMsg = nonUiThread.getName() + " thread is an instance of Worker or owns an ILock"; //$NON-NLS-1$
+ } else {
+ childMsg = "UI thread waiting on a job or lock."; //$NON-NLS-1$
+ }
+ childMsg += " Stack: \n" + info.toString(); //$NON-NLS-1$
+ Status child = new Status(IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, IStatus.ERROR, childMsg, null);
+ main.add(child);
+ }
+
+ WorkbenchPlugin.log(main);
+ }
}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/UISynchronizer.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/UISynchronizer.java
index 7b608b5..c07ba4e 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/UISynchronizer.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/UISynchronizer.java
@@ -159,7 +159,7 @@
//before it can serve the asyncExec to do the pending work
do {
if (lockListener.isUIWaiting()) {
- lockListener.interruptUI();
+ lockListener.interruptUI(runnable);
}
} while (!work.acquire(1000));
} catch (InterruptedException e) {
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/concurrency/SyncExecWhileUIThreadWaitsForLock.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/concurrency/SyncExecWhileUIThreadWaitsForLock.java
index a60dbd1..1c07eb4 100644
--- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/concurrency/SyncExecWhileUIThreadWaitsForLock.java
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/concurrency/SyncExecWhileUIThreadWaitsForLock.java
@@ -11,11 +11,17 @@
package org.eclipse.ui.tests.concurrency;
-import org.eclipse.swt.widgets.Display;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.eclipse.core.runtime.ILogListener;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.jobs.ILock;
-
import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.internal.WorkbenchPlugin;
import junit.framework.TestCase;
@@ -25,6 +31,32 @@
* UISynchronizer and UILockListener conspire to prevent deadlock in this case.
*/
public class SyncExecWhileUIThreadWaitsForLock extends TestCase {
+
+ private List<IStatus> reportedErrors;
+ private ILogListener listener;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ reportedErrors = new ArrayList<>();
+ listener = new ILogListener() {
+
+ @Override
+ public void logging(IStatus status, String plugin) {
+ reportedErrors.add(status);
+ }
+ };
+ WorkbenchPlugin.getDefault().getLog().addLogListener(listener);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (listener != null) {
+ WorkbenchPlugin.getDefault().getLog().removeLogListener(listener);
+ }
+ super.tearDown();
+ }
+
public void testDeadlock() {
final ILock lock = Job.getJobManager().newLock();
final boolean[] blocked = new boolean[] {false};
@@ -88,5 +120,10 @@
}
}
//if we get here, the test succeeded
+
+ assertEquals("Unexpected error count reported: " + reportedErrors, 1, reportedErrors.size());
+ MultiStatus status = (MultiStatus) reportedErrors.get(0);
+ assertEquals("Unexpected child status count reported: " + Arrays.toString(status.getChildren()), 2,
+ status.getChildren().length);
}
}