| /******************************************************************************* |
| * Copyright (c) 2003, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM - Initial API and implementation |
| * Jeremiah Lott (jeremiah.lott@timesys.com) - fix for deadlock bug 76378 |
| * |
| *******************************************************************************/ |
| 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; |
| |
| /** |
| * The UI lock listener is used to prevent the UI thread from deadlocking on a |
| * lock when the thread owning the lock is attempting to syncExec. |
| */ |
| public class UILockListener extends LockListener { |
| |
| /** |
| * The Queue is the construct that keeps track of Semaphores. |
| */ |
| public static class Queue { |
| private static final int BASE_SIZE = 8; |
| |
| protected PendingSyncExec[] elements = new PendingSyncExec[BASE_SIZE]; |
| |
| protected int head = 0; |
| |
| protected int tail = 0; |
| |
| /** |
| * Add the semaphore to the queue. |
| * |
| * @param element |
| */ |
| public synchronized void add(PendingSyncExec element) { |
| int newTail = increment(tail); |
| if (newTail == head) { |
| grow(); |
| newTail = tail + 1; |
| } |
| elements[tail] = element; |
| tail = newTail; |
| } |
| |
| private void grow() { |
| int newSize = elements.length * 2; |
| PendingSyncExec[] newElements = new PendingSyncExec[newSize]; |
| if (tail >= head) { |
| System.arraycopy(elements, head, newElements, head, size()); |
| } else { |
| int newHead = newSize - (elements.length - head); |
| System.arraycopy(elements, 0, newElements, 0, tail + 1); |
| System.arraycopy(elements, head, newElements, newHead, (newSize - newHead)); |
| head = newHead; |
| } |
| elements = newElements; |
| } |
| |
| private int increment(int index) { |
| return (index == (elements.length - 1)) ? 0 : index + 1; |
| } |
| |
| /** |
| * Remove the next semaphore to be woken up. |
| * |
| * @return |
| */ |
| public synchronized PendingSyncExec remove() { |
| if (tail == head) { |
| return null; |
| } |
| PendingSyncExec result = elements[head]; |
| elements[head] = null; |
| head = increment(head); |
| // reset the queue if it is empty and it has grown |
| if (tail == head && elements.length > BASE_SIZE) { |
| elements = new PendingSyncExec[BASE_SIZE]; |
| tail = head = 0; |
| } |
| return result; |
| } |
| |
| private int size() { |
| return tail > head ? (tail - head) : ((elements.length - head) + tail); |
| } |
| } |
| |
| protected Display display; |
| |
| protected final Queue pendingWork = new Queue(); |
| |
| protected PendingSyncExec currentWork = null; |
| |
| /** |
| * Points to the UI thread if it is currently waiting on a lock or null |
| */ |
| protected volatile Thread ui; |
| |
| /** |
| * Create a new instance of the receiver. |
| * |
| * @param display |
| */ |
| public UILockListener(Display display) { |
| this.display = display; |
| } |
| |
| @Override |
| public void aboutToRelease() { |
| if (isUI()) { |
| ui = null; |
| } |
| } |
| |
| @Override |
| public boolean aboutToWait(Thread lockOwner) { |
| if (isUI()) { |
| // If a syncExec was executed from the current operation, it |
| // has already acquired the lock. So, just return true. |
| if (currentWork != null && currentWork.getOperationThread() == lockOwner) { |
| return true; |
| } |
| ui = Thread.currentThread(); |
| try { |
| doPendingWork(); |
| } finally { |
| // UI field may be nulled if there is a nested wait during execution |
| // of pending work, so make sure it is assigned before we start waiting |
| ui = Thread.currentThread(); |
| } |
| } |
| return false; |
| } |
| |
| void addPendingWork(PendingSyncExec work) { |
| pendingWork.add(work); |
| } |
| |
| @Override |
| public boolean canBlock() { |
| return !isUI(); |
| } |
| |
| /** |
| * Should always be called from the UI thread. |
| */ |
| void doPendingWork() { |
| // Clear the interrupt flag that we may have set in interruptUI() |
| Thread.interrupted(); |
| PendingSyncExec work; |
| while ((work = pendingWork.remove()) != null) { |
| // Remember the old current work before replacing, to handle |
| // the nested waiting case (bug 76378) |
| PendingSyncExec oldWork = currentWork; |
| try { |
| currentWork = work; |
| work.run(); |
| } finally { |
| currentWork = oldWork; |
| } |
| } |
| } |
| |
| void interruptUI(Runnable runnable) { |
| reportInterruption(runnable); |
| display.getThread().interrupt(); |
| } |
| |
| boolean isLockOwner() { |
| return isLockOwnerThread(); |
| } |
| |
| boolean isUI() { |
| return (!display.isDisposed()) && (display.getThread() == Thread.currentThread()); |
| } |
| |
| boolean isUIWaiting() { |
| Thread localUi = ui; |
| return (localUi != null) && (Thread.currentThread() != localUi); |
| } |
| |
| /** |
| * 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, null); |
| |
| 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$ |
| } |
| Exception childEx = new IllegalStateException("Call stack for thread " + info.getThreadName()); //$NON-NLS-1$ |
| childEx.setStackTrace(info.getStackTrace()); |
| Status child = new Status(IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, IStatus.ERROR, childMsg, childEx); |
| main.add(child); |
| } |
| |
| WorkbenchPlugin.log(main); |
| } |
| } |