/**
* 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: ObjectLock.java,v 1.4 2004/09/03 03:34:27 snyder Exp $
*/
package org.exolab.castor.persist;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.exolab.castor.jdo.LockNotGrantedException;
import org.exolab.castor.jdo.ObjectDeletedException;
import org.exolab.castor.util.Messages;
/**
* 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: 1.4 $ $Date: 2004/09/03 03:34:27 $
* @see TransactionContext
*/
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 Log log = LogFactory.getFactory().getInstance(ObjectLock.class );
final static short ACTION_READ = 1;
final static short ACTION_WRITE = 2;
final static short ACTION_CREATE = 3;
final static short ACTION_UPDATE = 4;
static int idcount = 0;
final static int[] lock = new int[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;
private long _timeStamp;
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
*/
ObjectLock( OID oid ) {
_oid = oid;
// give each instance of ObjectLock an id, for debug only
synchronized ( lock ) {
_id = idcount;
idcount++;
}
}
/**
* Return the object's OID.
*/
OID getOID() {
return _oid;
}
/**
* Set OID of this lock to new value.
*
*/
void setOID( 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( TransactionContext tx, 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( 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;
else
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( TransactionContext tx, boolean write, int timeout )
throws LockNotGrantedException, ObjectDeletedWaitingForLockException {
long endtime = timeout>0? System.currentTimeMillis() + timeout*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!");
} 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;
} else {
_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");
//return;
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 ( timeout == 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 );
}
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 )
timeout = 0;
removeWaiting( tx );
tx.setWaitOnLock( null );
}
} finally {
removeWaiting( tx );
tx.setWaitOnLock( null );
}
}
}
synchronized void acquireCreateLock( 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!");
} 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( TransactionContext tx, int timeout )
throws LockNotGrantedException, ObjectDeletedException,
ObjectDeletedWaitingForLockException {
long endtime = timeout>0? System.currentTimeMillis() + timeout*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!");
} 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 ( timeout == 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
long clock = System.currentTimeMillis();
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 );
}
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 )
timeout = 0;
removeWaiting( tx );
tx.setWaitOnLock( null );
}
} finally {
removeWaiting( tx );
tx.setWaitOnLock( null );
}
}
}
public synchronized void setObject( TransactionContext tx, Object object ) {
_isExpired = false; // initialize cache expiration flag to false
_expiredObject = null;
if ( _confirmWaiting != null && _confirmWaiting == tx ) {
_timeStamp = System.currentTimeMillis();
_object = object;
if ( _confirmWaitingAction == ACTION_READ ) {
_readLock = new LinkedTx( tx, null );
} else {
_writeLock = tx;
}
_confirmWaiting = null;
notifyAll();
} else if ( _writeLock != null && _writeLock == tx ) {
_timeStamp = System.currentTimeMillis();
_object = object;
} else
throw new IllegalArgumentException("Transaction tx does not own this lock, "+toString()+"!");
}
public synchronized Object getObject( 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 getTimeStamp() {
return _timeStamp;
}
synchronized void confirm( TransactionContext tx, 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;
_timeStamp = System.currentTimeMillis();
//_writeLock = null;
notifyAll();
} else if ( _readLock == null ) {
} else 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( TransactionContext tx, int timeout )
throws LockNotGrantedException, ObjectDeletedWaitingForLockException {
// 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 = timeout>0? System.currentTimeMillis() + timeout*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 ( timeout == 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
long clock = System.currentTimeMillis();
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" );
}
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 )
timeout = 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( TransactionContext tx ) {
if (log.isDebugEnabled()) {
log.debug ( "Release " + this.toString() + " by " + tx );
}
try {
tx.setWaitOnLock( null );
if ( _writeLock == tx ) {
_writeLock = null;
if ( _invalidated || _deleted ) {
_timeStamp = 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( 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( 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( TransactionContext waitingTx, 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( 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
{
TransactionContext tx;
LinkedTx next;
LinkedTx( TransactionContext tx, LinkedTx next )
{
this.tx = tx;
this.next = next;
}
}
}