blob: 80ea71366dadad29cfc0fe027ebe862fdc5c1db7 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}