Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrey Loskutov2020-09-26 18:21:35 -0400
committerAndrey Loskutov2020-10-02 14:16:25 -0400
commit7a711982ff599842f6c049d59d58f2fbc2bd4eb3 (patch)
tree3b4278d43f37eaf2ac9625ce2c53da93ac72b85f
parent2d4d69298c9fc8e5fcc7934b5ff262c0e16a6172 (diff)
downloadeclipse.platform.debug-I20201004-1800.tar.gz
eclipse.platform.debug-I20201004-1800.tar.xz
eclipse.platform.debug-I20201004-1800.zip
Changed SelectedResourceManager to avoid accessing UI thread via syncExec() directly from the calling thread (that may own locks on which UI thread is waiting already). To avoid deadlocks, call to the UI thread is executed from a dedicated thread that does not hold any locks with the max timeout of 10 seconds. In case the UI thread doesn't react in this time, an error with stack traces will be reported and the current call to SelectedResourceManager will return null or empty selection. Change-Id: I6f426900f71240afc4c0d5d51d692faea2aba1e3 Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
-rw-r--r--org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/stringsubstitution/SelectedResourceManager.java131
1 files changed, 101 insertions, 30 deletions
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/stringsubstitution/SelectedResourceManager.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/stringsubstitution/SelectedResourceManager.java
index 352a8d6cf..2df6c515a 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/stringsubstitution/SelectedResourceManager.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/stringsubstitution/SelectedResourceManager.java
@@ -13,9 +13,20 @@
*******************************************************************************/
package org.eclipse.debug.internal.ui.stringsubstitution;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
import java.util.Iterator;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.jface.text.ITextSelection;
@@ -23,6 +34,7 @@ import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IWorkbenchPage;
@@ -37,9 +49,16 @@ import org.eclipse.ui.IWorkbenchWindow;
*/
public class SelectedResourceManager {
+ // Limit in seconds to wait on UI for accessing data
+ private static final int MAX_UI_WAIT_TIME = 10;
+
// singleton
private static SelectedResourceManager fgDefault;
+ // Used to avoid deadlocks while accessing UI thread from non UI code
+ private static ExecutorService executor = Executors.newSingleThreadExecutor();
+
+
/**
* Returns the singleton resource selection manager
*
@@ -69,14 +88,11 @@ public class SelectedResourceManager {
* @since 3.3
*/
public IStructuredSelection getCurrentSelection() {
- if(DebugUIPlugin.getStandardDisplay().getThread().equals(Thread.currentThread())) {
- return getCurrentSelection0();
- }
- else {
- final IStructuredSelection[] selection = new IStructuredSelection[1];
- DebugUIPlugin.getStandardDisplay().syncExec(() -> selection[0] = getCurrentSelection0());
- return selection[0];
+ IStructuredSelection selection = getFromUI(this::getCurrentSelection0);
+ if (selection == null) {
+ selection = StructuredSelection.EMPTY;
}
+ return selection;
}
/**
@@ -85,7 +101,7 @@ public class SelectedResourceManager {
*
* @since 3.4
*/
- private IStructuredSelection getCurrentSelection0() {
+ IStructuredSelection getCurrentSelection0() {
IWorkbenchWindow window = DebugUIPlugin.getActiveWorkbenchWindow();
if(window != null) {
IWorkbenchPage page = window.getActivePage();
@@ -119,14 +135,8 @@ public class SelectedResourceManager {
* @return selected resource or <code>null</code>
*/
public IResource getSelectedResource() {
- if(DebugUIPlugin.getStandardDisplay().getThread().equals(Thread.currentThread())) {
- return getSelectedResource0();
- }
- else {
- final IResource[] resource = new IResource[1];
- DebugUIPlugin.getStandardDisplay().syncExec(() -> resource[0] = getSelectedResource0());
- return resource[0];
- }
+ IResource resource = getFromUI(this::getSelectedResource0);
+ return resource;
}
/**
@@ -177,14 +187,8 @@ public class SelectedResourceManager {
* @return the current text selection as a <code>String</code> or <code>null</code>
*/
public String getSelectedText() {
- if(DebugUIPlugin.getStandardDisplay().getThread().equals(Thread.currentThread())) {
- return getSelectedText0();
- }
- else {
- final String[] text = new String[1];
- DebugUIPlugin.getStandardDisplay().syncExec(() -> text[0] = getSelectedText0());
- return text[0];
- }
+ String text = getFromUI(this::getSelectedText0);
+ return text;
}
/**
@@ -224,14 +228,81 @@ public class SelectedResourceManager {
* @since 3.2
*/
public IWorkbenchWindow getActiveWindow() {
- if(DebugUIPlugin.getStandardDisplay().getThread().equals(Thread.currentThread())) {
- return DebugUIPlugin.getActiveWorkbenchWindow();
+ IWorkbenchWindow window = getFromUI(DebugUIPlugin::getActiveWorkbenchWindow);
+ return window;
+ }
+
+ private <T> T getFromUI(Callable<T> call) {
+ try {
+ if (Display.getCurrent() != null) {
+ return call.call();
+ } else {
+ return runInUIThreadWithTimeout(call, MAX_UI_WAIT_TIME, TimeUnit.SECONDS);
+ }
+ } catch (TimeoutException e) {
+ reportTimeout();
+ return null;
+ } catch (Exception e) {
+ DebugUIPlugin.log(e);
+ return null;
}
- else {
- final IWorkbenchWindow[] window = new IWorkbenchWindow[1];
- DebugUIPlugin.getStandardDisplay().syncExec(() -> window[0] = DebugUIPlugin.getActiveWorkbenchWindow());
- return window[0];
+ }
+
+ /**
+ * Tries to run the task in the UI thread, and gives up if UI thread does not
+ * answer after given timeout
+ *
+ * @param timeout to wait for the UI lock
+ * @return may return null
+ * @throws Exception
+ */
+ static <V> V runInUIThreadWithTimeout(Callable<V> callable, long timeout, TimeUnit units) throws Exception {
+ FutureTask<V> task = new FutureTask<>(() -> syncExec(callable));
+ executor.execute(task);
+ return task.get(timeout, units);
+ }
+
+ static <V> V syncExec(Callable<V> callable) throws Exception {
+ AtomicReference<V> ref = new AtomicReference<>();
+ AtomicReference<Exception> ex = new AtomicReference<>();
+ DebugUIPlugin.getStandardDisplay().syncExec(() -> {
+ try {
+ ref.set(callable.call());
+ } catch (Exception e) {
+ ex.set(e);
+ }
+ });
+ if (ex.get() != null) {
+ throw ex.get();
}
+ return ref.get();
}
+ /**
+ * Reports an error the log with thread stack information for current and UI threads
+ */
+ private static void reportTimeout() {
+ Thread nonUiThread = Thread.currentThread();
+
+ String msg = "To avoid deadlock while executing Display.syncExec() from a non UI thread '" //$NON-NLS-1$
+ + nonUiThread.getName() + "', operation was cancelled."; //$NON-NLS-1$
+ MultiStatus main = new MultiStatus(DebugUIPlugin.getUniqueIdentifier(), IStatus.ERROR, msg, null);
+
+ ThreadInfo[] threads = ManagementFactory.getThreadMXBean().getThreadInfo(
+ new long[] { nonUiThread.getId(), Display.getDefault().getThread().getId() }, true, true);
+
+ for (ThreadInfo info : threads) {
+ String childMsg;
+ if (info.getThreadId() == nonUiThread.getId()) {
+ childMsg = nonUiThread.getName() + " thread probably holding a lock and trying to acquire UI lock"; //$NON-NLS-1$
+ } else {
+ childMsg = "UI thread waiting on a job or lock."; //$NON-NLS-1$
+ }
+ Exception childEx = new IllegalStateException("Call stack for thread " + info.getThreadName()); //$NON-NLS-1$
+ childEx.setStackTrace(info.getStackTrace());
+ main.add(DebugUIPlugin.newErrorStatus(childMsg, childEx));
+ }
+
+ DebugUIPlugin.log(main);
+ }
}

Back to the top