diff options
author | Andrey Loskutov | 2020-09-26 22:21:35 +0000 |
---|---|---|
committer | Andrey Loskutov | 2020-10-02 18:16:25 +0000 |
commit | 7a711982ff599842f6c049d59d58f2fbc2bd4eb3 (patch) | |
tree | 3b4278d43f37eaf2ac9625ce2c53da93ac72b85f | |
parent | 2d4d69298c9fc8e5fcc7934b5ff262c0e16a6172 (diff) | |
download | eclipse.platform.debug-7a711982ff599842f6c049d59d58f2fbc2bd4eb3.tar.gz eclipse.platform.debug-7a711982ff599842f6c049d59d58f2fbc2bd4eb3.tar.xz eclipse.platform.debug-7a711982ff599842f6c049d59d58f2fbc2bd4eb3.zip |
Bug 565033 - Deadlock opening Ant build fileY20201008-1200Y20201006-1200S4_18_0_M1aS4_18_0_M1I20201012-0320I20201009-0430I20201007-1800I20201007-1320I20201007-0600I20201006-1800I20201006-0840I20201006-0600I20201005-1800I20201005-0600I20201004-1800I20201004-0600I20201003-1800I20201003-0600I20201002-1800
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.java | 131 |
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); + } } |