All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.util.Mutex Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 */

package org.openide.util;

import java.awt.EventQueue;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

/** 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. Conversely, if you are the only reader you * can enter the mutex as a writer, but you'll be warned because it is very * deadlock prone (two readers trying to get write access concurently). *

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

* Examples of use: * *

* Mutex m = new Mutex();
*
* // Grant write access, compute an integer and return it:
* return m.writeAccess(new Mutex.Action<Integer>(){
*     public Integer run() {
*         return 1;
*     }
* });
*
* // Obtain read access, do some computation,
* // possibly throw an IOException:
* try {
*     m.readAccess(new Mutex.ExceptionAction<Void>() {
*         public Void run() throws IOException {
*             if (...) throw new IOException();
*             return null;
*         }
*     });
* } catch (MutexException ex) {
*     throw (IOException) ex.getException();
* }
*
* // check whether you are already in read access
* if (m.isReadAccess()) {
*     // do your work
* }
* 
* * @author Ales Novak */ public final class Mutex extends Object { /** counter of created mutexes */ private static int counter; /** logger for things that happen in mutex */ private static final Logger LOG = Logger.getLogger(Mutex.class.getName()); /** Mutex that allows code to be synchronized with the AWT event dispatch thread. *

* When the Mutex methods are invoked on this mutex, the methods' semantics * change as follows: *

    *
  • The {@link #isReadAccess} and {@link #isWriteAccess} methods * return true if the current thread is the event dispatch thread * and false otherwise. *
  • The {@link #postReadRequest} and {@link #postWriteRequest} methods * asynchronously execute the {@link java.lang.Runnable} passed in their * run parameter on the event dispatch thead. *
  • The {@link #readAccess(java.lang.Runnable)} and * {@link #writeAccess(java.lang.Runnable)} methods asynchronously execute the * {@link java.lang.Runnable} passed in their run parameter * on the event dispatch thread, unless the current thread is * the event dispatch thread, in which case * run.run() is immediately executed. *
  • The {@link #readAccess(Mutex.Action)}, * {@link #readAccess(Mutex.ExceptionAction action)}, * {@link #writeAccess(Mutex.Action action)} and * {@link #writeAccess(Mutex.ExceptionAction action)} * methods synchronously execute the {@link Mutex.ExceptionAction} * passed in their action parameter on the event dispatch thread, * unless the current thread is the event dispatch thread, in which case * action.run() is immediately executed. *
*/ public static final Mutex EVENT = new Mutex(); /** this is used from tests to prevent upgrade from readAccess to writeAccess * by strictly throwing exception. Otherwise we just notify that using ErrorManager. */ static boolean beStrict; // 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 { true, false, false, false },{ true, false, false, true } }; /** granted mode */ private int grantedMode = NONE; /** The mode the mutex was in before it started chaining */ private int origMode; /** protects internal data structures */ private final Object LOCK; /** wrapper, if any */ private final Executor wrapper; /** 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; /** identification of the mutex */ private int cnt; /** 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 = init(lock); this.wrapper = null; } /** Default constructor. */ public Mutex() { this.LOCK = init(new InternalLock()); this.wrapper = null; } /** @param privileged can enter privileged states of this Mutex * This helps avoid creating of custom Runnables. */ public Mutex(Privileged privileged) { if (privileged == null) { throw new IllegalArgumentException("privileged == null"); //NOI18N } else { this.LOCK = init(new InternalLock()); privileged.setParent(this); } this.wrapper = null; } /** Constructor for those who wish to do some custom additional tasks * whenever an action or runnable is executed in the {@link Mutex}. This * may be useful for wrapping all the actions with custom {@link ThreadLocal} * value, etc. Just implement the {@link Executor}'s execute(Runnable) * method and do pre and post initialization tasks before running the runnable. *

* The {@link Executor#execute} method shall return only when the passed in * {@link Runnable} is finished, otherwise methods like {@link Mutex#readAccess(Action)} and co. * might not return proper result. * * @param privileged can enter privileged states of this Mutex * @param executor allows to wrap the work of the mutex with a custom code * @since 7.12 */ public Mutex(Privileged privileged, Executor executor) { LOCK = new Mutex(privileged); this.wrapper = executor; } /** Initiates this Mutex */ private Object init(Object lock) { this.registeredThreads = new HashMap(7); this.waiters = new LinkedList(); this.cnt = counter++; if (LOG.isLoggable(Level.FINER)) { LOG.log(Level.FINER, "[" + cnt + "] created here", new Exception()); } return lock; } /** 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 T readAccess(final Action action) { if (this == EVENT) { try { return doEventAccess(action); } catch (MutexException e) { throw (InternalError) new InternalError("Exception from non-Exception Action").initCause(e.getException()); // NOI18N } } if (wrapper != null) { try { return doWrapperAccess(action, null, true); } catch (MutexException e) { throw (InternalError) new InternalError("Exception from non-Exception Action").initCause(e.getException()); // NOI18N } } Thread t = Thread.currentThread(); readEnter(t); try { return action.run(); } finally { leave(t); } } /** 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 T readAccess(final ExceptionAction action) throws MutexException { if (this == EVENT) { return doEventAccess(action); } if (wrapper != null) { return doWrapperAccess(action, null, true); } Thread t = Thread.currentThread(); readEnter(t); try { return action.run(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new MutexException(e); } finally { leave(t); } } /** Run an action with read access, returning no result. * It may be run asynchronously. * * @param action the action to perform * @see #readAccess(Mutex.Action) */ public void readAccess(final Runnable action) { if (this == EVENT) { doEvent(action); return; } if (wrapper != null) { try { doWrapperAccess(null, action, true); return; } catch (MutexException ex) { throw new IllegalStateException(ex); } } Thread t = Thread.currentThread(); readEnter(t); try { action.run(); } finally { leave(t); } } /** 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 T writeAccess(Action action) { if (this == EVENT) { try { return doEventAccess(action); } catch (MutexException e) { throw (InternalError) new InternalError("Exception from non-Exception Action").initCause(e.getException()); // NOI18N } } if (wrapper != null) { try { return doWrapperAccess(action, null, false); } catch (MutexException e) { throw (InternalError) new InternalError("Exception from non-Exception Action").initCause(e.getException()); // NOI18N } } Thread t = Thread.currentThread(); writeEnter(t); try { return action.run(); } finally { leave(t); } } /** 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 T writeAccess(ExceptionAction action) throws MutexException { if (this == EVENT) { return doEventAccess(action); } if (wrapper != null) { return doWrapperAccess(action, null, false); } Thread t = Thread.currentThread(); writeEnter(t); try { return action.run(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new MutexException(e); } finally { leave(t); } } /** Run an action with write access and return no result. * It may be run asynchronously. * * @param action the action to perform * @see #writeAccess(Mutex.Action) * @see #readAccess(Runnable) */ public void writeAccess(final Runnable action) { if (this == EVENT) { doEvent(action); return; } if (wrapper != null) { try { doWrapperAccess(null, action, false); } catch (MutexException ex) { throw new IllegalStateException(ex); } return; } Thread t = Thread.currentThread(); writeEnter(t); try { action.run(); } finally { leave(t); } } /** Tests whether this thread has already entered the mutex in read access. * If it returns true, calling readAccess * will be executed immediatelly * without any blocking. * Calling postWriteAccess will delay the execution * of its Runnable until a readAccess section is over * and calling writeAccess is strongly prohibited and will * result in a warning as a deadlock prone behaviour. *

Warning: since a thread with write access automatically * has effective read access as well (whether or not explicitly requested), if * you want to check whether a thread can read some data, you should check for * either kind of access, e.g.: *

assert myMutex.isReadAccess() || myMutex.isWriteAccess();
* * @return true if the thread is in read access section * @since 4.48 */ public boolean isReadAccess() { if (this == EVENT) { return javax.swing.SwingUtilities.isEventDispatchThread(); } if (wrapper != null) { Mutex m = (Mutex)LOCK; return m.isReadAccess(); } Thread t = Thread.currentThread(); ThreadInfo info; synchronized (LOCK) { info = getThreadInfo(t); if (info != null) { if (info.counts[S] > 0) { return true; } } } return false; } /** Tests whether this thread has already entered the mutex in write access. * If it returns true, calling writeAccess will be executed * immediatelly without any other blocking. postReadAccess * will be delayed until a write access runnable is over. * * @return true if the thread is in write access section * @since 4.48 */ public boolean isWriteAccess() { if (this == EVENT) { return javax.swing.SwingUtilities.isEventDispatchThread(); } if (wrapper != null) { Mutex m = (Mutex)LOCK; return m.isWriteAccess(); } Thread t = Thread.currentThread(); ThreadInfo info; synchronized (LOCK) { info = getThreadInfo(t); if (info != null) { if (info.counts[X] > 0) { return true; } } } return false; } /** 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) { postRequest(S, run, null); } /** 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) { postRequest(X, run, null); } /** toString */ @Override public String toString() { if (this == EVENT) { return "Mutex.EVENT"; // NOI18N } String newline = System.getProperty("line.separator"); StringBuilder sbuff = new StringBuilder(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 } return sbuff.toString(); } // priv methods ----------------------------------------- /** enters this mutex for writing */ final void writeEnter(Thread t) { enter(X, t, true); } /** enters this mutex for reading */ final void readEnter(Thread t) { enter(S, t, true); } private void doLog(String action, Object ... params) { String tid = Integer.toHexString(Thread.currentThread().hashCode()); LOG.log(Level.FINER, "[#" + cnt + "@" + tid + "] " + action, params); } /** enters this mutex with given mode * @param requested one of S, X * @param t */ private boolean enter(int requested, Thread t, boolean block) { boolean log = LOG.isLoggable(Level.FINER); if (log) doLog("Entering {0}, {1}", requested, block); // NOI18N boolean ret = enterImpl(requested, t, block); if (log) doLog("Entering exit: {0}", ret); // NOI18N return ret; } private boolean enterImpl(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(); } // 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)) { if (info.forced) { info.forced = false; } else { if ((requested == X) && (info.counts[S] > 0)) { IllegalStateException e = new IllegalStateException("WARNING: Going from readAccess to writeAccess, see #10778: http://www.netbeans.org/issues/show_bug.cgi?id=10778 "); if (beStrict) { throw e; } Exceptions.printStackTrace(e); } info.counts[requested]++; if ((requested == S) && (info.counts[requested] == 1)) { readersNo++; } } return true; } else if (canUpgrade(info.mode, requested)) { IllegalStateException e = new IllegalStateException("WARNING: Going from readAccess to writeAccess, see #10778: http://www.netbeans.org/issues/show_bug.cgi?id=10778 "); if (beStrict) { throw e; } Exceptions.printStackTrace(e); info.mode = X; info.counts[requested]++; info.rsnapshot = info.counts[S]; if (grantedMode == S) { setGrantedMode(X); } else if (grantedMode == X) { // defensive throw new IllegalStateException(); } // else if grantedMode == CHAIN - let it be return true; } else { IllegalStateException e = new IllegalStateException("WARNING: Going from readAccess to writeAccess through queue, see #10778: http://www.netbeans.org/issues/show_bug.cgi?id=10778 "); if (beStrict) { throw e; } Exceptions.printStackTrace(e); } } else { if (isCompatible(requested)) { setGrantedMode(requested); registeredThreads.put(t, info = new ThreadInfo(t, requested)); if (requested == S) { readersNo++; } return true; } } if (!block) { return false; } setGrantedMode(CHAIN); cell = chain(requested, t, 0); } // sync cell.sleep(); } // for } /** privilegedEnter serves for processing posted requests */ private boolean reenter(Thread t, int mode) { boolean log = LOG.isLoggable(Level.FINER); if (log) doLog("Re-Entering {0}", mode); // NOI18N boolean ret = reenterImpl(t, mode); if (log) doLog("Re-Entering exit: {0}", ret); // NOI18N return ret; } private boolean reenterImpl(Thread t, int mode) { // 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; // prevent from new readers setGrantedMode(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; synchronized (LOCK) { 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 setGrantedMode(CHAIN); cell = chain(mode, t, Integer.MAX_VALUE); if (readersNo == 0) { // seems I may enter // no one has higher prio? if (waiters.get(0) == cell) { waiters.remove(0); setGrantedMode(mode); return; } else { setGrantedMode(NONE); wakeUpOthers(); } } } // synchronized (LOCK) cell.sleep(); // cell already removed from waiters here } } /** Leaves this mutex */ final void leave(Thread t) { boolean log = LOG.isLoggable(Level.FINER); if (log) doLog("Leaving {0}", grantedMode); // NOI18N leaveImpl(t); if (log) doLog("Leaving exit: {0}", grantedMode); // NOI18N } private void leaveImpl(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); // grab lock } } } // sync // check posted requests if ((postedMode != NONE) && (info.getRunnableCount(postedMode) > 0)) { doLog("Processing posted requests: {0}", postedMode); // NOI18N 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) { Exceptions.printStackTrace(e); } catch (StackOverflowError e) { // Try as hard as possible to get a real stack trace e.printStackTrace(); Exceptions.printStackTrace(e); } catch (ThreadDeath td) { throw td; } catch (Error e) { Exceptions.printStackTrace(e); } } // for // help gc runnables = null; } finally { leave(t); // release lock grabbed - shared } } // 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 = S; setGrantedMode(S); } else { info.mode = NONE; setGrantedMode(NONE); registeredThreads.remove(info.t); } if (info.getRunnableCount(S) > 0) { // wake up other readers of this mutex wakeUpReaders(); 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(); } readersNo--; return X; } } 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() setGrantedMode(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 = 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) { setGrantedMode(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 if (qc.getPriority() == Integer.MAX_VALUE) { waiters.add(0, qc); } else { QueueCell cursor; int i = 0; do { cursor = waiters.get(i); if (cursor.getPriority() < qc.getPriority()) { waiters.add(i, qc); break; } i++; } while (i < size); if (i == size) { waiters.add(qc); } } return qc; } /** Scans through waiters and wakes up them */ private void wakeUpOthers() { if ((grantedMode == X) || (grantedMode == CHAIN)) { // defensive throw new IllegalStateException(); } if (waiters.isEmpty()) { return; } for (int i = 0; i < waiters.size(); i++) { QueueCell qc = waiters.get(i); synchronized (qc) { if (qc.isGotOut()) { // bogus waiter waiters.remove(i--); continue; } if (isCompatible(qc.mode)) { // woken S -> should I wake X? -> no waiters.remove(i--); qc.wakeMeUp(); setGrantedMode(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 { setGrantedMode(CHAIN); break; } } // sync (qc) } } private void wakeUpReaders() { assert (grantedMode == NONE) || (grantedMode == S); if (waiters.isEmpty()) { return; } for (int i = 0; i < waiters.size(); i++) { QueueCell qc = waiters.get(i); synchronized (qc) { if (qc.isGotOut()) { // bogus waiter waiters.remove(i--); continue; } if (qc.mode == S) { // readers only waiters.remove(i--); qc.wakeMeUp(); setGrantedMode(S); 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; readersNo++; registeredThreads.put(qc.t, ti); } } } // sync (qc) } } /** Posts new request for current thread * @param mutexMode mutex mode for which the action is rquested * @param run the action */ private void postRequest(final int mutexMode, final Runnable run, Executor exec) { if (this == EVENT) { doEventRequest(run); return; } if (wrapper != null) { Mutex m = (Mutex)LOCK; m.postRequest(mutexMode, run, wrapper); return; } final 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; } } } // this mutex is not held if (info == null) { if (exec != null) { class Exec implements Runnable { @Override public void run() { enter(mutexMode, t, true); try { run.run(); } finally { leave(t); } } } exec.execute(new Exec()); return; } enter(mutexMode, t, true); try { run.run(); } finally { leave(t); } 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) { // allow next reader in even in chained mode, if it was read access before if (requested == S && grantedMode == CHAIN && origMode == S) return true; return cmatrix[requested][grantedMode]; } private ThreadInfo getThreadInfo(Thread t) { return registeredThreads.get(t); } private boolean canUpgrade(int threadGranted, int requested) { return (threadGranted == S) && (requested == X) && (readersNo == 1); } // -------------------------------- WRAPPERS -------------------------------- private T doWrapperAccess( final ExceptionAction action, final Runnable runnable, final boolean readOnly ) throws MutexException { class R implements Runnable { T ret; MutexException e; @Override public void run() { Mutex m = (Mutex)LOCK; try { if (readOnly) { if (action != null) { ret = m.readAccess(action); } else { m.readAccess(runnable); } } else { if (action != null) { ret = m.writeAccess(action); } else { m.writeAccess(runnable); } } } catch (MutexException ex) { e = ex; } } } R run = new R(); Mutex m = (Mutex)LOCK; if (m.isWriteAccess() || m.isReadAccess()) { run.run(); } else { wrapper.execute(run); } if (run.e != null) { throw run.e; } return run.ret; } // ------------------------------- 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); } } /** Methods for access to event queue. * @param run runabble to post later */ private static void doEventRequest(Runnable run) { EventQueue.invokeLater(run); } /** Methods for access to event queue and waiting for result. * @param run runnable to post later */ private static T doEventAccess(final ExceptionAction run) throws MutexException { if (isDispatchThread()) { try { return run.run(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new MutexException(e); } } final AtomicReference> res = new AtomicReference>(); final AtomicBoolean started = new AtomicBoolean(); // #210991 final AtomicBoolean finished = new AtomicBoolean(); final AtomicBoolean invoked = new AtomicBoolean(); try { class AWTWorker implements Runnable { @Override public void run() { started.set(true); try { res.set(Union2.createFirst(run.run())); } catch (Exception e) { res.set(Union2.createSecond(e)); } catch (LinkageError e) { // #20467 res.set(Union2.createSecond(e)); } catch (StackOverflowError e) { // #20467 res.set(Union2.createSecond(e)); } finished.set(true); } } AWTWorker w = new AWTWorker(); EventQueue.invokeAndWait(w); invoked.set(true); } catch (InterruptedException e) { res.set(Union2.createSecond(e)); } catch (InvocationTargetException e) { res.set(Union2.createSecond(e)); } Union2 _res = res.get(); if (_res == null) { throw new IllegalStateException("#210991: got neither a result nor an exception; started=" + started + " finished=" + finished + " invoked=" + invoked); } else if (_res.hasFirst()) { return _res.first(); } else { Throwable e = _res.second(); if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { throw notifyException(e); } } } /** @return true iff current thread is EventDispatchThread */ 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; } /** Notify exception and returns new MutexException */ private static MutexException notifyException(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; } MutexException exc = new MutexException((Exception) t); exc.initCause(t); return exc; } private static void annotateEventStack(Throwable t) { //ErrorManager.getDefault().annotate(t, new Exception("Caught here in mutex")); // NOI18N } private static Throwable unfoldInvocationTargetException(InvocationTargetException e) { Throwable ret; do { ret = e.getTargetException(); if (ret instanceof InvocationTargetException) { e = (InvocationTargetException) ret; } else { e = null; } } while (e != null); return ret; } // --------------------------------------------- END OF EVENT METHODS ------------------------------ /** Action to be executed in a mutex without throwing any checked exceptions. * Unchecked exceptions will be propagated to calling code. * @param T the type of object to return */ public interface Action extends ExceptionAction { /** Execute the action. * @return any object, then returned from {@link Mutex#readAccess(Mutex.Action)} or {@link Mutex#writeAccess(Mutex.Action)} */ @Override T 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. * @param T the type of object to return */ public 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 */ T 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; /** 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; @SuppressWarnings("unchecked") public ThreadInfo(Thread t, int mode) { this.t = t; this.mode = mode; this.counts = new int[MODE_COUNT]; this.queues = (List[])new List[MODE_COUNT]; counts[mode] = 1; } @Override 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 */ private static final class InternalLock { InternalLock() { } } private static final class QueueCell { int mode; Thread t; boolean signal; boolean left; /** priority of the cell */ int priority2; public QueueCell(int mode, Thread t) { this.mode = mode; this.t = t; this.left = false; this.priority2 = 0; } @Override 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() { boolean wasInterrupted = false; try { while (!signal) { try { long start = System.currentTimeMillis(); wait(); if (LOG.isLoggable(Level.FINE) && EventQueue.isDispatchThread() && (System.currentTimeMillis() - start) > 1000) { LOG.log(Level.WARNING, toString(), new IllegalStateException("blocking on a mutex from EQ")); } return; } catch (InterruptedException e) { wasInterrupted = true; LOG.log(Level.FINE, null, e); } } } finally { left = true; if (wasInterrupted) { // #129003 Thread.currentThread().interrupt(); } } } /** 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 * 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.readEnter(Thread.currentThread()); } public void enterWriteAccess() { parent.writeEnter(Thread.currentThread()); } public void exitReadAccess() { parent.leave(Thread.currentThread()); } public void exitWriteAccess() { parent.leave(Thread.currentThread()); } } private void setGrantedMode(int mode) { if (grantedMode != CHAIN && mode == CHAIN) { origMode = grantedMode; } grantedMode = mode; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy