/* * 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 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. *
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 { LATER.post(new Runnable() { public void run() { enterReadAccess(); try { action.run(); } finally { exitReadAccess(); } } }); } } /** Run an action with write access. * The same thread may meanwhile reenter the mutex; see the class description for details. *
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: *
*
* try {
* mutex.writeAccess (new ExceptionAction () {
* public void run () throws IOException {
* throw new IOException ();
* }
* });
* } catch (MutexException ex) {
* throw (IOException) ex.getException ();
* }
*
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. * 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 { LATER.post(new Runnable() { public void run() { if (lock instanceof InternalLock) { enterWriteAccess(); try { action.run(); } finally { exitWriteAccess(); } } else { synchronized (lock) { enterWriteAccess(); try { action.run(); } finally { exitWriteAccess(); } } } } }); } } /** * Posts a read request to be run as soon as is reasonable. *
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:
* >
protected Object initialValue() {
return new ArrayList();
}
};
/**
* Get information about the current thread.
* Must be called with the lock held.
*/
private ThreadInfo currentThreadInfo() {
if (this == EVENT) throw new IllegalStateException();
Thread t = Thread.currentThread();
ThreadInfo ti = (ThreadInfo)threads.get(t);
if (ti == null) {
ti = new ThreadInfo(t);
threads.put(t, ti);
}
return ti;
}
/**
* Should be called when about to enter a mutex.
* Records it in the list of ordered mutexes held by this thread -
* if it is an ordered mutex.
* First checks that it is legal to enter.
* Pass null for the thread info when entering EVENT.
*/
private static void entering(ThreadInfo ti) throws IllegalStateException {
if (ti != null) {
// Entering a normal mutex
if (ti.getMutex().level != Integer.MAX_VALUE) {
List l = (List)orderedMutexesHeld.get();
// It is ordered, check it.
ThreadInfo last;
if (!l.isEmpty()) {
last = (ThreadInfo)l.get(l.size() - 1);
} else {
last = null;
}
// If we are holding something, make sure it is OK.
if (last != null && ti != last && ti.getMutex().level >= last.getMutex().level) {
// Cannot transition lower -> higher or same -> same
throw new IllegalStateException("Cannot enter " + ti.getMutex().name + " from " + last.getMutex().name); // NOI18N
}
// Record it if new.
if (ti != last) {
l.add(ti);
}
} else {
// Unordered mutex - go ahead.
}
} else {
// Entering EVENT
List l = (List)orderedMutexesHeld.get();
if (!l.isEmpty()) {
throw new IllegalStateException("Cannot enter EVENT from " + ((ThreadInfo)l.get(l.size() - 1)).getMutex().name); // NOI18N
}
// else OK
}
}
/**
* Call before exiting a mutex.
* Checks if it is legal to exit, if it is an ordered mutex,
* by making sure this is the last mutex to exit.
* Optionally, erases it from the stack (which is not done when you are exiting
* a nested read or write in this mutex).
* You cannot pass null for EVENT since no check is done when exiting EVENT.
*/
private static void exiting(ThreadInfo ti, boolean erase) throws IllegalStateException {
if (ti.getMutex().level != Integer.MAX_VALUE) {
List l = (List)orderedMutexesHeld.get();
if (l.isEmpty()) {
throw new IllegalStateException("Cannot exit " + ti.getMutex().name + " which you did not hold"); // NOI18N
}
ThreadInfo last = (ThreadInfo)l.get(l.size() - 1);
if (ti != last) {
throw new IllegalStateException("Cannot exit " + ti.getMutex().name + " when you have not yet exited " + last.getMutex().name); // NOI18N
}
if (erase) {
l.remove(l.size() - 1);
}
}
}
/**
* Count of active readers, or write mutex state.
* When positive, one or more readers are holding the read mutex.
* When zero, the mutex is uncontended.
* When -1, the write mutex is held.
*/
private int semaphore = 0;
/**
* Count of threads which are holding this mutex somehow.
* Used only as a sanity-check in certain circumstances.
*/
private int threadCount = 0;
/**
* Information about a thread and what it is waiting to do.
* All methods must be synchronized with the lock.
*/
private final class ThreadInfo {
/**
* Get the associated mutex.
*/
public Mutex getMutex() {
return Mutex.this;
}
/**
* The associated thread.
*/
public final Thread t;
/**
* True if this thread is holding the mutex somehow.
*/
public boolean live;
/**
* If non-null, a list of things to do in the read mutex
* immediately after exiting the write mutex.
* @see #postReadRequest
*/
public List lateReadActions = null;
/**
* If non-null, a list of things to do in the write mutex
* immediately after exiting a read mutex inside a write mutex.
* @see #postWriteRequest
*/
public List lateWriteActions = null;
/**
* What state this thread is in.
* If true, it is a writer (possibly in a nested read mutex),
* if false it is purely a reader (possibly again in a nested read mutex).
*/
public boolean writer;
/**
* Count of additional read mutex reentries made by this thread.
* They have no additional effect but are counted so that enter
* and exit calls can be properly paired.
*/
public int extraReads = 0;
/**
* Count of additional write mutex reentries made by this thread.
* They have no additional effect but are counted so that enter
* and exit calls can be properly paired.
* Must be zero in case {@link #writer} is false.
*/
public int extraWrites = 0;
/**
* Create new thread info based on the specified thread.
*/
public ThreadInfo(Thread t) {
this.t = t;
}
/**
* Register this thread info as live.
* Called when this thread first acquires this mutex somehow.
*/
public void register(boolean writer) {
if (live) throw new IllegalStateException();
live = true;
this.writer = writer;
threadCount++;
}
/**
* Unregister this thread info.
* Called when this thread finally release this mutex (in all nestings).
*/
public void unregister() {
if (!live) throw new IllegalStateException();
if (lateReadActions != null) throw new IllegalStateException();
if (lateWriteActions != null) throw new IllegalStateException();
if (extraReads > 0) throw new IllegalStateException();
if (extraWrites > 0) throw new IllegalStateException();
if (threadCount == 0) throw new IllegalStateException();
live = false;
writer = false; // arbitrary
threadCount--;
}
public String toString() {
return "ThreadInfo<" + t + ",writer=" + writer + ",extraReads=" + extraReads + ",extraWrites=" + extraWrites + ",lateReadActions=" + lateReadActions + ",lateWriteActions=" + lateWriteActions + ">"; // NOI18N
}
}
// --- Mutex.EVENT stuff ---
/** @return true iff current thread is EventDispatchThread */
private 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;
}
/**
* Run an exception action synchronously in AWT and return the result.
*/
private static Object eventAccess(final ExceptionAction action) throws MutexException {
if (isDispatchThread()) {
try {
return action.run();
} catch (Exception e) {
throw new MutexException(e);
}
} else {
entering(null);
final Throwable[] exc = new Throwable[1];
final Object[] result = new Object[1];
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
try {
result[0] = action.run();
} catch (Throwable t) {
exc[0] = t;
}
}
});
} catch (InterruptedException e) {
throw new IllegalStateException(e.toString());
} catch (InvocationTargetException e) {
// Should not happen since we caught Exception above already:
throw new IllegalStateException(e.getTargetException().toString());
}
if (exc[0] instanceof RuntimeException) {
throw (RuntimeException)exc[0];
} else if (exc[0] instanceof Error) {
throw (Error)exc[0];
} else if (exc[0] != null) {
throw new MutexException((Exception)exc[0]);
} else {
return result[0];
}
}
}
/**
* Run a plain action synchronously in AWT and return the result.
*/
private static Object eventAccess(final Action action) {
if (isDispatchThread()) {
return action.run();
} else {
entering(null);
final Object[] result = new Object[1];
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
result[0] = action.run();
}
});
} catch (InterruptedException e) {
throw new IllegalStateException(e.toString());
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof RuntimeException) {
throw (RuntimeException)t;
} else if (t instanceof Error) {
throw (Error)t;
} else {
throw new IllegalStateException(t.toString());
}
}
return result[0];
}
}
/**
* Run something in AWT.
* If we are already in AWT, it is just run.
* Otherwise it may be run synch or asynch according to the parameter.
*/
private static void eventAccess(Runnable run, boolean asynch) {
if (isDispatchThread()) {
run.run();
} else if (asynch) {
EventQueue.invokeLater(run);
} else {
entering(null);
try {
EventQueue.invokeAndWait(run);
} catch (InterruptedException e) {
throw new IllegalStateException(e.toString());
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof RuntimeException) {
throw (RuntimeException)t;
} else if (t instanceof Error) {
throw (Error)t;
} else {
throw new IllegalStateException(t.toString());
}
}
}
}
// --- Action interfaces ---
/** 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;
}
/** 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() {}
}
/** Provides access to Mutex's internal methods.
*
* This class can be used when one wants to avoid creating a
* bunch of Runnables. Instead use:
*
* 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();
}
}
}