/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.util;
import java.awt.EventQueue;
import java.util.*;
import java.lang.reflect.InvocationTargetException;
import org.openide.ErrorManager;
/** Read-many/write-one lock.
* Allows control over resources that
* can be read by several readers at once but only written by one writer.
* <P>
* It is guaranteed that if you are a writer you can also enter the
* mutex as a reader. Conversely, if you are the <em>only</em> reader you
* are allowed to enter the mutex as a writer.
* <P>
* If the mutex is used only by one thread, the thread can repeatedly
* enter it as a writer or reader. So one thread can never deadlock itself,
* whichever order operations are performed in.
* <P>
* There is no strategy to prevent starvation.
* Even if there is a writer waiting to enter, another reader might enter
* the section instead.
* <P>
* Examples of use:
*
* <p><code><PRE>
* Mutex m = new Mutex ();
*
* // Grant write access, compute an integer and return it:
* return (Integer)m.writeAccess (new Mutex.Action () {
* public Object run () {
* return new Integer (1);
* }
* });
*
* // Obtain read access, do some computation, possibly throw an IOException:
* try {
* m.readAccess (new Mutex.ExceptionAction () {
* public Object run () throws IOException {
* if (...) throw new IOException ();
*
* return null;
* }
* });
* } catch (MutexException ex) {
* throw (IOException)ex.getException ();
* }
* </PRE></code>
*
* @author Ales Novak
*/
public final class Mutex extends Object {
/** Mutex that allows code to be synchronized with the AWT event dispatch thread. */
public static final Mutex EVENT = new Mutex ();
// lock mode constants
/** Lock free */
private static final int NONE = 0x0;
/** Enqueue all requests */
private static final int CHAIN = 0x1;
/** eXclusive */
private static final int X = 0x2;
/** Shared */
private static final int S = 0x3;
/** number of modes */
private static final int MODE_COUNT = 0x4;
/** compatibility matrix */ // [requested][granted]
private static final boolean[][] cmatrix = {null, null, // NONE, CHAIN
/* NONE */ /* CHAIN */ /* X */ /* S */ // granted
/*r X */ {true, false, false, false},
/*e S */ {true, false, false, true}
/*q */
//uested
};
/** Decides whether two locks are compatible.?
* @param granted?
* @param requested?
* @return <tt>true</tt> iff they are compatible?
*/
private static boolean compatibleLocks(int granted, int requested) {
return cmatrix[requested][granted];
}
/** granted mode */
private int grantedMode = NONE;
/** protects internal data structures */
private /*final*/ Object LOCK;
/** threads that - owns or waits for this mutex */
private /*final*/ Map registeredThreads;
/** number of threads that holds S mode (readersNo == "count of threads in registeredThreads that holds S") */ // NOI18N
private int readersNo = 0;
/** a queue of waiting threads for this mutex */
private List waiters;
/** Enhanced constructor that permits specifying an object to use as a lock.
* The lock is used on entry and exit to {@link #readAccess} and during the
* whole execution of {@link #writeAccess}. The ability to specify locks
* allows several <code>Mutex</code>es to synchronize on one object or to synchronize
* a mutex with another critical section.
*
* @param lock lock to use
*/
public Mutex (Object lock) {
init(lock);
}
/** Default constructor.
*/
public Mutex() {
init(new InternalLock());
}
/** @param privileged can enter privileged states of this Mutex
* This helps avoid creating of custom Runnables.
*/
public Mutex(Privileged privileged) {
if (privileged == null) {
throw new IllegalArgumentException("privileged == null"); //NOI18N
} else {
init(new InternalLock());
privileged.setParent(this);
}
}
/** Initiates this Mutex */
private void init(Object lock) {
this.LOCK = lock;
this.registeredThreads = new HashMap(7);
this.waiters = new LinkedList();
}
/** Run an action only with read access.
* See class description re. entering for write access within the dynamic scope.
* @param action the action to perform
* @return the object returned from {@link Mutex.Action#run}
*/
public Object readAccess (Action action) {
if (this == EVENT) {
try {
return doEventAccess (action);
} catch (MutexException e) {
InternalError err = new InternalError("Exception from non-Exception Action"); // NOI18N
ErrorManager.getDefault().annotate(err, e.getException());
throw err;
}
}
Thread t = Thread.currentThread();
readEnter(t);
try {
return action.run();
} finally {
leave(t);
}
}
/** Run an action with read access and possibly throw a checked exception.
* The exception if thrown is then encapsulated
* in a <code>MutexException</code> and thrown from this method. One is encouraged
* to catch <code>MutexException</code>, obtain the inner exception, and rethrow it.
* Here is an example:
* <p><code><PRE>
* try {
* mutex.readAccess (new ExceptionAction () {
* public void run () throws IOException {
* throw new IOException ();
* }
* });
* } catch (MutexException ex) {
* throw (IOException) ex.getException ();
* }
* </PRE></code>
* Note that <em>runtime exceptions</em> are always passed through, and neither
* require this invocation style, nor are encapsulated.
* @param action the action to execute
* @return the object returned from {@link Mutex.ExceptionAction#run}
* @exception MutexException encapsulates a user exception
* @exception RuntimeException if any runtime exception is thrown from the run method
* @see #readAccess(Mutex.Action)
*/
public Object readAccess (ExceptionAction action) throws MutexException {
if (this == EVENT) {
return doEventAccess (action);
}
Thread t = Thread.currentThread();
readEnter(t);
try {
return action.run();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new MutexException(e);
} catch (LinkageError e) {
// #20467
throw new MutexException(new InvocationTargetException(e));
} catch (StackOverflowError e) {
// #20467
throw new MutexException(new InvocationTargetException(e));
} finally {
leave(t);
}
}
/** Run an action with read access, returning no result.
* It may be run asynchronously.
*
* @param action the action to perform
* @see #readAccess(Mutex.Action)
*/
public void readAccess (final Runnable action) {
if (this == EVENT) {
doEvent (action);
return;
}
Thread t = Thread.currentThread();
readEnter(t);
try {
action.run();
} finally {
leave(t);
}
}
/** Run an action with write access.
* The same thread may meanwhile reenter the mutex; see the class description for details.
*
* @param action the action to perform
* @return the result of {@link Mutex.Action#run}
*/
public Object writeAccess (Action action) {
if (this == EVENT) {
try {
return doEventAccess (action);
} catch (MutexException e) {
InternalError err = new InternalError("Exception from non-Exception Action"); // NOI18N
ErrorManager.getDefault().annotate(err, e.getException());
throw err;
}
}
Thread t = Thread.currentThread();
writeEnter(t);
try {
return action.run();
} finally {
leave(t);
}
}
/** Run an action with write access and possibly throw an exception.
* Here is an example:
* <p><code><PRE>
* try {
* mutex.writeAccess (new ExceptionAction () {
* public void run () throws IOException {
* throw new IOException ();
* }
* });
* } catch (MutexException ex) {
* throw (IOException) ex.getException ();
* }
* </PRE></code>
*
* @param action the action to execute
* @return the result of {@link Mutex.ExceptionAction#run}
* @exception MutexException an encapsulated checked exception, if any
* @exception RuntimeException if a runtime exception is thrown in the action
* @see #writeAccess(Mutex.Action)
* @see #readAccess(Mutex.ExceptionAction)
*/
public Object writeAccess (ExceptionAction action) throws MutexException {
if (this == EVENT) {
return doEventAccess (action);
}
Thread t = Thread.currentThread();
writeEnter(t);
try {
return action.run();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new MutexException(e);
} catch (LinkageError e) {
// #20467
throw new MutexException(new InvocationTargetException(e));
} catch (StackOverflowError e) {
// #20467
throw new MutexException(new InvocationTargetException(e));
} finally {
leave(t);
}
}
/** Run an action with write access and return no result.
* It may be run asynchronously.
*
* @param action the action to perform
* @see #writeAccess(Mutex.Action)
* @see #readAccess(Runnable)
*/
public void writeAccess (final Runnable action) {
if (this == EVENT) {
doEvent (action);
return;
}
Thread t = Thread.currentThread();
writeEnter(t);
try {
action.run();
} finally {
leave(t);
}
}
/** Posts a read request. This request runs immediately iff
* this Mutex is in the shared mode or this Mutex is not contended
* at all.
*
* This request is delayed if this Mutex is in the exclusive
* mode and is held by this thread, until the exclusive is left.
*
* Finally, this request blocks, if this Mutex is in the exclusive
* mode and is held by another thread.
*
* <p><strong>Warning:</strong> this method blocks.</p>
*
* @param run runnable to run
*/
public void postReadRequest (final Runnable run) {
postRequest(S, run);
}
/** Posts a write request. This request runs immediately iff
* this Mutex is in the "pure" exclusive mode, i.e. this Mutex
* is not reentered in shared mode after the exclusive mode
* was acquired. Otherwise it is delayed until all read requests
* are executed.
*
* This request runs immediately if this Mutex is not contended at all.
*
* This request blocks if this Mutex is in the shared mode.
*
* <p><strong>Warning:</strong> this method blocks.</p>
* @param run runnable to run
*/
public void postWriteRequest (Runnable run) {
postRequest(X, run);
}
/** toString */
public String toString() {
if (this == EVENT) {
return "Mutex.EVENT"; // NOI18N
}
String newline = System.getProperty("line.separator");
StringBuffer sbuff = new StringBuffer(512);
synchronized(LOCK){
sbuff.append("threads: ").append(registeredThreads).append(newline); // NOI18N
sbuff.append("readersNo: ").append(readersNo).append(newline); // NOI18N
sbuff.append("waiters: ").append(waiters).append(newline); // NOI18N
sbuff.append("grantedMode: ").append(grantedMode).append(newline); // NOI18N
}
return sbuff.toString();
}
// priv methods -----------------------------------------
/** enters this mutex for writing */
private void writeEnter(Thread t) {
enter(X, t, true);
}
/** enters this mutex for reading */
private void readEnter(Thread t) {
enter(S, t, true);
}
/** enters this mutex with given mode
* @param requested one of S, X
* @param t
*/
private boolean enter(int requested, Thread t, boolean block) {
QueueCell cell = null;
int loopc = 0;
for (;;) {
loopc++;
synchronized (LOCK) {
// does the thread reenter this mutex?
ThreadInfo info = getThreadInfo(t);
if (info != null) {
if (grantedMode == NONE) {
// defensive
throw new IllegalStateException();
}
// reenters
// requested == S -> always succeeds
// info.mode == X -> always succeeds
if (((info.mode == S) && (grantedMode == X)) ||
((info.mode == X) && (grantedMode == S))) {
// defensive
throw new IllegalStateException();
}
if ((info.mode == X) ||
(info.mode == requested)) { // X - X, X - S, S - S
if (info.forced) {
info.forced = false;
} else {
info.counts[requested]++;
if ((requested == S) && (info.counts[requested] == 1)) {
readersNo++;
info.stamp = readersNo;
}
}
return true;
} else if (canUpgrade(info.mode, requested)) { // S - X and no holders
info.mode = X;
info.counts[requested]++;
info.rsnapshot = info.counts[S];
if (grantedMode == S) {
grantedMode = X;
} else if (grantedMode == X) {
// defensive
throw new IllegalStateException();
} // else if grantedMode == CHAIN - let it be
return true;
} else { // S - X and holders
if (Boolean.getBoolean("netbeans.debug.threads")) { // NOI18N
System.err.println("WARNING: Going from readAccess to writeAccess");
Thread.dumpStack();
}
// chain follows
}
} else { // first acquisition
if (isCompatible(requested)) { // NONE -> S,X or S -> S
grantedMode = requested;
registeredThreads.put(t, info = new ThreadInfo(t, requested));
if (requested == S) {
readersNo++;
info.stamp = readersNo;
}
return true;
} // else {
// granted is S and requested is X
// granted is X and requested is S or X
//}
}
if (! block) {
return false;
}
grantedMode = CHAIN;
cell = chain(requested, t, 0);
} // sync
cell.sleep();
} // for
}
/** privilegedEnter serves for processing posted requests */
private boolean reenter(Thread t, int mode, int stamp) {
// from leaveX -> grantedMode is NONE or S
if (mode == S) {
if (grantedMode != NONE && grantedMode != S) {
throw new IllegalStateException(this.toString());
}
enter(mode, t, true);
return false;
}
// assert (mode == X)
ThreadInfo tinfo = getThreadInfo(t);
boolean chainFromLeaveX = (grantedMode == CHAIN && tinfo != null && tinfo.counts[X] > 0);
// process grantedMode == X or CHAIN from leaveX OR grantedMode == NONE from leaveS
if (grantedMode == X || grantedMode == NONE || chainFromLeaveX) {
enter(mode, t, true);
return false;
} else { // remains grantedMode == CHAIN or S from leaveS, so it will be CHAIN
if (readersNo == 0) {
throw new IllegalStateException(this.toString());
}
ThreadInfo info = new ThreadInfo(t, mode);
registeredThreads.put(t, info);
// prevent from grantedMode == NONE (another thread - leaveS)
readersNo += 2;
info.stamp = stamp;
// prevent from new readers
grantedMode = CHAIN;
return true;
} // else X means ERROR!!!
}
/** @param t holds S (one entry) and wants X, grantedMode != NONE && grantedMode != X */
private void privilegedEnter(Thread t, int mode) {
boolean decrease = true;
ThreadInfo info;
synchronized (LOCK) {
info = getThreadInfo(t);
}
for (;;) {
QueueCell cell;
synchronized (LOCK) {
if (decrease) {
decrease = false;
readersNo -= 2;
}
// always chain this thread
// since there can be another one
// in the queue with higher priority
grantedMode = CHAIN;
cell = chain(mode, t, Integer.MAX_VALUE - info.stamp);
if (readersNo == 0) { // seems I may enter
// no one has higher prio?
if (waiters.get(0) == cell) {
waiters.remove(0);
return;
} else {
grantedMode = NONE;
wakeUpOthers();
}
}
} // synchronized (LOCK)
cell.sleep();
// cell already removed from waiters here
}
}
/** Leaves this mutex */
private void leave(Thread t) {
ThreadInfo info;
int postedMode = NONE;
boolean needLock = false;
synchronized (LOCK) {
info = getThreadInfo(t);
switch (grantedMode) {
case NONE:
throw new IllegalStateException();
case CHAIN:
if (info.counts[X] > 0) {
// it matters that X is handled first - see ThreadInfo.rsnapshot
postedMode = leaveX(info);
} else if (info.counts[S] > 0) {
postedMode = leaveS(info);
} else {
throw new IllegalStateException();
}
break;
case X:
postedMode = leaveX(info);
break;
case S:
postedMode = leaveS(info);
break;
} // switch
// do not give up LOCK until queued runnables are run
if (postedMode != NONE) {
int runsize = info.getRunnableCount(postedMode);
if (runsize != 0) {
needLock = reenter(t, postedMode, info.stamp); // grab lock
}
}
} // sync
// check posted requests
if (postedMode != NONE && info.getRunnableCount(postedMode) > 0) {
try {
if (needLock) { // go from S to X or CHAIN
privilegedEnter(t, postedMode);
}
// holds postedMode lock here
List runnables = info.dequeue(postedMode);
final int size = runnables.size();
for (int i = 0; i < size; i++) {
try {
Runnable r = (Runnable) runnables.get(i);
r.run();
} catch (Exception e) {
ErrorManager.getDefault().notify(e);
} catch (LinkageError e) {
// #20467
ErrorManager.getDefault().notify(e);
} catch (StackOverflowError e) {
// #20467
ErrorManager.getDefault().notify(e);
} // try
} // for
// help gc
runnables = null;
} finally {
leave(t); // release lock grabbed - shared
}
} // mode
}
/** Leaves the lock supposing that info.counts[X] is greater than zero */
private int leaveX(ThreadInfo info) {
if ((info.counts[X] <= 0) ||
(info.rsnapshot > info.counts[S])) {
// defensive
throw new IllegalStateException();
}
if (info.rsnapshot == info.counts[S]) {
info.counts[X]--;
if (info.counts[X] == 0) {
info.rsnapshot = 0;
// downgrade the lock
if (info.counts[S] > 0) {
info.mode = grantedMode = S;
} else {
info.mode = grantedMode = NONE;
registeredThreads.remove(info.t);
}
if (info.getRunnableCount(S) > 0) {
return S;
}
// mode has changed
wakeUpOthers();
}
} else {
// rsnapshot < counts[S]
if (info.counts[S] <= 0) {
// defensive
throw new IllegalStateException();
}
if (--info.counts[S] == 0) {
if (readersNo <= 0) {
throw new IllegalStateException();
}
readersNo--;
return X;
}
}
return NONE;
}
/** Leaves the lock supposing that info.counts[S] is greater than zero */
private int leaveS(ThreadInfo info) {
if ((info.counts[S] <= 0) ||
(info.counts[X] > 0)) {
// defensive
throw new IllegalStateException();
}
info.counts[S]--;
if (info.counts[S] == 0) {
// remove the thread
info.mode = NONE;
registeredThreads.remove(info.t);
// downsize readersNo
if (readersNo <= 0) {
throw new IllegalStateException();
}
readersNo--;
if (readersNo == 0) {
// set grantedMode to NONE
// and then wakeUp others - either immediately
// or in privelegedEnter()
grantedMode = NONE;
if (info.getRunnableCount(X) > 0) {
return X;
}
wakeUpOthers();
} else if (info.getRunnableCount(X) > 0) {
return X;
} else if ((grantedMode == CHAIN) &&
(readersNo == 1)) {
// can be the mode advanced from CHAIN? Examine first item of waiters!
for (int i = 0; i < waiters.size(); i++) {
QueueCell qc = (QueueCell) waiters.get(i);
synchronized (qc) {
if (qc.isGotOut()) {
waiters.remove(i--);
continue;
}
ThreadInfo tinfo = getThreadInfo(qc.t);
if (tinfo != null) {
if (tinfo.mode == S) {
if (qc.mode != X) {
// defensive
throw new IllegalStateException();
}
if (waiters.size() == 1) {
grantedMode = X;
} // else let CHAIN
tinfo.mode = X;
waiters.remove(i);
qc.wakeMeUp();
}
} // else first request is a first X request of some thread
break;
} // sync (qc)
} // for
} // else
} // count[S] == 0
return NONE;
}
/** Adds this thread to the queue of waiting threads
* @warning LOCK must be held
*/
private QueueCell chain(final int requested, final Thread t, final int priority) {
//long timeout = 0;
/*
if (killDeadlocksOn) {
checkDeadlock(requested, t);
timeout = (isDispatchThread() || checkAwtTreeLock() ? TIMEOUT : 0);
}
*/
QueueCell qc = new QueueCell(requested, t);
//qc.timeout = timeout;
qc.priority2 = priority;
final int size = waiters.size();
if (size == 0) {
waiters.add(qc);
} else {
QueueCell cursor;
int i = 0;
do {
cursor = (QueueCell) waiters.get(i);
if (cursor.getPriority() < qc.getPriority()) {
waiters.add(i, qc);
break;
}
i++;
} while (i < size);
if (i == size) {
waiters.add(qc);
}
}
return qc;
}
/** Scans through waiters and wakes up them */
private void wakeUpOthers() {
if ((grantedMode == X) ||
(grantedMode == CHAIN)) {
// defensive
throw new IllegalStateException();
}
if (waiters.size() == 0) {
return;
}
for (int i = 0; i < waiters.size(); i++) {
QueueCell qc = (QueueCell) waiters.get(i);
synchronized (qc) {
if (qc.isGotOut()) {
// bogus waiter
waiters.remove(i--);
continue;
}
if (compatibleLocks(grantedMode, qc.mode)) { // woken S -> should I wake X? -> no
waiters.remove(i--);
qc.wakeMeUp();
grantedMode = qc.mode;
if (getThreadInfo(qc.t) == null) {
// force to have a record since recorded threads
// do not use isCompatible call
ThreadInfo ti = new ThreadInfo(qc.t, qc.mode);
ti.forced = true;
if (qc.mode == S) {
readersNo++;
}
registeredThreads.put(qc.t, ti);
}
} else {
grantedMode = CHAIN;
break;
}
} // sync (qc)
}
}
/** Posts new request for current thread
* @param mutexMode mutex mode for which the action is rquested
* @param run the action
*/
private void postRequest(int mutexMode, Runnable run) {
if (this == EVENT) {
doEventRequest(run);
return;
}
Thread t = Thread.currentThread();
ThreadInfo info;
synchronized (LOCK) {
info = getThreadInfo(t);
if (info != null) {
// the same mode and mutex is not entered in the other mode
// assert (mutexMode == S || mutexMode == X)
if (mutexMode == info.mode && info.counts[S + X - mutexMode] == 0) {
enter(mutexMode, t, true);
} else { // the mutex is held but can not be entered in X mode
info.enqueue(mutexMode, run);
return;
}
}
}
// this mutex is not held
if (info == null) {
enter(mutexMode, t, true);
try {
run.run();
} finally {
leave(t);
}
return;
}
// run it immediately
// info != null so enter(...) succeeded
try {
run.run();
} finally {
leave(t);
}
}
/** @param requested is requested mode of locking
* @return <tt>true</tt> if and only if current mode and requested mode are compatible
*/
private boolean isCompatible(int requested) {
return compatibleLocks(grantedMode, requested);
}
private ThreadInfo getThreadInfo(Thread t) {
return (ThreadInfo) registeredThreads.get(t);
}
private boolean canUpgrade(int threadGranted, int requested) {
return (threadGranted == S) && (requested == X) && (readersNo == 1);
}
// ------------------------------- EVENT METHODS ----------------------------
/** Runs the runnable in event queue, either immediatelly,
* or it posts it into the queue.
*/
private static void doEvent (Runnable run) {
if (EventQueue.isDispatchThread ()) {
run.run ();
} else {
EventQueue.invokeLater (run);
}
}
/** Methods for access to event queue.
* @param run runabble to post later
*/
private static void doEventRequest (Runnable run) {
EventQueue.invokeLater (run);
}
/** Methods for access to event queue and waiting for result.
* @param run runabble to post later
*/
private static Object doEventAccess (final ExceptionAction run) throws MutexException {
if (isDispatchThread()) {
try {
return run.run ();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new MutexException (e);
} catch (LinkageError e) {
// #20467
throw new MutexException(new InvocationTargetException(e));
} catch (StackOverflowError e) {
// #20467
throw new MutexException(new InvocationTargetException(e));
}
}
final Throwable[] arr = new Throwable[1];
try {
final Object[] res = new Object[1];
EventQueue.invokeAndWait (new Runnable () {
public void run () {
try {
res[0] = run.run ();
} catch (Exception e) {
arr[0] = e;
} catch (LinkageError e) {
// #20467
arr[0] = e;
} catch (StackOverflowError e) {
// #20467
arr[0] = e;
}
}
});
if (arr[0] == null) {
return res[0];
}
} catch (InterruptedException e) {
arr[0] = e;
} catch (InvocationTargetException e) {
arr[0] = e;
}
if(arr[0] instanceof RuntimeException) {
throw (RuntimeException)arr[0];
}
throw notifyException(ErrorManager.EXCEPTION, arr[0]);
}
/** @return true iff current thread is EventDispatchThread */
static boolean isDispatchThread() {
boolean dispatch = EventQueue.isDispatchThread ();
if (!dispatch && Utilities.getOperatingSystem () == Utilities.OS_SOLARIS) {
// on solaris the event queue is not always recognized correctly
// => try to guess by name
dispatch = (Thread.currentThread().getClass().getName().indexOf("EventDispatchThread") >= 0); // NOI18N
}
return dispatch;
}
/** Notify exception and returns new MutexException */
private static final MutexException notifyException(int severity, Throwable t) {
if (t instanceof InvocationTargetException) {
t = unfoldInvocationTargetException((InvocationTargetException) t);
}
if (t instanceof Error) {
annotateEventStack(t);
throw (Error) t;
}
if (t instanceof RuntimeException) {
annotateEventStack(t);
throw (RuntimeException) t;
}
MutexException exc = new MutexException((Exception) t);
ErrorManager.getDefault().annotate(exc, t);
return exc;
}
private static final void annotateEventStack(Throwable t) {
ErrorManager.getDefault().annotate(t,
new Exception("Caught here in mutex")); // NOI18N
}
private static final Throwable unfoldInvocationTargetException(InvocationTargetException e) {
Throwable ret;
do {
ret = e.getTargetException();
if (ret instanceof InvocationTargetException) {
e = (InvocationTargetException) ret;
} else {
e = null;
}
} while (e != null);
return ret;
}
// --------------------------------------------- END OF EVENT METHODS ------------------------------
/** Action to be executed in a mutex without throwing any checked exceptions.
* Unchecked exceptions will be propagated to calling code.
*/
public static interface Action extends ExceptionAction {
/** Execute the action.
* @return any object, then returned from {@link Mutex#readAccess(Mutex.Action)} or {@link Mutex#writeAccess(Mutex.Action)}
*/
public Object run ();
}
/** Action to be executed in a mutex, possibly throwing checked exceptions.
* May throw a checked exception, in which case calling
* code should catch the encapsulating exception and rethrow the
* real one.
* Unchecked exceptions will be propagated to calling code without encapsulation.
*/
public static interface ExceptionAction {
/** Execute the action.
* Can throw an exception.
* @return any object, then returned from {@link Mutex#readAccess(Mutex.ExceptionAction)} or {@link Mutex#writeAccess(Mutex.ExceptionAction)}
* @exception Exception any exception the body needs to throw
*/
public Object run () throws Exception;
}
private static final class ThreadInfo {
/** t is forcibly sent from waiters to enter() by wakeUpOthers() */
boolean forced;
/** ThreadInfo for this Thread */
final Thread t;
/** granted mode */
int mode;
// 0 - NONE, 1 - CHAIN, 2 - X, 3 - S
/** enter counter */
int[] counts;
/** queue of runnable rquests that are to be executed (in X mode) right after S mode is left
* deadlock avoidance technique
*/
List[] queues;
/** time stamp of the thread
* set only for S mode
*/
int stamp;
/** value of counts[S] when the mode was upgraded
* rsnapshot works as follows:
* if a thread holds the mutex in the S mode and it reenters the mutex
* and requests X and the mode can be granted (no other readers) then this
* variable is set to counts[S]. This is used in the leave method in the X branch.
* (X mode is granted by other words)
* If rsnapshot is less than counts[S] then the counter is decremented etc. If the rsnapshot is
* equal to count[S] then count[X] is decremented. If the X counter is zeroed then
* rsnapshot is zeroed as well and current mode is downgraded to S mode.
* rsnapshot gets less than counts[S] if current mode is X and the mutex is reentered
* with S request.
*/
int rsnapshot;
public ThreadInfo(Thread t, int mode) {
this.t = t;
this.mode = mode;
this.counts = new int[MODE_COUNT];
this.queues = new List[MODE_COUNT];
counts[mode] = 1;
}
public String toString() {
return super.toString() + " thread: " + t + " mode: " + mode + " X: " + counts[2] + " S: " + counts[3]; // NOI18N
}
/** Adds the Runnable into the queue of waiting requests */
public void enqueue(int mode, Runnable run) {
if (queues[mode] == null) {
queues[mode] = new ArrayList(13);
}
queues[mode].add(run);
}
/** @return a List of enqueued Runnables - may be null */
public List dequeue(int mode) {
List ret = queues[mode];
queues[mode] = null;
return ret;
}
public int getRunnableCount(int mode) {
return (queues[mode] == null ? 0 : queues[mode].size());
}
}
/** This class is defined only for better understanding of thread dumps where are informations like
* java.lang.Object@xxxxxxxx owner thread_x
* wait for enter thread_y
*/
private static final class InternalLock {
InternalLock() {}
}
private static final class QueueCell {
int mode;
Thread t;
boolean signal;
/** if the thread is owner of AWTTreeLock then the timeout is greater than zero */
long timeout;
boolean left;
/** priority of the cell */
int priority2;
public QueueCell(int mode, Thread t) {
this.mode = mode;
this.t = t;
this.timeout = 0;
this.left = false;
this.priority2 = 0;
}
public String toString() {
return super.toString() + " mode: " + mode + " thread: " + t; // NOI18N
}
/** @return priority of this cell */
public long getPriority() {
return (priority2 == 0 ? t.getPriority() : priority2);
}
/** @return true iff the thread left sleep */
public boolean isGotOut() {
return left;
}
/** current thread will sleep until wakeMeUp is called
* if wakeMeUp was already called then the thread will not sleep
*/
public synchronized void sleep() {
try {
while (!signal) {
try {
wait();
return;
} catch (InterruptedException e) {
ErrorManager.getDefault().notify(e);
}
}
} finally {
left = true;
}
}
/** sends signal to a sleeper - to a thread that is in the sleep() */
public void wakeMeUp() {
signal = true;
notifyAll();
}
}
/** Provides access to Mutex's internal methods.
*
* This class can be used when one wants to avoid creating a
* bunch of Runnables. Instead,
* <pre>
* try {
* enterXAccess ();
* yourCustomMethod ();
* } finally {
* exitXAccess ();
* }
* </pre>
* can be used.
*
* You must, however, control the related Mutex, i.e. you must be creator of
* the Mutex.
*
* @since 1.17
*/
public static final class Privileged {
private Mutex parent;
final void setParent(Mutex parent) {
this.parent = parent;
}
public void enterReadAccess() {
parent.readEnter(Thread.currentThread());
}
public void enterWriteAccess() {
parent.writeEnter(Thread.currentThread());
}
public void exitReadAccess() {
parent.leave(Thread.currentThread());
}
public void exitWriteAccess() {
parent.leave(Thread.currentThread());
}
}
}