blob: 3642f3cc6b927bf615ab8940d00128bebd02b7d0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2006 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 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;
}
public void aboutToRelease() {
if (isUI()) {
ui = null;
}
}
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);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.LockListener#canBlock()
*/
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() {
display.getThread().interrupt();
}
boolean isLockOwner() {
return isLockOwnerThread();
}
boolean isUI() {
return (!display.isDisposed())
&& (display.getThread() == Thread.currentThread());
}
boolean isUIWaiting() {
return (ui != null) && (Thread.currentThread() != ui);
}
}