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

* 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 not starve a writer or reader indefinitely. *

* 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 ();
* }
* 
* * @author Ales Novak, Jesse Glick */ 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 (); private static final RequestProcessor LATER = new RequestProcessor("Mutex"); // NOI18N /** 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 */ public Mutex (Object lock) { this.lock = lock; } /** Default constructor. */ public Mutex() { this(new InternalLock()); } /** @param privileged can enter privileged states of this Mutex * This helps avoid creating of custom Runnables. */ public Mutex(Privileged privileged) { this(); if (privileged == null) { throw new IllegalArgumentException("privileged == null"); //NOI18N } else { privileged.setParent(this); } } /** 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) { 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. * @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 (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. * @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; 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. * * @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); } 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 ();
    *  }
    * 
* * @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); } enterWriteAccess(); try { return action.run(); } 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. * @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 (synch) { try { action.run(); } finally { exitWriteAccess(); } } else { LATER.post(new Runnable() { public void run() { enterWriteAccess(); try { action.run(); } finally { exitWriteAccess(); } } }); } } /** 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. * *

Warning: this method blocks.

* * @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 == null) 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. 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. * *

Warning: this method blocks.

* @param run runnable to run */ public void postWriteRequest (Runnable run) { if (this == EVENT) { eventAccess(run, false); return; } if (canWrite()) { boolean synch; synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti == null) throw new IllegalStateException(); if (!ti.writer) throw new IllegalStateException(); synch = ti.extraReads == 0; if (!synch) { if (ti.lateWriteActions == null) { ti.lateWriteActions = new LinkedList(); } ti.lateWriteActions.add(run); } } if (synch) { run.run(); } } else if (canRead()) { throw new IllegalStateException("Cannot call postWriteRequest while holding a read mutex; consider using writeAccess(Runnable)"); // NOI18N } else { enterWriteAccess(); try { run.run(); } finally { exitWriteAccess(); } } } /** * Enter the read mutex. * @see Mutex.Privileged#enterReadAccess */ void enterReadAccess() { synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti != null) { // Already in read or write mutex, can certainly enter read. ti.extraReads++; } else { // ti == null, 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. new ThreadInfo(false).register(); semaphore++; } } } /** * Exit the read mutex. * @see Mutex.Privileged#exitReadAccess */ void exitReadAccess() { List lateWriteActions = null; synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti == null) throw new IllegalStateException(); if (ti.extraReads > 0) { // Just mark them off. ti.extraReads--; if (ti.extraReads == 0 && ti.writer && ti.lateWriteActions != null) { // We are a writer who has just finished nested reads and now can // run some late write actions. if (semaphore != -1) throw new IllegalStateException(); lateWriteActions = ti.lateWriteActions; ti.lateWriteActions = null; } } else { // Really exiting. if (ti.writer) throw new IllegalStateException(); if (semaphore <= 0) throw new IllegalStateException(); if (ti.extraWrites > 0) throw new IllegalStateException(); ti.unregister(); semaphore--; lock.notifyAll(); if (semaphore == 0 && !threads.isEmpty()) throw new IllegalStateException(threads.toString()); } } if (lateWriteActions != null) { // We exited the lock, but since are a writer no one else can enter // anything, so that is safe. Iterator it = lateWriteActions.iterator(); while (it.hasNext()) { Runnable r = (Runnable)it.next(); r.run(); } } } /** * Enter the write mutex. * @see Mutex.Privileged#enterWriteAccess */ void enterWriteAccess() { synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti != null) { if (ti.writer) { // Already in write mutex, can reenter freely. ti.extraWrites++; } else { throw new IllegalStateException("Illegal mutex upgrade from read to write"); // NOI18N } } else { // ti == null, entering fresh. // Wait for any readers or writers to exit. while (semaphore != 0) { try { lock.wait(); } catch (InterruptedException e) { throw new IllegalStateException(e.toString()); } } // Uncontended, go ahead. new ThreadInfo(true).register(); semaphore = -1; } } } /** * Exit the write mutex. * @see Mutex.Privileged#exitWriteAccess */ void exitWriteAccess() { List lateReadActions = null; ThreadInfo ti; synchronized (lock) { ti = currentThreadInfo(); if (ti == null) throw new IllegalStateException(); if (!ti.writer) throw new IllegalStateException(); if (semaphore != -1) throw new IllegalStateException(); if (ti.extraWrites > 0) { // Just mark them off. ti.extraWrites--; } else if (ti.lateReadActions != null) { if (ti.extraReads > 0) throw new IllegalStateException(); // Will exit after running these. lateReadActions = ti.lateReadActions; ti.lateReadActions = null; } else { // Really exiting. if (ti.extraReads > 0) throw new IllegalStateException(); ti.unregister(); semaphore = 0; lock.notifyAll(); if (!threads.isEmpty()) throw new IllegalStateException(threads.toString()); } } if (lateReadActions != null) { // No other threads can enter before this because we have not yet // released the semaphore. synchronized (lock) { semaphore = 1; ti.writer = false; lock.notifyAll(); // Now the semaphore is released, we are in plain read access. if (threads.size() != 1) throw new IllegalStateException(threads.toString()); } 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. * @since XXX */ public boolean canRead() { if (this == EVENT) { return isDispatchThread(); } synchronized (lock) { if (semaphore == 0) { // Uncontended, obviously not. Only a shortcut, // next clause would catch it anyway. return false; } return currentThreadInfo() != null; } } /** * Check if the current thread is holding the write mutex. * @since XXX */ public boolean canWrite() { if (this == EVENT) { return isDispatchThread(); } 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 != null) { if (!ti.writer) throw new IllegalStateException(); return true; } else { return false; } } } /** toString */ public String toString() { if (this == EVENT) { return "Mutex.EVENT"; // NOI18N } return "Mutex"; // NOI18N } // --- 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. */ private final Object lock; /** * Map from threads to associated information. * A thread will only be in here if it is currently either in the mutex * somehow or waiting for it; when it is done, its entry is removed. */ private final Map threads = new HashMap(); // Map /** * Get information about the current thread. * Must be called with the lock held. */ private ThreadInfo currentThreadInfo() { return (ThreadInfo)threads.get(Thread.currentThread()); } /** * 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; /** * Information about a thread and what it is waiting to do. */ private final class ThreadInfo { /** * The associated thread. */ public final Thread t; /** * 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. */ public int extraWrites = 0; /** * Create new thread info based on the current thread. */ public ThreadInfo(boolean writer) { t = Thread.currentThread(); this.writer = writer; } /** * Register this thread info in the table. */ public void register() { threads.put(t, this); } /** * Remove this thread info from the table. */ public void unregister() { threads.remove(t); } 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 { final Exception[] exc = new Exception[0]; final Object[] result = new Object[0]; try { EventQueue.invokeAndWait(new Runnable() { public void run() { try { result[0] = action.run(); } catch (Exception e) { exc[0] = e; } } }); } 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] != null) { throw new MutexException(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 { final Object[] result = new Object[0]; 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 { 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, *
     * try {
     *     enterXAccess ();
     *     yourCustomMethod ();
     * } finally {
     *     exitXAccess ();
     * }
     * 
* 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.enterReadAccess(); } public void enterWriteAccess() { parent.enterWriteAccess(); } public void exitReadAccess() { parent.exitReadAccess(); } public void exitWriteAccess() { parent.exitWriteAccess(); } } }