/** * Redistribution and use of this software and associated documentation * ("Software"), with or without modification, are permitted provided * that the following conditions are met: * * 1. Redistributions of source code must retain copyright * statements and notices. Redistributions must also contain a * copy of this document. * * 2. Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. The name "Exolab" must not be used to endorse or promote * products derived from this Software without prior written * permission of Intalio, Inc. For written permission, * please contact info@exolab.org. * * 4. Products derived from this Software may not be called "Exolab" * nor may "Exolab" appear in their names without prior written * permission of Intalio, Inc. Exolab is a registered * trademark of Intalio, Inc. * * 5. Due credit should be given to the Exolab Project * (http://www.exolab.org/). * * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * Copyright 1999 (C) Intalio, Inc. All Rights Reserved. * * $Id$ */ package org.exolab.castor.persist; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.castor.core.util.Messages; import org.castor.persist.TransactionContext; import org.exolab.castor.jdo.LockNotGrantedException; /** * Read/write locks and lock synchronization on an object. Each object * is required to have one <tt>ObjectLock</tt> which at any given time * may be unlocked, write locked by one transaction, or read locked * by one or more transactions. * <p> * In order to obtain a lock, the transaction must call one of the * acquire, passing itself, the lock type and the lock timeout. The * transaction must attempt to obtain only one lock at any given time * by synchronizing all calls to one of the <tt>acquire</tt>. If the transaction * has acquired a read lock it may attempt to re-acquire the read * lock. If the transaction attempts to acquire a write lock the lock * will be upgraded. * <p> * A read lock cannot be acquired while there is a write lock on the * object, and a write lock cannot be acquired while there is one or * more read locks. If a lock cannot be acquired, the transaction * will hold until the lock is available or timeout occurs. If timeout * occured (or a dead lock has been detected), {@link * LockNotGrantedException} is thrown. If the object has been delete * while waiting for the lock, {@link ObjectDeletedException} is * thrown. * <p> * When the lock is acquired, the locked object is returned. * <p> * The transaction must call {@link #release} when the lock is no * longer required, allowing other transactions to obtain a lock. The * transaction must release all references to the object prior to * calling {@link #release}. * <p> * If the object has been deleted, the transaction must call {@link * #delete} instead of {@link #release}. * * * @author <a href="arkin@intalio.com">Assaf Arkin</a> * @author <a href="yip@intalio.com">Thomas Yip</a> * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $ * @see TransactionContext */ public final class ObjectLock implements DepositBox { /** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta * Commons Logging</a> instance used for all logging. */ private static final Log LOG = LogFactory.getFactory().getInstance(ObjectLock.class); protected static final short ACTION_READ = 1; protected static final short ACTION_WRITE = 2; protected static final short ACTION_CREATE = 3; protected static final short ACTION_UPDATE = 4; private static final int[] LOCK = new int[0]; private static int _idcount = 0; private int _id; /** The object being locked. */ private Object[] _object; /** The object's OID. */ private OID _oid; /** Write lock on this object. Refers to the transaction that has * acquired the write lock. Read and write locks are mutually * exclusive. */ private TransactionContext _writeLock; /** Read locks on this object. A LinkedTx list of all transactions * that have acquired a read lock. Read and write locks are * mutually exclusive. */ private LinkedTx _readLock; /** List of all transactions waiting for a read lock. Attempts to * acquire read lock while object has write lock will be recorded * here. When write lock is released, all read locks will acquire. */ private LinkedTx _readWaiting; private int _waitCount; /** List of all transactions waiting for a write lock (including * waiting for upgrade from read lock). Attempts to acquire a * write lock while object has a read lock will be recorded here. * When read lock is released, the first write lock will acquire. */ private LinkedTx _writeWaiting; private TransactionContext _confirmWaiting; private short _confirmWaitingAction; /** Number of transactions which are interested to invoke method on this lock. * If the number is zero, and the lock isFree(), then it is safe dispose this lock. */ private int _gateCount; /** * The object's version. */ private long _version; private boolean _deleted; private boolean _invalidated; private boolean _isExpired; private Object[] _expiredObject; /** * Create a new lock for the specified object. Must not create two * locks for the same object. This will be the object returned from * a successful call to one of the <tt>acquire</tt>. * * @param oid The object to create a lock for */ public ObjectLock(final OID oid) { _oid = oid; // give each instance of ObjectLock an id, for debug only synchronized (LOCK) { _id = _idcount; _idcount++; } } public ObjectLock(final OID oid, final Object[] object, final long version) { this(oid); _object = object; _version = version; } /** * Return the object's OID. */ public OID getOID() { return _oid; } /** * Set OID of this lock to new value. * */ void setOID(final OID oid) { _oid = oid; } /** * Indicate that a transaction is interested in this lock. * A transaction should call this method if it is going to * change the state of this lock (by calling acquire, update * or relase.) It method should be synchronized externally * to avoid race condition. enter and leave should be called * exactly the same number of time. */ void enter() { _gateCount++; } /** * Indicate that a transaction is not interested to change the * state of this lock anymore. (ie, will not call either acquire * update, release or delete.) * It method should be synchronized externally. */ void leave() { _gateCount--; } /** * Return true if there is any transaction called {@link #enter}, * but not yet called {@link #leave}. */ boolean isEntered() { return _gateCount != 0; } /** * Return true if this object can be safely disposed. An ObjectLock * can be safely disposed if and only if the no transaction is * holding any lock, nor any transaction isEntered. */ boolean isDisposable() { return _gateCount == 0 && isFree() && _waitCount == 0; } /** * Returns true if the transaction holds a read or write lock on * the object. This method is an efficient mean to determine whether * a lock is required, or if the object is owned by the transaction. * * @param tx The transaction * @param write True if must have a write lock * @return True if the transaction has a lock on this object */ boolean hasLock(final TransactionContext tx, final boolean write) { LinkedTx read; if (_writeLock == tx) { return true; } if (_confirmWaiting == tx) { if ((_confirmWaitingAction == ACTION_WRITE) || (_confirmWaitingAction == ACTION_CREATE)) { return true; } else if (!write && _confirmWaitingAction == ACTION_READ) { return true; } return false; } if (write) { return false; } read = _readLock; while (read != null) { if (read._tx == tx) { return true; } read = read._next; } return false; } /** * Return true if and only if this lock can be safely disposed. * * @return True if no lock and no waiting. */ boolean isFree() { return ((_writeLock == null) && (_readLock == null) && (_writeWaiting == null) && (_readWaiting == null) && (_confirmWaiting == null) && (_waitCount == 0)); } boolean isExclusivelyOwned(final TransactionContext tx) { if ((_writeLock == null) && (_readLock == null)) { return false; } if ((_writeLock == null) && (_readLock._tx == tx) && (_readLock._next._tx == null)) { return true; } if ((_writeLock == tx) && (_readLock == null)) { return true; } return false; } /** * Return true if this entry has been expired from the cache. */ boolean isExpired() { return _isExpired; } public Object[] getObject() { if ((_expiredObject != null) && (_object == null)) { return _expiredObject; } return _object; } /** * Indicate that object needs to be expired from the cache. */ public void expire() { _isExpired = true; } /** * Indicate that object has been removed from the cache. Perform any * post expiration cleanup. In particular, remove the reference to any * saved cached objects. */ public void expired() { _isExpired = false; _expiredObject = null; } synchronized void acquireLoadLock(final TransactionContext tx, final boolean write, final int timeout) throws LockNotGrantedException { int internalTimeout = timeout; long endtime = (internalTimeout > 0) ? System.currentTimeMillis() + internalTimeout * 1000 : Long.MAX_VALUE; while (true) { try { // cases to consider: // 3/ waitingForConfirmation exist // then, we wait // 4/ need a read, and objectLock has something // then, we return and wait for confirmation // 5/ need a read, and objectLock has nothing // then, we return and wait for confirmation // 6/ need a write // then, we return and wait for confirmation // 7/ we're in some kind of lock, or waiting, exception // 1/ write exist // then, put it tx into read/write waiting // 2/ read exist // then, put it read, or write waiting if (_deleted) { throw new ObjectDeletedWaitingForLockException("Object deleted"); } else if (_confirmWaiting != null) { // other thread is loading or creating object and haven't finished try { _waitCount++; wait(); } catch (InterruptedException e) { throw new LockNotGrantedException("Thread interrupted acquiring lock!", e); } finally { _waitCount--; } } else if (_writeLock == tx) { //throw new IllegalStateException( // "Transaction: " + tx + " has already hold the write lock on " // + _oid + " Acquire shouldn't be called twice"); return; } else if ((_readLock == null) && (_writeLock == null) && write) { // no transaction hold any lock, _confirmWaiting = tx; _confirmWaitingAction = ACTION_WRITE; return; } else if ((_readLock == null) && (_writeLock == null) && !write) { // no transaction hold any lock, if (_object == null) { _confirmWaiting = tx; _confirmWaitingAction = ACTION_READ; return; } _readLock = new LinkedTx(tx, null); return; } else if ((_readLock != null) && !write) { // already a transaction holding read lock, can acquire read lock LinkedTx linked = _readLock; while (linked != null) { if (linked._tx == tx) { throw new IllegalStateException("Transaction: " + tx + " has already hold the write lock on " + _oid + " Acquire shouldn't be called twice"); } linked = linked._next; } // if not already in readLock _readLock = new LinkedTx(tx, _readLock); return; } else { // other transaction holding writeLock, waits for write // or, other transaction holding readLock, waiting for read if (internalTimeout == 0) { if (LOG.isDebugEnabled()) { LOG.debug("Timeout on " + this.toString() + " by " + tx); } throw new LockNotGrantedException( (write ? "persist.writeLockTimeout" : "persist.readLockTimeout") + _oid + "/" + _id + " by " + tx); } if (LOG.isDebugEnabled()) { LOG.debug("Waiting on " + this.toString() + " by " + tx); } // Detect possibility of dead-lock. Must remain in wait-on-lock // position until lock is granted or exception thrown. tx.setWaitOnLock(this); detectDeadlock(tx, 10); // Must wait for lock and then attempt to reacquire if (write) { _writeWaiting = new LinkedTx(tx, _writeWaiting); } else { _readWaiting = new LinkedTx(tx, _readWaiting); } // Wait until notified or timeout elapses. Must detect // when notified but object deleted (i.e. locks released) // All waiting transactions are notified at once, but once // notified a race condition starts to acquire new lock try { long waittime = endtime - System.currentTimeMillis(); wait((waittime < 0) ? 0 : waittime); } catch (InterruptedException except) { // If the thread is interrupted, come out with the proper message throw new LockNotGrantedException( (write ? "persist.writeLockTimeout" : "persist.readLockTimeout") + _oid + "/" + _id + " by " + tx, except); } if (_deleted) { // If object has been deleted while waiting for lock, report deletion. throw new ObjectDeletedWaitingForLockException( "object deleted" + _oid + "/" + _id + " by " + tx); } // Try to re-acquire lock, this time less timeout, // eventually timeout of zero will either succeed or fail // without blocking. if (System.currentTimeMillis() > endtime) { internalTimeout = 0; } removeWaiting(tx); tx.setWaitOnLock(null); } } finally { removeWaiting(tx); tx.setWaitOnLock(null); } } } synchronized void acquireCreateLock(final TransactionContext tx) throws LockNotGrantedException { while (true) { // cases to consider: // 1/ waitingForConfirmation exist // 2/ lock can't be granted, throw LockNotGrantedException // 3/ lock can be granted // then, we return and wait for confirmation if (_deleted || (_confirmWaiting != null)) { // other thread is loading or creating object and haven't finished try { _waitCount++; wait(); while (_deleted) { wait(); } } catch (InterruptedException e) { throw new LockNotGrantedException("Thread interrupted acquiring lock!", e); } finally { _waitCount--; } } else if ((_readLock != null) || (_writeLock != null)) { throw new LockNotGrantedException("Lock already exist!"); } else { _confirmWaiting = tx; _confirmWaitingAction = ACTION_CREATE; return; } } } // probaraly we just don't need update.... synchronized void acquireUpdateLock(final TransactionContext tx, final int timeout) throws LockNotGrantedException { int internalTimeout = timeout; long endtime = (internalTimeout > 0) ? System.currentTimeMillis() + internalTimeout * 1000 : Long.MAX_VALUE; while (true) { try { // case to consider: // 1/ waitingForConfirmation exist // 2/ lock can be granted, and _object is not empty // then, we return and wait for confirmation // 3/ lock can not granted, wait if (_deleted || (_confirmWaiting != null)) { try { _waitCount++; wait(); /* if ( _deleted ) { throw new ObjectDeletedWaitingForLockException("Object deleted!"); }*/ } catch (InterruptedException e) { throw new LockNotGrantedException("Thread interrupted acquiring lock!", e); } finally { _waitCount--; } } else if (_writeLock == tx) { return; } else if ((_writeLock == null) && (_readLock == null)) { // can get the lock now _confirmWaiting = tx; _confirmWaitingAction = ACTION_UPDATE; return; } else { if (internalTimeout == 0) { if (LOG.isDebugEnabled()) { LOG.debug("Timeout on " + this.toString() + " by " + tx); } throw new LockNotGrantedException(Messages.message( "persist.writeLockTimeout")); } if (LOG.isDebugEnabled()) { LOG.debug("Waiting on " + this.toString() + " by " + tx); } // Detect possibility of dead-lock. Must remain in wait-on-lock // position until lock is granted or exception thrown. tx.setWaitOnLock(this); detectDeadlock(tx, 10); // Must wait for lock and then attempt to reacquire _writeWaiting = new LinkedTx(tx, _writeWaiting); // Wait until notified or timeout elapses. Must detect // when notified but object deleted (i.e. locks released) // All waiting transactions are notified at once, but once // notified a race condition starts to acquire new lock try { long waittime = endtime - System.currentTimeMillis(); wait((waittime < 0) ? 0 : waittime); } catch (InterruptedException except) { // If the thread is interrupted, come out with the proper message throw new LockNotGrantedException(Messages.message( "persist.writeLockTimeout") + _oid + "/" + _id + " by " + tx, except); } if (_deleted) { // If object has been deleted while waiting for lock, report deletion. throw new ObjectDeletedWaitingForLockException( "Object deleted " + _oid + "/" + _id + " by " + tx); } // Try to re-acquire lock, this time less timeout, // eventually timeout of zero will either succeed or fail // without blocking. if (System.currentTimeMillis() > endtime) { internalTimeout = 0; } removeWaiting(tx); tx.setWaitOnLock(null); } } finally { removeWaiting(tx); tx.setWaitOnLock(null); } } } public synchronized void setObject(final TransactionContext tx, final Object[] object, final long version) { _isExpired = false; // initialize cache expiration flag to false _expiredObject = null; if ((_confirmWaiting != null) && (_confirmWaiting == tx)) { _version = version; _object = object; if (_confirmWaitingAction == ACTION_READ) { _readLock = new LinkedTx(tx, null); } else { _writeLock = tx; } _confirmWaiting = null; notifyAll(); } else if ((_writeLock != null) && (_writeLock == tx)) { _version = version; _object = object; } else { throw new IllegalArgumentException( "Transaction tx does not own this lock, " + toString() + "!"); } } public synchronized Object[] getObject(final TransactionContext tx) { if ((_confirmWaiting != null) && (_confirmWaiting == tx)) { return _object; } else if ((_writeLock != null) && (_writeLock == tx)) { return _object; } else { LinkedTx link = _readLock; while (link != null) { if (link._tx == tx) { return _object; } link = link._next; } throw new IllegalArgumentException("Transaction tx does not own this lock!"); } } public synchronized long getVersion() { return _version; } public synchronized void setVersion(long version) { this._version = version; } synchronized void confirm(final TransactionContext tx, final boolean succeed) { // cases to consider: // 1/ not in waitingForConfirmation // 2/ load_read, // downgrade the lock // 3/ else // move confirmation and // notify() if (_confirmWaiting == tx) { if (succeed) { if (_confirmWaitingAction == ACTION_READ) { if (_readLock == null) { _readLock = new LinkedTx(tx, null); } } else { _writeLock = tx; } } _confirmWaiting = null; notifyAll(); } else if (_confirmWaiting == null) { if (!succeed) { // remove it from readLock if (_writeLock != null) { // same as delete the lock _deleted = true; _object = null; _version = System.currentTimeMillis(); //_writeLock = null; notifyAll(); } else if (_readLock != null) { if (_readLock._tx == tx) { _readLock = _readLock._next; } else { LinkedTx link = _readLock; while (link != null) { if ((link._next != null) && (link._next._tx == tx)) { link._next = link._next._next; notifyAll(); return; } link = link._next; } } } } notifyAll(); } else { throw new IllegalStateException( "Confirm transaction does not match the locked transaction"); } } /** * Acquires a lock on the object on behalf of the specified * transaction. A write lock will be acquired only if there are no * read/write locks on the object; only one write lock may be in * effect. A read lock will be acquired only if there is no write * lock on the object; multiple read locks are allowed. If the * lock cannot be acquired immediately, the thread will block * until the lock is made available or the timeout has elapsed. * If the timeout has elapsed or a dead lock has been detected, * a {@link LockNotGrantedException} is thrown. If the object has * been deleted while waiting for a lock, a {@link * ObjectDeletedException} is thrown. To prevent dead locks, a * transaction must only call this method for any given object * from a single thread and must mark the lock it is trying to * acquire and return it from a call to {@link * TransactionContext#getWaitOnLock} if the call to this method * has not returned yet. If a read lock is available for the * transaction and a write lock is requested, the read lock is * cancelled whether or not the write is acquired. * * @param tx The transaction requesting the lock * @param timeout Timeout waiting to acquire lock (in milliseconds), * zero for no waiting * @throws LockNotGrantedException Lock could not be granted in * the specified timeout or a dead lock has been detected * @throws ObjectDeletedWaitingForLockException The object has * been deleted while waiting for the lock */ synchronized void upgrade(final TransactionContext tx, final int timeout) throws LockNotGrantedException { int internalTimeout = timeout; // Note: This method must succeed even if an exception is thrown // in the middle. An exception may be thrown by a Thread.stop(). // Must make sure not to lose consistency. if (_confirmWaiting != null) { IllegalStateException e = new IllegalStateException( "Internal error: acquire when confirmWaiting is not null"); throw e; } if (!hasLock(tx, false)) { IllegalStateException e = new IllegalStateException( "Transaction didn't previously acquire this lock"); throw e; } long endtime = (internalTimeout > 0) ? System.currentTimeMillis() + internalTimeout * 1000 : Long.MAX_VALUE; while (true) { // Repeat forever until lock is acquired or timeout try { if (_writeLock == tx) { // Already have write lock, can acquire object return; } else if ((_writeLock == null) && (_readLock._tx == tx) && (_readLock._next == null)) { // Upgrading from read to write, no other locks, can upgrade // Order is important in case thread is stopped in the middle //_readLock = null; if (LOG.isDebugEnabled()) { LOG.debug("Acquired on " + this.toString() + " by " + tx); } _writeLock = tx; _readLock = null; return; } else { // Don't wait if timeout is zero if (internalTimeout == 0) { if (LOG.isDebugEnabled()) { LOG.debug("Timeout on " + this.toString() + " by " + tx); } throw new LockNotGrantedException( "persist.writeTimeout" + _oid + "/" + _id + " by " + tx); } if (LOG.isDebugEnabled()) { LOG.debug("Waiting on " + this.toString() + " by " + tx); } // Detect possibility of dead-lock. Must remain in wait-on-lock // position until lock is granted or exception thrown. tx.setWaitOnLock(this); detectDeadlock(tx, 10); // Must wait for lock and then attempt to reacquire _writeWaiting = new LinkedTx(tx, _writeWaiting); // Wait until notified or timeout elapses. Must detect // when notified but object deleted (i.e. locks released) // All waiting transactions are notified at once, but once // notified a race condition starts to acquire new lock try { long waittime = endtime - System.currentTimeMillis(); wait((waittime < 0) ? 0 : waittime); } catch (InterruptedException except) { // If the thread is interrupted, come out with the proper message throw new LockNotGrantedException("persist.writeLockTimeout", except); } if (_deleted) { // object should not be deleted, as we got lock on it throw new IllegalStateException( "internal error: object deleted" + _oid + "/" + _id + " by " + tx); } // Try to re-acquire lock, this time less timeout, // eventually timeout of zero will either succeed or fail // without blocking. if (System.currentTimeMillis() > endtime) { internalTimeout = 0; } removeWaiting(tx); tx.setWaitOnLock(null); } } finally { // Must always remove waiting transaction. removeWaiting(tx); tx.setWaitOnLock(null); } } } /** * Releases a lock on the object previously acquired. * A write lock cannot be downgraded into a read lock * and the transaction loses its lock on the object. Other * transactions are allowed to acquire a read/write lock on the * object. * * @param tx The transaction that holds the lock */ synchronized void release(final TransactionContext tx) { if (LOG.isDebugEnabled()) { LOG.debug("Release " + this.toString() + " by " + tx); } try { tx.setWaitOnLock(null); if (_writeLock == tx) { _writeLock = null; if (_invalidated || _deleted) { _version = System.currentTimeMillis(); // save a copy of the expired objects contents; // this will be used to expire all contained objects if (_isExpired) { _expiredObject = _object; } _object = null; } _deleted = false; _invalidated = false; } else if (_readLock != null) { if (_readLock._tx == tx) { _readLock = _readLock._next; } else { LinkedTx read = _readLock; while (read != null) { if (read._next != null && read._next._tx == tx) { read._next = read._next._next; break; } read = read._next; } if (read == null) { throw new IllegalStateException(Messages.message( "persist.notOwnerLock") + _oid + "/" + _id + " by " + tx); } } } else { throw new IllegalStateException(Messages.message( "persist.notOwnerLock") + _oid + "/" + _id + " by " + tx); } // Notify all waiting transactions that they may attempt to // acquire lock. First one to succeed wins (or multiple if // waiting for read lock). notifyAll(); } catch (ThreadDeath death) { // This operation must never fail, not even in the // event of a thread death release(tx); throw death; } } /** * Informs the lock that the object has been deleted by the * transaction holding the write lock. The lock on the object is * released and all transactions waiting for a lock will * terminate with an {@link ObjectDeletedException}. * * @param tx The transaction that holds the lock * @throws RuntimeException Attempt to delete object without * acquiring a write lock */ synchronized void delete(final TransactionContext tx) { if (tx != _writeLock) { throw new IllegalStateException(Messages.message( "persist.notOwnerLock") + " oid:" + _oid + "/" + _id + " by " + tx); } if (LOG.isDebugEnabled()) { LOG.debug ("Delete " + this.toString() + " by " + tx); } try { // Mark lock as unlocked and deleted, notify all waiting transactions _deleted = true; //_writeLock = null; _object = null; notifyAll(); } catch (ThreadDeath death) { // Delete operation must never fail, not even in the // event of a thread death release(tx); throw death; } } synchronized void invalidate(final TransactionContext tx) { if (tx != _writeLock) { throw new IllegalStateException(Messages.message( "persist.notOwnerLock") + " oid:" + _oid + "/" + _id + " by " + tx); } if (LOG.isDebugEnabled()) { LOG.debug ("Delete " + this.toString() + " by " + tx); } _invalidated = true; } /** * Detects a possible dead lock involving the transaction waiting * to acquire this lock. If the lock is locked (read or write) by * any transaction waiting for a lock on <tt>waitingTx</tt>, a * dead lock is detected and {@link LockNotGrantedException} * thrown. * * @param waitingTx The transaction waiting to acquire this lock */ private void detectDeadlock(final TransactionContext waitingTx, final int numOfRec) throws LockNotGrantedException { ObjectLock waitOn; if (numOfRec <= 0) { return; } // Inspect write lock and all read locks (the two are mutually exclusive). // For each lock look at all the waiting transactions( waitOn) and // determine whether they are currently waiting for a lock. A transaction // is waiting for a lock if it has called acquire() and has not // returned from the call. // If one of these locks is locked (read or write) by this transaction, // a dead lock has been detected. Recursion is necessary to prevent // indirect dead locks (A locked by B, B locked by C, C acquires lock on A) // Only the last lock attempt in a dead-lock situation will cancel. if (_writeLock != null) { // _writeLock is the blocking transaction. We are only interested in // a blocked transacrtion. waitOn = _writeLock.getWaitOnLock(); if (waitOn != null) { LinkedTx read; // Is the blocked transaction blocked by the transaction locking // this object? This is a deadlock. if (waitOn._writeLock == waitingTx) { throw new LockNotGrantedException(Messages.message("persist.deadlock")); } read = waitOn._readLock; while (read != null) { if (read._tx == waitingTx) { throw new LockNotGrantedException(Messages.message("persist.deadlock")); } read = read._next; } waitOn.detectDeadlock(waitingTx, numOfRec - 1); } } else { LinkedTx lock; lock = _readLock; while (lock != null) { // T1 trying to acquire lock on O1, which is locked by T2 // T2 trying to acauire lock on O1, T1 is waiting on O1 // lock is the blocking transaction. We are only interested in // a blocked transacrtion. waitOn = lock._tx.getWaitOnLock(); if ((waitOn != null) && (lock._tx != waitingTx)) { LinkedTx read; if (waitOn._writeLock == waitingTx) { throw new LockNotGrantedException(Messages.message("persist.deadlock")); } read = waitOn._readLock; while (read != null) { if (read._tx == waitingTx) { throw new LockNotGrantedException(Messages.message("persist.deadlock")); } read = read._next; } waitOn.detectDeadlock(waitingTx, numOfRec - 1); } lock = lock._next; } } } /** * Remove the transaction from the waiting list (both read and write). */ private void removeWaiting(final TransactionContext tx) { try { if (_writeWaiting != null) { if (_writeWaiting._tx == tx) { _writeWaiting = _writeWaiting._next; } else { LinkedTx wait; wait = _writeWaiting; while (wait._next != null) { if (wait._next._tx == tx) { wait._next = wait._next._next; break; } wait = wait._next; } } } if (_readWaiting != null) { if (_readWaiting._tx == tx) { _readWaiting = _readWaiting._next; } else { LinkedTx wait; wait = _readWaiting; while (wait._next != null) { if (wait._next._tx == tx) { wait._next = wait._next._next; break; } wait = wait._next; } } } if (_deleted && (_readWaiting == null) && (_writeWaiting == null) && (_confirmWaiting == null)) { _deleted = false; } } catch (ThreadDeath death) { // This operation must never fail, not even in the // event of a thread death removeWaiting(tx); throw death; } } public String toString() { return _oid.toString() + "/" + _id + " " + (((_readLock == null) ? "-" : "R") + "/" + ((_writeLock == null) ? "-" : "W")); } /** * Object uses to hold a linked list of transactions holding * write locks or waiting for a read/write lock. */ static class LinkedTx { private TransactionContext _tx; private LinkedTx _next; LinkedTx(final TransactionContext tx, final LinkedTx next) { _tx = tx; _next = next; } } }