Index: openide/src/org/openide/util/Mutex.java =================================================================== RCS file: /cvs/openide/src/org/openide/util/Mutex.java,v retrieving revision 1.56 diff -u -r1.56 Mutex.java --- openide/src/org/openide/util/Mutex.java 27 Feb 2003 23:41:08 -0000 1.56 +++ openide/src/org/openide/util/Mutex.java 27 Mar 2003 23:03:57 -0000 @@ -24,16 +24,10 @@ * 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. Conversely, if you are the only reader you -* are allowed to enter the mutex as a writer. +* mutex as a reader. But you cannot enter the write mutex if you hold +* the read mutex, since that can cause deadlocks. *

-* If the mutex is used only by one thread, the thread can repeatedly -* enter it as a writer or reader. So one thread can never deadlock itself, -* whichever order operations are performed in. -*

-* There is no strategy to prevent starvation. -* Even if there is a writer waiting to enter, another reader might enter -* the section instead. + * This implementation will not starve a writer or reader indefinitely. *

* Examples of use: * @@ -61,52 +55,14 @@ * } * * -* @author Ales Novak +* @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 (); - - // lock mode constants - /** Lock free */ - private static final int NONE = 0x0; - /** Enqueue all requests */ - private static final int CHAIN = 0x1; - /** eXclusive */ - private static final int X = 0x2; - /** Shared */ - private static final int S = 0x3; - - /** number of modes */ - private static final int MODE_COUNT = 0x4; - - /** compatibility matrix */ // [requested][granted] - private static final boolean[][] cmatrix = {null, null, // NONE, CHAIN - /* NONE */ /* CHAIN */ /* X */ /* S */ // granted - /*r X */ {true, false, false, false}, - /*e S */ {true, false, false, true} - /*q */ - //uested - }; - - /** Decides whether two locks are compatible.? - * @param granted? - * @param requested? - * @return true iff they are compatible? - */ - private static boolean compatibleLocks(int granted, int requested) { - return cmatrix[requested][granted]; - } - /** granted mode */ - private int grantedMode = NONE; - /** protects internal data structures */ - private /*final*/ Object LOCK; - /** threads that - owns or waits for this mutex */ - private /*final*/ Map registeredThreads; - /** number of threads that holds S mode (readersNo == "count of threads in registeredThreads that holds S") */ // NOI18N - private int readersNo = 0; - /** a queue of waiting threads for this mutex */ - private List waiters; + + 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 @@ -117,57 +73,41 @@ * @param lock lock to use */ public Mutex (Object lock) { - init(lock); + this.lock = lock; } /** Default constructor. */ public Mutex() { - init(new InternalLock()); + 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 { - init(new InternalLock()); privileged.setParent(this); } } - /** Initiates this Mutex */ - private void init(Object lock) { - this.LOCK = lock; - this.registeredThreads = new HashMap(7); - this.waiters = new LinkedList(); - } - /** Run an action only with read access. * See class description re. entering for write access within the dynamic scope. * @param action the action to perform * @return the object returned from {@link Mutex.Action#run} */ public Object readAccess (Action action) { - if (this == EVENT) { - try { - return doEventAccess (action); - } catch (MutexException e) { - InternalError err = new InternalError("Exception from non-Exception Action"); // NOI18N - ErrorManager.getDefault().annotate(err, e.getException()); - throw err; - } + return eventAccess(action); } - - Thread t = Thread.currentThread(); - readEnter(t); + enterReadAccess(); try { return action.run(); } finally { - leave(t); + exitReadAccess(); } } @@ -196,49 +136,55 @@ * @see #readAccess(Mutex.Action) */ public Object readAccess (ExceptionAction action) throws MutexException { - if (this == EVENT) { - return doEventAccess (action); + return eventAccess(action); } - - Thread t = Thread.currentThread(); - readEnter(t); + enterReadAccess(); try { return action.run(); - } catch (RuntimeException e) { - throw e; } catch (Exception e) { throw new MutexException(e); - } catch (LinkageError e) { - // #20467 - throw new MutexException(new InvocationTargetException(e)); - } catch (StackOverflowError e) { - // #20467 - throw new MutexException(new InvocationTargetException(e)); } finally { - leave(t); + exitReadAccess(); } } /** Run an action with read access, returning no result. - * It may be run asynchronously. - * + * 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) { - doEvent (action); + eventAccess(action, true); return; } - - Thread t = Thread.currentThread(); - readEnter(t); - try { - action.run(); - } finally { - leave(t); + 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(); + } + } + }); } } @@ -250,23 +196,14 @@ * @return the result of {@link Mutex.Action#run} */ public Object writeAccess (Action action) { - if (this == EVENT) { - try { - return doEventAccess (action); - } catch (MutexException e) { - InternalError err = new InternalError("Exception from non-Exception Action"); // NOI18N - ErrorManager.getDefault().annotate(err, e.getException()); - throw err; - } + return eventAccess(action); } - - Thread t = Thread.currentThread(); - writeEnter(t); + enterWriteAccess(); try { return action.run(); } finally { - leave(t); + exitWriteAccess(); } } @@ -292,50 +229,56 @@ * @see #readAccess(Mutex.ExceptionAction) */ public Object writeAccess (ExceptionAction action) throws MutexException { - if (this == EVENT) { - return doEventAccess (action); + return eventAccess(action); } - - Thread t = Thread.currentThread(); - writeEnter(t); + enterWriteAccess(); try { return action.run(); - } catch (RuntimeException e) { - throw e; } catch (Exception e) { throw new MutexException(e); - } catch (LinkageError e) { - // #20467 - throw new MutexException(new InvocationTargetException(e)); - } catch (StackOverflowError e) { - // #20467 - throw new MutexException(new InvocationTargetException(e)); } finally { - leave(t); + exitWriteAccess(); } } /** Run an action with write access and return no result. - * It may be run asynchronously. - * + * 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) { - doEvent (action); + eventAccess(action, true); return; } - - Thread t = Thread.currentThread(); - writeEnter(t); - try { - action.run(); - } finally { - leave(t); + 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(); + } + } + }); } } @@ -355,7 +298,28 @@ * @param run runnable to run */ public void postReadRequest (final Runnable run) { - postRequest(S, 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 @@ -372,613 +336,349 @@ * @param run runnable to run */ public void postWriteRequest (Runnable run) { - postRequest(X, run); - } - - /** toString */ - public String toString() { if (this == EVENT) { - return "Mutex.EVENT"; // NOI18N - } - - String newline = System.getProperty("line.separator"); - StringBuffer sbuff = new StringBuffer(512); - synchronized(LOCK){ - sbuff.append("threads: ").append(registeredThreads).append(newline); // NOI18N - sbuff.append("readersNo: ").append(readersNo).append(newline); // NOI18N - sbuff.append("waiters: ").append(waiters).append(newline); // NOI18N - sbuff.append("grantedMode: ").append(grantedMode).append(newline); // NOI18N + eventAccess(run, false); + return; } - return sbuff.toString(); - } - - // priv methods ----------------------------------------- - - /** enters this mutex for writing */ - private void writeEnter(Thread t) { - enter(X, t, true); - } - /** enters this mutex for reading */ - private void readEnter(Thread t) { - enter(S, t, true); - } - - /** enters this mutex with given mode - * @param requested one of S, X - * @param t - */ - private boolean enter(int requested, Thread t, boolean block) { - - QueueCell cell = null; - int loopc = 0; - - for (;;) { - loopc++; - synchronized (LOCK) { - // does the thread reenter this mutex? - ThreadInfo info = getThreadInfo(t); - - if (info != null) { - if (grantedMode == NONE) { - // defensive - throw new IllegalStateException(); + 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(); } - // reenters - // requested == S -> always succeeds - // info.mode == X -> always succeeds - if (((info.mode == S) && (grantedMode == X)) || - ((info.mode == X) && (grantedMode == S))) { - // defensive - throw new IllegalStateException(); - } - if ((info.mode == X) || - (info.mode == requested)) { // X - X, X - S, S - S - if (info.forced) { - info.forced = false; - } else { - info.counts[requested]++; - if ((requested == S) && (info.counts[requested] == 1)) { - readersNo++; - info.stamp = readersNo; - } - } - return true; - } else if (canUpgrade(info.mode, requested)) { // S - X and no holders - info.mode = X; - info.counts[requested]++; - info.rsnapshot = info.counts[S]; - if (grantedMode == S) { - grantedMode = X; - } else if (grantedMode == X) { - // defensive - throw new IllegalStateException(); - } // else if grantedMode == CHAIN - let it be - - return true; - } else { // S - X and holders - if (Boolean.getBoolean("netbeans.debug.threads")) { // NOI18N - System.err.println("WARNING: Going from readAccess to writeAccess"); - Thread.dumpStack(); - } - // chain follows - } - } else { // first acquisition - if (isCompatible(requested)) { // NONE -> S,X or S -> S - grantedMode = requested; - registeredThreads.put(t, info = new ThreadInfo(t, requested)); - if (requested == S) { - readersNo++; - info.stamp = readersNo; - } - return true; - } // else { - // granted is S and requested is X - // granted is X and requested is S or X - //} - } - - if (! block) { - return false; + ti.lateWriteActions.add(run); } - grantedMode = CHAIN; - cell = chain(requested, t, 0); - } // sync - cell.sleep(); - } // for - } - - /** privilegedEnter serves for processing posted requests */ - private boolean reenter(Thread t, int mode, int stamp) { - - // from leaveX -> grantedMode is NONE or S - if (mode == S) { - if (grantedMode != NONE && grantedMode != S) { - throw new IllegalStateException(this.toString()); } - enter(mode, t, true); - return false; - } - - // assert (mode == X) - - ThreadInfo tinfo = getThreadInfo(t); - boolean chainFromLeaveX = (grantedMode == CHAIN && tinfo != null && tinfo.counts[X] > 0); - - // process grantedMode == X or CHAIN from leaveX OR grantedMode == NONE from leaveS - if (grantedMode == X || grantedMode == NONE || chainFromLeaveX) { - enter(mode, t, true); - return false; - } else { // remains grantedMode == CHAIN or S from leaveS, so it will be CHAIN - if (readersNo == 0) { - throw new IllegalStateException(this.toString()); - } - ThreadInfo info = new ThreadInfo(t, mode); - registeredThreads.put(t, info); - // prevent from grantedMode == NONE (another thread - leaveS) - readersNo += 2; - info.stamp = stamp; - // prevent from new readers - grantedMode = CHAIN; - return true; - } // else X means ERROR!!! - } - - /** @param t holds S (one entry) and wants X, grantedMode != NONE && grantedMode != X */ - private void privilegedEnter(Thread t, int mode) { - boolean decrease = true; - ThreadInfo info; - - synchronized (LOCK) { - info = getThreadInfo(t); - } - - for (;;) { - QueueCell cell; - synchronized (LOCK) { - - if (decrease) { - decrease = false; - readersNo -= 2; - } - - // always chain this thread - // since there can be another one - // in the queue with higher priority - grantedMode = CHAIN; - cell = chain(mode, t, Integer.MAX_VALUE - info.stamp); - - if (readersNo == 0) { // seems I may enter - // no one has higher prio? - if (waiters.get(0) == cell) { - waiters.remove(0); - return; - } else { - grantedMode = NONE; - wakeUpOthers(); - } - } - } // synchronized (LOCK) - cell.sleep(); - // cell already removed from waiters here - } - } - - /** Leaves this mutex */ - private void leave(Thread t) { - ThreadInfo info; - int postedMode = NONE; - boolean needLock = false; - - synchronized (LOCK) { - info = getThreadInfo(t); - - switch (grantedMode) { - case NONE: - throw new IllegalStateException(); - - case CHAIN: - if (info.counts[X] > 0) { - // it matters that X is handled first - see ThreadInfo.rsnapshot - postedMode = leaveX(info); - } else if (info.counts[S] > 0) { - postedMode = leaveS(info); - } else { - throw new IllegalStateException(); - } - break; - - case X: - postedMode = leaveX(info); - break; - - case S: - postedMode = leaveS(info); - break; - } // switch - - // do not give up LOCK until queued runnables are run - if (postedMode != NONE) { - int runsize = info.getRunnableCount(postedMode); - if (runsize != 0) { - needLock = reenter(t, postedMode, info.stamp); // grab lock - } + if (synch) { + run.run(); } - } // sync - - // check posted requests - if (postedMode != NONE && info.getRunnableCount(postedMode) > 0) { + } else if (canRead()) { + throw new IllegalStateException("Cannot call postWriteRequest while holding a read mutex; consider using writeAccess(Runnable)"); // NOI18N + } else { + enterWriteAccess(); try { - if (needLock) { // go from S to X or CHAIN - privilegedEnter(t, postedMode); - } - // holds postedMode lock here - List runnables = info.dequeue(postedMode); - final int size = runnables.size(); - for (int i = 0; i < size; i++) { - try { - Runnable r = (Runnable) runnables.get(i); - r.run(); - } catch (Exception e) { - ErrorManager.getDefault().notify(e); - } catch (LinkageError e) { - // #20467 - ErrorManager.getDefault().notify(e); - } catch (StackOverflowError e) { - // #20467 - ErrorManager.getDefault().notify(e); - } // try - } // for - // help gc - runnables = null; + run.run(); } finally { - leave(t); // release lock grabbed - shared + exitWriteAccess(); } - } // mode - } - - /** Leaves the lock supposing that info.counts[X] is greater than zero */ - private int leaveX(ThreadInfo info) { - - if ((info.counts[X] <= 0) || - (info.rsnapshot > info.counts[S])) { - // defensive - throw new IllegalStateException(); } + } - if (info.rsnapshot == info.counts[S]) { - info.counts[X]--; - if (info.counts[X] == 0) { - info.rsnapshot = 0; - // downgrade the lock - if (info.counts[S] > 0) { - info.mode = grantedMode = S; - } else { - info.mode = grantedMode = NONE; - registeredThreads.remove(info.t); - } - - if (info.getRunnableCount(S) > 0) { - return S; - } - - // mode has changed - wakeUpOthers(); - } - } else { - // rsnapshot < counts[S] - - if (info.counts[S] <= 0) { - // defensive - throw new IllegalStateException(); - } - - if (--info.counts[S] == 0) { - if (readersNo <= 0) { - throw new IllegalStateException(); + /** + * 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()); + } } - readersNo--; - return X; + // Uncontended or shared, go ahead. + new ThreadInfo(false).register(); + semaphore++; } } - - return NONE; } - /** Leaves the lock supposing that info.counts[S] is greater than zero */ - private int leaveS(ThreadInfo info) { - if ((info.counts[S] <= 0) || - (info.counts[X] > 0)) { - // defensive - throw new IllegalStateException(); - } - - info.counts[S]--; - if (info.counts[S] == 0) { - - // remove the thread - info.mode = NONE; - registeredThreads.remove(info.t); - - // downsize readersNo - if (readersNo <= 0) { - throw new IllegalStateException(); - } - readersNo--; - - if (readersNo == 0) { - // set grantedMode to NONE - // and then wakeUp others - either immediately - // or in privelegedEnter() - grantedMode = NONE; - - if (info.getRunnableCount(X) > 0) { - return X; - } - - wakeUpOthers(); - } else if (info.getRunnableCount(X) > 0) { - return X; - } else if ((grantedMode == CHAIN) && - (readersNo == 1)) { - // can be the mode advanced from CHAIN? Examine first item of waiters! - - for (int i = 0; i < waiters.size(); i++) { - QueueCell qc = (QueueCell) waiters.get(i); - synchronized (qc) { - if (qc.isGotOut()) { - waiters.remove(i--); - continue; - } - - ThreadInfo tinfo = getThreadInfo(qc.t); - - if (tinfo != null) { - if (tinfo.mode == S) { - if (qc.mode != X) { - // defensive - throw new IllegalStateException(); - } - - if (waiters.size() == 1) { - grantedMode = X; - } // else let CHAIN - tinfo.mode = X; - waiters.remove(i); - qc.wakeMeUp(); - } - } // else first request is a first X request of some thread - break; - } // sync (qc) - } // for - } // else - } // count[S] == 0 - - return NONE; - } - - /** Adds this thread to the queue of waiting threads - * @warning LOCK must be held - */ - private QueueCell chain(final int requested, final Thread t, final int priority) { - - //long timeout = 0; - - /* - if (killDeadlocksOn) { - checkDeadlock(requested, t); - timeout = (isDispatchThread() || checkAwtTreeLock() ? TIMEOUT : 0); - } - */ - - QueueCell qc = new QueueCell(requested, t); - //qc.timeout = timeout; - qc.priority2 = priority; - - final int size = waiters.size(); - if (size == 0) { - waiters.add(qc); - } else { - QueueCell cursor; - int i = 0; - do { - cursor = (QueueCell) waiters.get(i); - if (cursor.getPriority() < qc.getPriority()) { - waiters.add(i, qc); - break; + /** + * 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; } - i++; - } while (i < size); - if (i == size) { - waiters.add(qc); + } 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(); } } - return qc; } - /** Scans through waiters and wakes up them */ - private void wakeUpOthers() { - - if ((grantedMode == X) || - (grantedMode == CHAIN)) { - // defensive - throw new IllegalStateException(); - } - - if (waiters.size() == 0) { - return; - } - - for (int i = 0; i < waiters.size(); i++) { - QueueCell qc = (QueueCell) waiters.get(i); - - synchronized (qc) { - if (qc.isGotOut()) { - // bogus waiter - waiters.remove(i--); - continue; + /** + * 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 } - - if (compatibleLocks(grantedMode, qc.mode)) { // woken S -> should I wake X? -> no - waiters.remove(i--); - qc.wakeMeUp(); - grantedMode = qc.mode; - if (getThreadInfo(qc.t) == null) { - // force to have a record since recorded threads - // do not use isCompatible call - ThreadInfo ti = new ThreadInfo(qc.t, qc.mode); - ti.forced = true; - if (qc.mode == S) { - readersNo++; - } - registeredThreads.put(qc.t, ti); + } else { + // 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()); } - } else { - grantedMode = CHAIN; - break; } - } // sync (qc) + // Uncontended, go ahead. + new ThreadInfo(true).register(); + semaphore = -1; + } } } - /** Posts new request for current thread - * @param mutexMode mutex mode for which the action is rquested - * @param run the action - */ - private void postRequest(int mutexMode, Runnable run) { - - if (this == EVENT) { - doEventRequest(run); - return; - } - - Thread t = Thread.currentThread(); - ThreadInfo info; - - synchronized (LOCK) { - info = getThreadInfo(t); - if (info != null) { - // the same mode and mutex is not entered in the other mode - // assert (mutexMode == S || mutexMode == X) - if (mutexMode == info.mode && info.counts[S + X - mutexMode] == 0) { - enter(mutexMode, t, true); - } else { // the mutex is held but can not be entered in X mode - info.enqueue(mutexMode, run); - return; - } + /** + * 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()); } - } - - // this mutex is not held - if (info == null) { - enter(mutexMode, t, true); try { - run.run(); + Iterator it = lateReadActions.iterator(); + while (it.hasNext()) { + Runnable r = (Runnable)it.next(); + r.run(); + } } finally { - leave(t); + exitReadAccess(); } - return; - } - - // run it immediately - // info != null so enter(...) succeeded - try { - run.run(); - } finally { - leave(t); } } - - /** @param requested is requested mode of locking - * @return true if and only if current mode and requested mode are compatible - */ - private boolean isCompatible(int requested) { - return compatibleLocks(grantedMode, requested); - } - - private ThreadInfo getThreadInfo(Thread t) { - return (ThreadInfo) registeredThreads.get(t); - } - - private boolean canUpgrade(int threadGranted, int requested) { - return (threadGranted == S) && (requested == X) && (readersNo == 1); + + /** + * 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; + } } - - // ------------------------------- EVENT METHODS ---------------------------- - /** Runs the runnable in event queue, either immediatelly, - * or it posts it into the queue. - */ - private static void doEvent (Runnable run) { - if (EventQueue.isDispatchThread ()) { - run.run (); - } else { - EventQueue.invokeLater (run); + + /** + * 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; + } } } - /** Methods for access to event queue. - * @param run runabble to post later - */ - private static void doEventRequest (Runnable run) { - EventQueue.invokeLater (run); + /** toString */ + public String toString() { + if (this == EVENT) { + return "Mutex.EVENT"; // NOI18N + } + return "Mutex"; // NOI18N } + + // --- Internal data structures --- - /** Methods for access to event queue and waiting for result. - * @param run runabble to post later - */ - private static Object doEventAccess (final ExceptionAction run) throws MutexException { + /** + * 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; - if (isDispatchThread()) { - try { - return run.run (); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new MutexException (e); - } catch (LinkageError e) { - // #20467 - throw new MutexException(new InvocationTargetException(e)); - } catch (StackOverflowError e) { - // #20467 - throw new MutexException(new InvocationTargetException(e)); - } + /** + * 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); } - - final Throwable[] arr = new Throwable[1]; - try { - final Object[] res = new Object[1]; - EventQueue.invokeAndWait (new Runnable () { - public void run () { - try { - res[0] = run.run (); - } catch (Exception e) { - arr[0] = e; - } catch (LinkageError e) { - // #20467 - arr[0] = e; - } catch (StackOverflowError e) { - // #20467 - arr[0] = e; - } - } - }); - if (arr[0] == null) { - return res[0]; - } - } catch (InterruptedException e) { - arr[0] = e; - } catch (InvocationTargetException e) { - arr[0] = e; + + public String toString() { + return "ThreadInfo<" + t + ",writer=" + writer + ",extraReads=" + extraReads + ",extraWrites=" + extraWrites + ",lateReadActions=" + lateReadActions + ",lateWriteActions=" + lateWriteActions + ">"; // NOI18N } - if(arr[0] instanceof RuntimeException) { - throw (RuntimeException)arr[0]; - } - - throw notifyException(ErrorManager.EXCEPTION, arr[0]); } + + // --- Mutex.EVENT stuff --- /** @return true iff current thread is EventDispatchThread */ - static boolean isDispatchThread() { + 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 @@ -987,49 +687,104 @@ } return dispatch; } - - /** Notify exception and returns new MutexException */ - private static final MutexException notifyException(int severity, Throwable t) { - if (t instanceof InvocationTargetException) { - t = unfoldInvocationTargetException((InvocationTargetException) t); - } - - if (t instanceof Error) { - annotateEventStack(t); - throw (Error) t; - } - - if (t instanceof RuntimeException) { - annotateEventStack(t); - throw (RuntimeException) t; + + /** + * 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]; + } } - - MutexException exc = new MutexException((Exception) t); - ErrorManager.getDefault().annotate(exc, t); - return exc; } - private static final void annotateEventStack(Throwable t) { - ErrorManager.getDefault().annotate(t, - new Exception("Caught here in mutex")); // NOI18N + /** + * 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]; + } } - private static final Throwable unfoldInvocationTargetException(InvocationTargetException e) { - Throwable ret; - do { - ret = e.getTargetException(); - if (ret instanceof InvocationTargetException) { - e = (InvocationTargetException) ret; - } else { - e = null; + /** + * 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()); + } } - } while (e != null); - - return ret; + } } - - // --------------------------------------------- END OF EVENT METHODS ------------------------------ - + + // --- Action interfaces --- + /** Action to be executed in a mutex without throwing any checked exceptions. * Unchecked exceptions will be propagated to calling code. */ @@ -1055,72 +810,6 @@ public Object run () throws Exception; } - private static final class ThreadInfo { - - /** t is forcibly sent from waiters to enter() by wakeUpOthers() */ - boolean forced; - /** ThreadInfo for this Thread */ - final Thread t; - /** granted mode */ - int mode; - // 0 - NONE, 1 - CHAIN, 2 - X, 3 - S - /** enter counter */ - int[] counts; - /** queue of runnable rquests that are to be executed (in X mode) right after S mode is left - * deadlock avoidance technique - */ - List[] queues; - /** time stamp of the thread - * set only for S mode - */ - int stamp; - - /** value of counts[S] when the mode was upgraded - * rsnapshot works as follows: - * if a thread holds the mutex in the S mode and it reenters the mutex - * and requests X and the mode can be granted (no other readers) then this - * variable is set to counts[S]. This is used in the leave method in the X branch. - * (X mode is granted by other words) - * If rsnapshot is less than counts[S] then the counter is decremented etc. If the rsnapshot is - * equal to count[S] then count[X] is decremented. If the X counter is zeroed then - * rsnapshot is zeroed as well and current mode is downgraded to S mode. - * rsnapshot gets less than counts[S] if current mode is X and the mutex is reentered - * with S request. - */ - int rsnapshot; - - public ThreadInfo(Thread t, int mode) { - this.t = t; - this.mode = mode; - this.counts = new int[MODE_COUNT]; - this.queues = new List[MODE_COUNT]; - counts[mode] = 1; - } - - public String toString() { - return super.toString() + " thread: " + t + " mode: " + mode + " X: " + counts[2] + " S: " + counts[3]; // NOI18N - } - - /** Adds the Runnable into the queue of waiting requests */ - public void enqueue(int mode, Runnable run) { - if (queues[mode] == null) { - queues[mode] = new ArrayList(13); - } - queues[mode].add(run); - } - - /** @return a List of enqueued Runnables - may be null */ - public List dequeue(int mode) { - List ret = queues[mode]; - queues[mode] = null; - return ret; - } - - public int getRunnableCount(int mode) { - return (queues[mode] == null ? 0 : queues[mode].size()); - } - } - /** This class is defined only for better understanding of thread dumps where are informations like * java.lang.Object@xxxxxxxx owner thread_x * wait for enter thread_y @@ -1129,64 +818,6 @@ InternalLock() {} } - private static final class QueueCell { - - int mode; - Thread t; - boolean signal; - /** if the thread is owner of AWTTreeLock then the timeout is greater than zero */ - long timeout; - boolean left; - /** priority of the cell */ - int priority2; - - public QueueCell(int mode, Thread t) { - this.mode = mode; - this.t = t; - this.timeout = 0; - this.left = false; - this.priority2 = 0; - } - - public String toString() { - return super.toString() + " mode: " + mode + " thread: " + t; // NOI18N - } - - /** @return priority of this cell */ - public long getPriority() { - return (priority2 == 0 ? t.getPriority() : priority2); - } - - /** @return true iff the thread left sleep */ - public boolean isGotOut() { - return left; - } - - /** current thread will sleep until wakeMeUp is called - * if wakeMeUp was already called then the thread will not sleep - */ - public synchronized void sleep() { - try { - while (!signal) { - try { - wait(); - return; - } catch (InterruptedException e) { - ErrorManager.getDefault().notify(e); - } - } - } finally { - left = true; - } - } - - /** sends signal to a sleeper - to a thread that is in the sleep() */ - public void wakeMeUp() { - signal = true; - notifyAll(); - } - } - /** Provides access to Mutex's internal methods. * * This class can be used when one wants to avoid creating a @@ -1214,19 +845,19 @@ } public void enterReadAccess() { - parent.readEnter(Thread.currentThread()); + parent.enterReadAccess(); } public void enterWriteAccess() { - parent.writeEnter(Thread.currentThread()); + parent.enterWriteAccess(); } public void exitReadAccess() { - parent.leave(Thread.currentThread()); + parent.exitReadAccess(); } public void exitWriteAccess() { - parent.leave(Thread.currentThread()); + parent.exitWriteAccess(); } } } Index: openide/test/unit/src/org/openide/util/MutexTest.java =================================================================== RCS file: /cvs/openide/test/unit/src/org/openide/util/MutexTest.java,v retrieving revision 1.7 diff -u -r1.7 MutexTest.java --- openide/test/unit/src/org/openide/util/MutexTest.java 27 Feb 2003 23:41:26 -0000 1.7 +++ openide/test/unit/src/org/openide/util/MutexTest.java 27 Mar 2003 23:03:57 -0000 @@ -133,7 +133,8 @@ /** Behaviour of postReadRequest is defined by this test. */ - public void testPostReadRequest () { + // Cannot go S -> X + public void DONOTtestPostReadRequest () { State s = new State (); @@ -162,7 +163,8 @@ } /** Test enter from S mode to X mode */ - public void testXtoS() { + // You cannot go from S -> X + public void DONOTtestXtoS() { State s = new State (); p.enterReadAccess (); @@ -409,7 +411,8 @@ /** Tests postWriteRequest and postReadRequest while the Mutex is contended in S mode by * another thread as well as this thread. */ - public void testSContendedSPsPx() throws InterruptedException { + // Cannot go S -> X + public void DONOTtestSContendedSPsPx() throws InterruptedException { asyncEnter(p, false, 2000); State s = new State (); @@ -439,7 +442,8 @@ /** The Mutex is held in S mode by a thread which also posted a * write request. Another thread tries enterWriteAccess. */ - public void testSPxContendedX() throws Exception { + // No good. Cannot call postWriteRequest from a read mutex. + public void DONOTtestSPxContendedX() throws Exception { final State s = new State (); asyncEnter(p, false, 2000, new Runnable() {