/* * 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. *

    *
  1. There is no distinction between read and write access. There is only one * access mode, which is exclusive, and runs on the AWT thread, not in the * caller thread. *
  2. There is no {@link Mutex.Privileged}, so you cannot make entry or exit calls * by themselves (which would make no sense). *
  3. You cannot specify a level. The event mutex is considered to be at a higher * level than any ordinary mutex with a defined level. This means that from the * event thread, you can enter any mutex (subject to other restrictions), but * while holding any ordered mutex you may not block on the event thread * (using Mutex.EVENT methods). *
  4. {@link #readAccess(Mutex.Action)}, {@link #readAccess(Mutex.ExceptionAction)}, * {@link #writeAccess(Mutex.Action)}, {@link #writeAccess(Mutex.ExceptionAction)}, * {@link #postReadRequest}, and {@link #postWriteRequest} when called from the * event thread run synchronously. Else they all block, like * {@link EventQueue#invokeAndWait}. *
  5. {@link #readAccess(Runnable)} and {@link #writeAccess(Runnable)} called from the * event thread run synchronously, else they run asynchronously, like * {@link EventQueue#invokeLater}. *
  6. {@link #canRead} and {@link #canWrite} just test whether you are in the event * thread, like {@link EventQueue#isDispatchThread}. *
*/ public static final Mutex EVENT = new Mutex("EVENT", Integer.MAX_VALUE); // NOI18N /** * Request processor (one thread) on which to post deferred requests that will * need to acquire a lock impossible to acquire now. * @see #readAccess(Runnable) * @see #writeAccess(Runnable) */ private static final RequestProcessor LATER = new RequestProcessor("Mutex"); // NOI18N /** * Ordering level of this mutex, or Integer.MAX_VALUE for unordered. */ private final int level; /** * Some identifying name for this mutex. */ private final String name; /** 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 Mutexes 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: *

    * try {
    *   mutex.readAccess (new ExceptionAction () {
    *     public void run () throws IOException {
    *       throw new IOException ();
    *     }
    *   });
    *  } catch (MutexException ex) {
    *    throw (IOException) ex.getException ();
    *  }
    * 
* Note that runtime exceptions are always passed through, and neither * require this invocation style, nor are encapsulated. *

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 held.add(new HeldMutex(this, false)); LATER.post(new Runnable() { public void run() { runWhileHolding(held, action); } }); } } /** 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. *

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#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 held.add(new HeldMutex(this, true)); LATER.post(new Runnable() { public void run() { if (lock instanceof InternalLock) { runWhileHolding(held, action); } else { synchronized (lock) { runWhileHolding(held, action); } } } }); } } /** * Posts a read request to be run as soon as is reasonable. *

    *
  1. If this thread is holding write access, with or without nested read access, * then the request is run later, when the write access is exited. See * {@link Mutex.Privileged#exitWriteAccess} for details. *
  2. If this thread is holding read access, the request is run now. *
  3. If this thread is not holding any access, the request is run now, * blocking for access to the mutex. *
* @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. *
    *
  1. If this thread is holding read access, possibly inside write access, * then the request is run later, when the read access is exited. See * {@link Mutex.Privileged#exitReadAccess} for details. *
  2. If this thread is holding write access, with no nested read access, * the request is run now. *
  3. If this thread is not holding any access, the request is run now, * blocking for access to the mutex. *
* @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. *

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"); // NOI18N return b.toString(); } } // --- Internal data structures --- /** * Lock for all internal structures. * They may be accessed only when synched on this. * Also waiting threads are waiting on this lock and * are when exiting some operation any waiters are notified. *

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 /** * From a given thread, this thread-local var holds a stack of mutexes with * defined order which are being held. * Mutexes with no defined order are not listed, nor is EVENT. * The end of the list is the most recent item. * Note that there cannot be duplicates, because ordered mutexes cannot be * acquired out of a strict order - you may reenter the last-acquired ordered * mutex, of course, but this information is represented by extraReaders and * extraWriters. */ private static final ThreadLocal orderedMutexesHeld = new ThreadLocal() { // ThreadLocal> 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> protected Object initialValue() { return new WeakSet(); } }; /** * 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); ((Set)relevantMutexes.get()).add(this); } 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 } } /** * Find a set of all mutexes currently held by this thread, ordered or not. * The list is sorted by mutex level, highest first, with unordered mutexes * at unspecified positions. Writers are listed before readers of the same mutex. */ private static SortedSet findHeldMutexes() { // SortedSet SortedSet s = new TreeSet(); Iterator it = ((Set)relevantMutexes.get()).iterator(); while (it.hasNext()) { Mutex m = (Mutex)it.next(); synchronized (m.lock) { ThreadInfo ti = m.currentThreadInfo(); if (ti.live) { s.add(new HeldMutex(m, ti.writer && ti.extraReads == 0)); } } } return s; } /** * A struct consisting of a held mutex and information about whether it is held * in read or write mode. Note that read access nested inside write access counts * as being in read mode. * Sortable as in {@link #findHeldMutexes}. */ private static final class HeldMutex implements Comparable { /** associated mutex which is held */ public final Mutex mutex; /** true if in write access, false if in read */ public final boolean writer; public HeldMutex(Mutex mutex, boolean writer) { this.mutex = mutex; this.writer = writer; } public boolean equals(Object o) { if (!(o instanceof HeldMutex)) return false; HeldMutex hm = (HeldMutex)o; return mutex == hm.mutex && writer == hm.writer; } public int hashCode() { return mutex.hashCode() + (writer ? 1 : 0); } public int compareTo(Object o) { HeldMutex hm = (HeldMutex)o; if (mutex == hm.mutex) { if (writer) { if (hm.writer) { return 0; } else { return -1; // writer before reader } } else { if (hm.writer) { return 1; // reader after writer } else { return 0; } } } else { if (mutex.level != Integer.MAX_VALUE) { if (hm.mutex.level != Integer.MAX_VALUE) { if (mutex.level == hm.mutex.level) throw new IllegalArgumentException(); // Higher-level mutexes first. return (mutex.level > hm.mutex.level) ? -1 : 1; } else { // Arbitrarily, ordered mutexes last. return 1; } } else { if (hm.mutex.level != Integer.MAX_VALUE) { // Ordered mutexes last. return -1; } else { // Two unordered mutexes, choose some order. return System.identityHashCode(mutex) - System.identityHashCode(hm.mutex); } } } } } /** * Run some runnable while holding a set of mutexes. * They will acquired in the listed order (which should be conflict-free) * and released in the reverse order. */ private static void runWhileHolding(SortedSet/*SortedSet*/ held, Runnable r) { List acquired = new ArrayList(held.size()); Iterator it = held.iterator(); try { while (it.hasNext()) { HeldMutex hm = (HeldMutex)it.next(); if (hm.writer) { hm.mutex.enterWriteAccess(); } else { hm.mutex.enterReadAccess(); } acquired.add(hm); } r.run(); } finally { Collections.reverse(acquired); it = acquired.iterator(); while (it.hasNext()) { HeldMutex hm = (HeldMutex)it.next(); if (hm.writer) { hm.mutex.exitWriteAccess(); } else { hm.mutex.exitReadAccess(); } } } } // --- 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(final Runnable run, boolean asynch) { if (isDispatchThread()) { run.run(); } else if (asynch) { final SortedSet held = findHeldMutexes(); // SortedSet EventQueue.invokeLater(new Runnable() { public void run() { runWhileHolding(held, 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. * *

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: *

         * p.enterReadAccess();
         * // must be no additional code here!
         * try {
         *     // whatever code...
         * } finally {
         *     // must be no additional code here!
         *     p.exitReadAccess();
         * }
         * 
* *

Detailed behavior: *

    *
  1. You may already be holding the read or write mutex. But you must * still nest entries and exits, 1-to-1. *
  2. If this mutex has a level, you may not enter it if you are already * holding another mutex with a smaller or equal level in this thread. *
  3. If another thread is holding the write mutex, this method * will block until it leaves. *
*/ 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();
         * }
         * 
* *

Detailed behavior: *

    *
  1. You may already be holding the write mutex. But you must * still nest entries and exits, 1-to-1. *
  2. You may not be holding the read mutex - even if inside * the write mutex. *
  3. If this mutex has a level, you may not enter it if you are already * holding another mutex with a smaller or equal level in this thread. *
  4. If other threads are holding the read or write mutex, this method * will block until they all leave. *
*/ public void enterWriteAccess() { parent.enterWriteAccess(); } /** * Exit the read mutex. * For important usage instructions, see {@link #enterReadAccess}. * *

Detailed behavior: *

    *
  1. You must have already entered this mutex in read mode (once for * every time you exit it). *
  2. You must exit a mutex in the same thread you entered it. *
  3. If this mutex has a level, it must be the last mutex with a level * which you entered in this thread. You cannot interleave exits of * mutexes with levels; they must nest. *
  4. If this read access is inside another read access, this method * will return immediately. *
  5. If this read access is the outermost read access, and not inside any * write access, it will return immediately - unless you have called * {@link Mutex#postWriteRequest} within the scope of this or some inner * read access in this thread, in which case all such write requests will * be run now (in order). This thread will compete for write access with * any other threads waiting for the mutex, after any other outermost * readers have also finished. This method will not return until the * posted requests are complete. *
  6. If this read access is the outermost read access within a write access, * it will return immediately unless 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 exitReadAccess() { parent.exitReadAccess(); } /** * Exit the write mutex. * For important usage instructions, see {@link #enterWriteAccess}. * *

Detailed behavior: *

    *
  1. You must have already entered this mutex in write mode (once for * every time you exit it). *
  2. You must exit a mutex in the same thread you entered it. *
  3. If this mutex has a level, it must be the last mutex with a level * which you entered in this thread. You cannot interleave exits of * mutexes with levels; they must nest. *
  4. If this write access is inside another write access, this method * will return immediately. *
  5. If this write access is the outermost write access, it will return * immediately - unless you have called {@link Mutex#postReadRequest} * within the scope of this or some inner write access in this thread, * in which case all such read requests will be run now (in order). * No other writer may enter before they are started, but other readers * might enter before they are finished. This method will not return until * the posted requests are complete. *
*/ public void exitWriteAccess() { parent.exitWriteAccess(); } } }