Bug 501681 - Deadlock in WorkbenchErrorHandler.handle
Fix a deadlock situation introduced via bug 241244 commit
1904f477320dac4a9f4d45f7be478efba4a0b735.
If a job code wants to handle a status in a "blocking" way, submit an
async task for UI thread to avoid deadlocks.
Change-Id: I3d86408fefd324db41ab87494ed98f62d05131c9
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/statushandlers/StatusManager.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/statushandlers/StatusManager.java
index 0e232ef..a84203e 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/statushandlers/StatusManager.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/statushandlers/StatusManager.java
@@ -54,8 +54,8 @@
* <li>SHOW - a style indicating that handlers should show a problem to an user
* without blocking the calling method while awaiting user response. This is
* generally done using a non modal {@link Dialog}</li>
- * <li>BLOCK - a style indicating that the handling should block the calling
- * method until the user has responded. This is generally done using a modal
+ * <li>BLOCK - a style indicating that the handling should block the UI
+ * until the user has responded. This is generally done using a modal
* window such as a {@link Dialog}</li>
* </ul>
* </p>
@@ -102,7 +102,7 @@
* therefore likely but not required that the <code>StatusHandler</code>
* would achieve this through the use of a modal dialog.
* </p><p>Due to the fact
- * that use of <code>BLOCK</code> will block any thread, care should be
+ * that use of <code>BLOCK</code> will block UI, care should be
* taken in this use of this flag.
* </p>
*/
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/statushandlers/WorkbenchErrorHandler.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/statushandlers/WorkbenchErrorHandler.java
index 30a35a2..4fed5d8 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/statushandlers/WorkbenchErrorHandler.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/statushandlers/WorkbenchErrorHandler.java
@@ -48,22 +48,7 @@
if (Display.getCurrent() != null) {
showStatusAdapter(statusAdapter, block);
} else {
- if (block) {
- Display.getDefault().syncExec(new Runnable() {
- @Override
- public void run() {
- showStatusAdapter(statusAdapter, true);
- }
- });
-
- } else {
- Display.getDefault().asyncExec(new Runnable() {
- @Override
- public void run() {
- showStatusAdapter(statusAdapter, false);
- }
- });
- }
+ Display.getDefault().asyncExec(() -> showStatusAdapter(statusAdapter, block));
}
}
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/concurrency/FreezeMonitor.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/concurrency/FreezeMonitor.java
new file mode 100644
index 0000000..9bff464
--- /dev/null
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/concurrency/FreezeMonitor.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.ui.tests.concurrency;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+
+import org.eclipse.core.runtime.ICoreRunnable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.jobs.Job;
+
+public class FreezeMonitor {
+
+ private static /* @Nullable */ Job monitorJob;
+
+ public static void expectCompletionIn(final long millis) {
+ done();
+ monitorJob = Job.create("FreezeMonitor", new ICoreRunnable() {
+ @Override
+ public void run(IProgressMonitor monitor) {
+ if (monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ StringBuilder result = new StringBuilder();
+ result.append("Possible frozen test case\n");
+ ThreadMXBean threadStuff = ManagementFactory.getThreadMXBean();
+ ThreadInfo[] allThreads = threadStuff.getThreadInfo(threadStuff.getAllThreadIds(), 200);
+ for (ThreadInfo threadInfo : allThreads) {
+ result.append("\"");
+ result.append(threadInfo.getThreadName());
+ result.append("\": ");
+ result.append(threadInfo.getThreadState());
+ result.append("\n");
+ final StackTraceElement[] elements = threadInfo.getStackTrace();
+ for (StackTraceElement element : elements) {
+ result.append(" ");
+ result.append(element);
+ result.append("\n");
+ }
+ result.append("\n");
+ }
+ System.out.println(result.toString());
+ }
+ });
+ monitorJob.schedule(millis);
+ }
+
+ public static void done() {
+ if (monitorJob != null) {
+ monitorJob.cancel();
+ monitorJob = null;
+ }
+ }
+}
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/statushandlers/StatusDialogManagerTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/statushandlers/StatusDialogManagerTest.java
index 9f133da..26653c8 100644
--- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/statushandlers/StatusDialogManagerTest.java
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/statushandlers/StatusDialogManagerTest.java
@@ -12,6 +12,10 @@
package org.eclipse.ui.tests.statushandlers;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
@@ -34,6 +38,7 @@
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
@@ -49,8 +54,10 @@
import org.eclipse.ui.statushandlers.IStatusAdapterConstants;
import org.eclipse.ui.statushandlers.StatusAdapter;
import org.eclipse.ui.statushandlers.StatusManager;
+import org.eclipse.ui.statushandlers.StatusManager.INotificationListener;
import org.eclipse.ui.statushandlers.WorkbenchErrorHandler;
import org.eclipse.ui.statushandlers.WorkbenchStatusDialogManager;
+import org.eclipse.ui.tests.concurrency.FreezeMonitor;
import org.eclipse.ui.tests.harness.util.UITestCase;
import junit.framework.TestCase;
@@ -73,11 +80,12 @@
@Override
protected void setUp() throws Exception {
+ super.setUp();
automatedMode = ErrorDialog.AUTOMATED_MODE;
wsdm = new WorkbenchStatusDialogManager(null, null);
ErrorDialog.AUTOMATED_MODE = false;
wsdm.setProperty(IStatusDialogConstants.ANIMATION, Boolean.FALSE);
- super.setUp();
+ FreezeMonitor.expectCompletionIn(60_000);
}
public void testBlockingAppearance() {
@@ -1049,6 +1057,100 @@
assertTrue("Custom support area provider should be consulted", consulted[0]);
}
+ public void testDeadlockFromBug501681() throws Exception {
+ assertNotNull("Test must run in UI thread", Display.getCurrent());
+
+ final StatusAdapter statusAdapter = createStatusAdapter("Oops");
+ AtomicReference<StatusAdapter[]> reported = new AtomicReference<>();
+ INotificationListener listener = (type, adapters) -> {
+ reported.set(adapters);
+ };
+
+ AtomicReference<ReentrantLock> lock = new AtomicReference<>();
+ lock.set(new ReentrantLock());
+ final Semaphore semaphore = new Semaphore(0);
+
+ // This simulates a thread locked some shared resource
+ // It will release lock only if "wait" is set to "false"
+ Thread t = new Thread(() -> {
+ lock.get().lock();
+ try {
+ semaphore.acquire();
+ } catch (InterruptedException e) {
+ // continue
+ }
+ lock.get().unlock();
+ });
+ t.start();
+
+ // Wait for thread to acquire the lock
+ while (!lock.get().isLocked()) {
+ Thread.sleep(50);
+ }
+ assertTrue(lock.get().isLocked());
+
+ // Verify status handling works (without blocking)
+ StatusManager.getManager().addListener(listener);
+ StatusManager.getManager().handle(statusAdapter, StatusManager.SHOW | StatusManager.LOG);
+ assertSame(statusAdapter, reported.get()[0]);
+ Shell shell = StatusDialogUtil.getStatusShell();
+ if (shell != null) {
+ shell.dispose();
+ }
+ reported.set(null);
+
+ // Verify status handling works with blocking
+ final StatusAdapter statusAdapter2 = createStatusAdapter("Oops2");
+ try {
+ // this job will try to report some "blocking" status
+ // if it does NOT deadlock, the "wait" will be unset and lock will
+ // be released
+ Job badJob = new Job("Will report blocking error") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ StatusManager.getManager().handle(statusAdapter2, StatusManager.BLOCK | StatusManager.LOG);
+ // stop blocking thread
+ semaphore.release();
+ return Status.OK_STATUS;
+ }
+ };
+
+ // start later so that we have time to lock ourselves
+ badJob.schedule(100);
+
+ // This will now block UI until the lock is released
+ // Without the patch for bug 501681 this will never happen
+ // because the job we start in a moment will wait for UI thread to
+ // show the blocking dialog
+ lock.get().lock();
+
+ // make sure job was done
+ badJob.join();
+ } finally {
+ // Will "unblock" the blocking dialog shown by the job, must be
+ // posted with delay, because dialog is not yet shown
+ postUnblockingTask();
+
+ // Allow the blocking dialog to be shown
+ UITestCase.processEvents();
+ StatusManager.getManager().removeListener(listener);
+ }
+ assertFalse("Job should successfully finish", semaphore.hasQueuedThreads());
+ assertNotNull("Status adapter was not logged", reported.get());
+ assertSame(statusAdapter2, reported.get()[0]);
+ }
+
+ private void postUnblockingTask() {
+ Display.getCurrent().timerExec(100, () -> {
+ Shell shell = StatusDialogUtil.getStatusShellImmediately();
+ if (shell != null) {
+ shell.dispose();
+ } else {
+ // shell not shown yet? re-post
+ postUnblockingTask();
+ }
+ });
+ }
/**
* Delivers custom support area.
@@ -1238,6 +1340,7 @@
@Override
protected void tearDown() throws Exception {
Shell shell = StatusDialogUtil.getStatusShell();
+ FreezeMonitor.done();
if (shell != null) {
shell.dispose();
WorkbenchStatusDialogManagerImpl impl = (WorkbenchStatusDialogManagerImpl) wsdm
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/statushandlers/StatusDialogUtil.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/statushandlers/StatusDialogUtil.java
index ff82c48..96fa614 100644
--- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/statushandlers/StatusDialogUtil.java
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/statushandlers/StatusDialogUtil.java
@@ -59,6 +59,10 @@
public static Shell getStatusShell(){
UITestCase.waitForJobs(100, 1000);
+ return getStatusShellImmediately();
+ }
+
+ public static Shell getStatusShellImmediately() {
Shell[] shells = Display.getDefault().getShells();
for (int i = 0; i < shells.length; i++) {
if (shells[i].getText().equals("Problem Occurred")
diff --git a/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF
index 5e73ce0..649e040 100644
--- a/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF
+++ b/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
Bundle-ManifestVersion: 2
Bundle-Name: Eclipse UI Tests
Bundle-SymbolicName: org.eclipse.ui.tests; singleton:=true
-Bundle-Version: 3.11.2.qualifier
+Bundle-Version: 3.12.0.qualifier
Eclipse-BundleShape: dir
Bundle-Activator: org.eclipse.ui.tests.TestPlugin
Bundle-Vendor: Eclipse.org
diff --git a/tests/org.eclipse.ui.tests/pom.xml b/tests/org.eclipse.ui.tests/pom.xml
index 2637ef9..448e428 100644
--- a/tests/org.eclipse.ui.tests/pom.xml
+++ b/tests/org.eclipse.ui.tests/pom.xml
@@ -19,7 +19,7 @@
</parent>
<groupId>org.eclipse.ui</groupId>
<artifactId>org.eclipse.ui.tests</artifactId>
- <version>3.11.2-SNAPSHOT</version>
+ <version>3.12.0-SNAPSHOT</version>
<packaging>eclipse-test-plugin</packaging>
<properties>