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