/* * 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. */ // XXX uses: FolderChildren, Children.MutexChildren, module system // XXX deadlock analyzer on system exit // XXX while holding a lower lock, r/wAccess(Runnable) on a higher lock should run asynch // XXX and what about postR/WRequest(Runnable) while holding a lower lock? package org.openide.util; import java.awt.EventQueue; import java.util.*; import java.lang.reflect.InvocationTargetException; /** Read-many/write-one lock. * Allows control over resources that * can be read by several readers at once but only written by one writer. *
* It is guaranteed that if you are a writer you can also enter the * mutex as a reader. But you cannot enter the write mutex if you hold * the read mutex, since that can cause deadlocks. *
* This implementation will probably not starve a writer or reader indefinitely, * but the exact behavior is at the mercy of {@link Object#wait()} and {@link Object#notifyAll()}. *
* Examples of use: * *
*
*
* 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 ();
* }
*
Mutexes may have defined levels. A lock ordering is defined among * mutexes with levels (those without levels do not participate in this * ordering at all). While holding a mutex with a high level, you may enter * a mutex with a lower level. But you may not enter a mutex with a high * level while holding a mutex with a lower or equal level (unless you are * reentering the same mutex). This scheme should prevent a class of deadlocks * resulting from two threads acquiring locks in opposite orders. * * @author Ales Novak (old code), Jesse Glick (rewrite - #32439) */ public final class Mutex extends Object { /** * Pseudo-mutex that allows code to be synchronized with the AWT event dispatch thread. * This is handy in that you can define a constant of type Mutex in some API, initially * set to a normal mutex, and then switch everything to the event thread (or vice-versa). *
It behaves somewhat differently from a regular mutex. *
Mutex.EVENT
methods).
* Mutex
es to synchronize on one object or to synchronize
* a mutex with another critical section.
*
* @param lock lock to use
* @deprecated Does not permit specification of levels, and cannot work with
* {@link #canRead} and {@link #canWrite} correctly.
*/
public Mutex (Object lock) {
this.lock = lock;
level = Integer.MAX_VALUE;
name = lock.toString();
}
/** Default constructor.
* @deprecated Does not permit specification of levels.
*/
public Mutex() {
this(new InternalLock());
}
/** @param privileged can enter privileged states of this Mutex
* This helps avoid creating of custom Runnables.
* @deprecated Does not permit specification of levels.
*/
public Mutex(Privileged privileged) {
this();
init(privileged);
}
/**
* Create a mutex with a defined level.
* @param name an identifying name to use when debugging
* @param level an integer level other than {@link Integer.MAX_VALUE} (see class Javadoc for explanation)
*/
public Mutex(String name, int level) {
if (level == Integer.MAX_VALUE && EVENT != null) throw new IllegalArgumentException();
if (name == null) throw new IllegalArgumentException();
this.name = name;
this.lock = new InternalLock();
this.level = level;
}
/**
* Create a mutex with a privileged key and a defined level.
* @param name an identifying name to use when debugging
* @param privileged a key which may be used to call unbalanced entry/exit methods directly
* @param level an integer level other than {@link Integer.MAX_VALUE} (see class Javadoc for explanation)
*/
public Mutex(String name, Privileged privileged, int level) {
this(name, level);
init(privileged);
}
private void init(Privileged privileged) {
if (privileged == null) {
throw new IllegalArgumentException("privileged == null"); //NOI18N
} else {
privileged.setParent(this);
}
}
/** Run an action only with read access.
* See {@link Mutex.Privileged#enterReadAccess} and {@link Mutex.Privileged#exitReadAccess}
* for a precise description of lock reentry and ordering semantics.
* @param action the action to perform
* @return the object returned from {@link Mutex.Action#run}
*/
public Object readAccess (Action action) {
if (this == EVENT) {
return eventAccess(action);
}
enterReadAccess();
try {
return action.run();
} finally {
exitReadAccess();
}
}
/** Run an action with read access and possibly throw a checked exception.
* The exception if thrown is then encapsulated
* in a MutexException
and thrown from this method. One is encouraged
* to catch MutexException
, obtain the inner exception, and rethrow it.
* Here is an example:
*
* Note that runtime exceptions are always passed through, and neither
* require this invocation style, nor are encapsulated.
*
* try {
* mutex.readAccess (new ExceptionAction () {
* public void run () throws IOException {
* throw new IOException ();
* }
* });
* } catch (MutexException ex) {
* throw (IOException) ex.getException ();
* }
*
See {@link Mutex.Privileged#enterReadAccess} and {@link Mutex.Privileged#exitReadAccess} * for a precise description of lock reentry and ordering semantics. * @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 eventAccess(action); } enterReadAccess(); try { return action.run(); } catch (RuntimeException e) { throw (RuntimeException)e; } catch (Exception e) { throw new MutexException(e); } finally { exitReadAccess(); } } /** Run an action with read access, returning no result. * It may be run asynchronously (or not) at the discretion of the implementation. * This implementation tries to run if synchronously if it will not have to wait * for a lock, else runs it at some later point in the future. *
If you call this method while holding other mutexes (EVENT
does not count)
* and the runnable is run asynchronously, it will be called while holding those same mutexes -
* considered in terms of having either read or write access. If some of those mutexes (including
* this one) have no defined level, it is not defined in which relative order they will be
* acquired.
*
See {@link Mutex.Privileged#enterReadAccess} and {@link Mutex.Privileged#exitReadAccess}
* for a precise description of lock reentry and ordering semantics (less relevant
* when running asynchronously).
* @param action the action to perform
* @see #readAccess(Mutex.Action)
*/
public void readAccess (final Runnable action) {
if (this == EVENT) {
eventAccess(action, true);
return;
}
boolean synch;
synchronized (lock) {
synch = semaphore >= 0 || canRead();
if (synch) {
enterReadAccess();
}
}
if (synch) {
try {
action.run();
} finally {
exitReadAccess();
}
} else {
final SortedSet held = findHeldMutexes(); // SortedSet May not be called while holding read access.
* See {@link Mutex.Privileged#enterWriteAccess} and {@link Mutex.Privileged#exitWriteAccess}
* for a precise description of lock reentry and ordering semantics.
* @param action the action to perform
* @return the result of {@link Mutex.Action#run}
*/
public Object writeAccess (Action action) {
if (this == EVENT) {
return eventAccess(action);
}
if (lock instanceof InternalLock) {
enterWriteAccess();
try {
return action.run();
} finally {
exitWriteAccess();
}
} else {
// Same but synch on lock.
synchronized (lock) {
enterWriteAccess();
try {
return action.run();
} finally {
exitWriteAccess();
}
}
}
}
/** Run an action with write access and possibly throw an exception.
* Here is an example:
* May not be called while holding read access.
* See {@link Mutex.Privileged#enterWriteAccess} and {@link Mutex.Privileged#exitWriteAccess}
* for a precise description of lock reentry and ordering semantics.
*
* @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 eventAccess(action);
}
if (lock instanceof InternalLock) {
enterWriteAccess();
try {
return action.run();
} catch (RuntimeException e) {
throw (RuntimeException)e;
} catch (Exception e) {
throw new MutexException(e);
} finally {
exitWriteAccess();
}
} else {
// Same but synch on lock.
synchronized (lock) {
enterWriteAccess();
try {
return action.run();
} catch (RuntimeException e) {
throw (RuntimeException)e;
} catch (Exception e) {
throw new MutexException(e);
} finally {
exitWriteAccess();
}
}
}
}
/** Run an action with write access and return no result.
* It may be run asynchronously (or not) at the discretion of the implementation.
* This implementation tries to run if synchronously if it will not have to wait
* for a lock, else runs it at some later point in the future.
* May be called while holding read access, forcing asynchronous execution.
* If you call this method while holding other mutexes ( See {@link Mutex.Privileged#enterWriteAccess} and {@link Mutex.Privileged#exitWriteAccess}
* for a precise description of lock reentry and ordering semantics (less relevant
* when running asynchronously).
* @param action the action to perform
* @see #writeAccess(Mutex.Action)
* @see #readAccess(Runnable)
*/
public void writeAccess (final Runnable action) {
if (this == EVENT) {
eventAccess(action, true);
return;
}
boolean synch;
synchronized (lock) {
synch = semaphore == 0 || canWrite();
if (synch) {
enterWriteAccess();
if (!(lock instanceof InternalLock)) {
try {
action.run();
} finally {
exitWriteAccess();
}
}
}
}
if (synch && lock instanceof InternalLock) {
try {
action.run();
} finally {
exitWriteAccess();
}
} else {
final SortedSet held = findHeldMutexes(); // SortedSet You may not call this on a mutex using a {@link Mutex#Mutex(Object) special lock}.
* @return true if either read or write access is available
* @since XXX
*/
public boolean canRead() {
if (this == EVENT) {
return isDispatchThread();
}
if (!(lock instanceof InternalLock)) {
throw new IllegalStateException("Mutex " + name + " uses a special lock, cannot call canRead"); // NOI18N
}
synchronized (lock) {
if (semaphore == 0) {
// Uncontended, obviously not. Only a shortcut,
// next clause would catch it anyway.
return false;
}
return currentThreadInfo().live;
}
}
/**
* Check if the current thread is holding the write mutex.
* Note that this will be false in case write access was entered and then
* read access was entered inside of that, until the nested read access is
* again exited.
* You may not call this on a mutex using a {@link Mutex#Mutex(Object) special lock}.
* @return true if write access is available
* @since XXX
*/
public boolean canWrite() {
if (this == EVENT) {
return isDispatchThread();
}
if (!(lock instanceof InternalLock)) {
throw new IllegalStateException("Mutex " + name + " uses a special lock, cannot call canWrite"); // NOI18N
}
synchronized (lock) {
if (semaphore != -1) {
// Not in write mode, obviously not. Only a shortcut,
// next clause would catch it anyway.
return false;
}
ThreadInfo ti = currentThreadInfo();
if (ti.live) {
if (!ti.writer) throw new IllegalStateException();
return ti.extraReads == 0;
} else {
return false;
}
}
}
/* information helpful for debugging */
public String toString() {
if (this == EVENT) {
return "Mutex.EVENT"; // NOI18N
}
synchronized (lock) {
StringBuffer b = new StringBuffer("Mutex If the lock is not an internal lock, specified by the constructor
* {@link Mutex#Mutex(Object}, then additionally write requests keep
* a monitor on this lock for their entire duration. Normally the monitor
* is held only during entering and exiting the mutex, even for the write
* mutex.
*/
private final Object lock;
/**
* Map from threads to associated information.
*/
private final Map threads = new WeakHashMap(); // Map You must control the related Mutex, i.e. you must be the creator of
* the Mutex. Thus you may create a Mutex.Privileged for efficient access within
* a package and only expose the Mutex to outside code, to ensure that it is
* not abused by being entered and not exited.
*
* @since 1.17
*/
public static final class Privileged {
/**
* Create a new privileged key to a mutex.
* (It may only be used in one mutex.)
*/
public Privileged() {}
private Mutex parent;
final void setParent(Mutex parent) {
if (this.parent != null) throw new IllegalStateException();
this.parent = parent;
}
/**
* Enter read access for this mutex.
* You must ensure that {@link #exitReadAccess} is reliably called
* when you are done. The normal way to do this is as follows:
* Detailed behavior:
* Detailed behavior:
* Detailed behavior:
* Detailed behavior:
*
*
* try {
* mutex.writeAccess (new ExceptionAction () {
* public void run () throws IOException {
* throw new IOException ();
* }
* });
* } catch (MutexException ex) {
* throw (IOException) ex.getException ();
* }
*
EVENT
does not count)
* and the runnable is run asynchronously, it will be called while holding those same mutexes -
* considered in terms of having either read or write access. If some of those mutexes (including
* this one) have no defined level, it is not defined in which relative order they will be
* acquired.
*
*
* @param run runnable to run
*/
public void postReadRequest (final Runnable run) {
if (this == EVENT) {
eventAccess(run, false);
return;
}
if (canWrite()) {
synchronized (lock) {
ThreadInfo ti = currentThreadInfo();
if (!ti.live) throw new IllegalStateException();
if (!ti.writer) throw new IllegalStateException();
if (ti.lateReadActions == null) {
ti.lateReadActions = new LinkedList();
}
ti.lateReadActions.add(run);
}
} else {
enterReadAccess();
try {
run.run();
} finally {
exitReadAccess();
}
}
}
/**
* Posts a write request to be run as soon as is reasonable.
*
*
* @param run runnable to run
*/
public void postWriteRequest (Runnable run) {
if (this == EVENT) {
eventAccess(run, false);
return;
}
if (canWrite()) {
run.run();
} else if (canRead()) {
// Cannot do it now; do it when exiting outermost read lock.
synchronized (lock) {
ThreadInfo ti = currentThreadInfo();
if (!ti.live) throw new IllegalStateException();
if (ti.lateWriteActions == null) {
ti.lateWriteActions = new LinkedList();
}
ti.lateWriteActions.add(run);
}
} else {
if (lock instanceof InternalLock) {
enterWriteAccess();
try {
run.run();
} finally {
exitWriteAccess();
}
} else {
synchronized (lock) {
enterWriteAccess();
try {
run.run();
} finally {
exitWriteAccess();
}
}
}
}
}
/**
* Enter the read mutex.
* @see Mutex.Privileged#enterReadAccess
*/
void enterReadAccess() {
synchronized (lock) {
ThreadInfo ti = currentThreadInfo();
entering(ti);
if (ti.live) {
// Already in read or write mutex, can certainly enter read.
ti.extraReads++;
} else {
// Entering fresh. Wait for any writers to exit.
while (semaphore < 0) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new IllegalStateException(e.toString());
}
}
// Uncontended or shared, go ahead.
ti.register(false);
semaphore++;
}
}
}
/**
* Exit the read mutex.
* @see Mutex.Privileged#exitReadAccess
*/
void exitReadAccess() {
// This method is a little more complicated than is comfortable.
// 1. A top-level read mutex is being exited. Fine.
// 1a. There were some late write actions; grab the write lock, run them, release it.
// 2. A nested read mutex is being exited. OK.
// 3. A top-level read mutex inside a write mutex is being exited. OK.
// 3a. There were some late write actions; run them now.
// 4. A nested read mutex inside a write mutex is being exited. OK.
List lateWriteActions = null;
boolean wasWriter = false;
synchronized (lock) {
ThreadInfo ti = currentThreadInfo();
if (!ti.live) throw new IllegalStateException();
if (ti.extraReads > 0) {
// Cases 2, 3, or 4.
if (semaphore == 0) throw new IllegalStateException();
exiting(ti, false);
// Just mark them off.
ti.extraReads--;
if (ti.extraReads == 0 && ti.writer && ti.lateWriteActions != null) {
// Case 3a. We are a writer who has just finished nested reads and now can
// run some late write actions.
lateWriteActions = ti.lateWriteActions;
ti.lateWriteActions = null;
wasWriter = true;
// We exited the lock, but since we are a writer no one else can enter
// anything, so that is safe.
}
} else {
// Case 1.
if (ti.writer) throw new IllegalStateException();
if (semaphore <= 0) throw new IllegalStateException();
if (ti.extraWrites > 0) throw new IllegalStateException();
exiting(ti, true);
// Really exiting. Case 1.
if (ti.lateWriteActions != null) {
// We are a reader who has just finished all nested reads and now can
// run some late write actions. Case 1a.
lateWriteActions = ti.lateWriteActions;
ti.lateWriteActions = null;
wasWriter = false;
}
ti.unregister();
semaphore--;
lock.notifyAll();
if (semaphore == 0 && threadCount > 0) throw new IllegalStateException(toString());
if (lateWriteActions != null && !(lock instanceof InternalLock)) {
// Have to run them with lock still held.
enterWriteAccess();
try {
Iterator it = lateWriteActions.iterator();
while (it.hasNext()) {
Runnable r = (Runnable)it.next();
r.run();
}
} finally {
exitWriteAccess();
lateWriteActions = null;
}
}
}
}
if (lateWriteActions != null) {
if (!wasWriter) {
enterWriteAccess();
}
try {
Iterator it = lateWriteActions.iterator();
while (it.hasNext()) {
Runnable r = (Runnable)it.next();
r.run();
}
} finally {
if (!wasWriter) {
exitWriteAccess();
}
}
}
}
/**
* Enter the write mutex.
* @see Mutex.Privileged#enterWriteAccess
*/
void enterWriteAccess() {
synchronized (lock) {
ThreadInfo ti = currentThreadInfo();
if (ti.live) {
if (ti.writer && ti.extraReads == 0) {
// Already in write mutex, can reenter freely.
entering(ti);
ti.extraWrites++;
} else {
throw new IllegalStateException("Illegal mutex upgrade from read to write in " + name); // NOI18N
}
} else {
// ti == null, entering fresh.
// Wait for any readers or writers to exit.
entering(ti);
while (semaphore != 0) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new IllegalStateException(e.toString());
}
}
// Uncontended, go ahead.
ti.register(true);
semaphore = -1;
}
}
}
/**
* Exit the write mutex.
* @see Mutex.Privileged#exitWriteAccess
*/
void exitWriteAccess() {
List lateReadActions = null;
ThreadInfo ti;
synchronized (lock) {
ti = currentThreadInfo();
if (!ti.live) throw new IllegalStateException();
if (!ti.writer) throw new IllegalStateException();
if (semaphore != -1) throw new IllegalStateException();
if (ti.extraReads > 0) throw new IllegalStateException();
if (ti.extraWrites > 0) {
// Just mark them off.
exiting(ti, false);
ti.extraWrites--;
} else if (ti.lateReadActions != null) {
if (threadCount != 1) throw new IllegalStateException(toString());
exiting(ti, false);
// Will exit after running these.
lateReadActions = ti.lateReadActions;
ti.lateReadActions = null;
semaphore = 1;
ti.writer = false;
lock.notifyAll();
// Now the semaphore is released, we are in plain read access.
// No other writers can enter before this because we have not yet
// released the lock. Other readers can however enter.
} else {
// Really exiting.
exiting(ti, true);
ti.unregister();
semaphore = 0;
lock.notifyAll();
if (threadCount > 0) throw new IllegalStateException(toString());
}
}
if (lateReadActions != null) {
try {
Iterator it = lateReadActions.iterator();
while (it.hasNext()) {
Runnable r = (Runnable)it.next();
r.run();
}
} finally {
exitReadAccess();
}
}
}
/**
* Check if the current thread is holding a read or write mutex.
* >
protected Object initialValue() {
return new ArrayList();
}
};
/**
* From a given thread, this thread-local var holds a set of all mutexes
* with or without definite order (not counting EVENT) which have ever been
* held or might have been held and which still exist.
*/
private static final ThreadLocal relevantMutexes = new ThreadLocal() { // ThreadLocal
* p.enter*Access();
* try {
* // your code here
* } finally {
* p.exit*Access();
* }
*
* You must be careful to match enter and exit calls reliably and exactly.
* Please read the Javadoc for each method carefully.
*
*
* p.enterReadAccess();
* // must be no additional code here!
* try {
* // whatever code...
* } finally {
* // must be no additional code here!
* p.exitReadAccess();
* }
*
*
*
*
*/
public void enterReadAccess() {
parent.enterReadAccess();
}
/**
* Enter write access for this mutex.
* You must ensure that {@link #exitWriteAccess} is reliably called
* when you are done. The normal way to do this is as follows:
*
* p.enterWriteAccess();
* // must be no additional code here!
* try {
* // whatever code...
* } finally {
* // must be no additional code here!
* p.exitWriteAccess();
* }
*
*
*
*
*/
public void enterWriteAccess() {
parent.enterWriteAccess();
}
/**
* Exit the read mutex.
* For important usage instructions, see {@link #enterReadAccess}.
*
*
*
*/
public void exitReadAccess() {
parent.exitReadAccess();
}
/**
* Exit the write mutex.
* For important usage instructions, see {@link #enterWriteAccess}.
*
* postWriteRequest
has been
* called within its scope. In that case all such write requests will run
* now, as above, with the difference that they are already holding the write
* lock and need not compete for it.
*
*
*/
public void exitWriteAccess() {
parent.exitWriteAccess();
}
}
}