| /******************************************************************************* |
| * Copyright (c) 2003, 2015 IBM Corporation 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: |
| * 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 class Queue { |
| private static final int BASE_SIZE = 8; |
| |
| protected Semaphore[] elements = new Semaphore[BASE_SIZE]; |
| |
| protected int head = 0; |
| |
| protected int tail = 0; |
| |
| /** |
| * Add the semaphore to the queue. |
| * @param element |
| */ |
| public synchronized void add(Semaphore 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; |
| Semaphore[] newElements = new Semaphore[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 Semaphore remove() { |
| if (tail == head) { |
| return null; |
| } |
| Semaphore 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 Semaphore[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 Semaphore currentWork = null; |
| |
| protected 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(Semaphore 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(); |
| Semaphore work; |
| while ((work = pendingWork.remove()) != null) { |
| //remember the old current work before replacing, to handle |
| //the nested waiting case (bug 76378) |
| Semaphore oldWork = currentWork; |
| try { |
| currentWork = work; |
| Runnable runnable = work.getRunnable(); |
| if (runnable != null) { |
| runnable.run(); |
| } |
| |
| } finally { |
| currentWork = oldWork; |
| work.release(); |
| } |
| } |
| } |
| |
| void interruptUI(Runnable runnable) { |
| reportInterruption(runnable); |
| display.getThread().interrupt(); |
| } |
| |
| boolean isLockOwner() { |
| return isLockOwnerThread(); |
| } |
| |
| boolean isUI() { |
| return (!display.isDisposed()) |
| && (display.getThread() == Thread.currentThread()); |
| } |
| |
| 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); |
| } |
| } |