[jboss-cvs] JBossCache/src/org/jboss/cache/lock ...
Brian Stansberry
brian.stansberry at jboss.com
Thu Jul 20 17:58:22 EDT 2006
User: bstansberry
Date: 06/07/20 17:58:22
Added: src/org/jboss/cache/lock LockUtil.java
Log:
[JBCACHE-465] Extract the state transfer code out of TreeCache
Revision Changes Path
1.1 date: 2006/07/20 21:58:22; author: bstansberry; state: Exp;JBossCache/src/org/jboss/cache/lock/LockUtil.java
Index: LockUtil.java
===================================================================
package org.jboss.cache.lock;
import java.util.Collection;
import java.util.Iterator;
import javax.transaction.Status;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.DataNode;
import org.jboss.cache.GlobalTransaction;
import org.jboss.cache.TransactionTable;
import org.jboss.cache.TreeCache;
import org.jboss.cache.statetransfer.StateTransferManager;
public abstract class LockUtil
{
private final static Log log = LogFactory.getLog(StateTransferManager.class);
private static interface TransactionLockStatus extends Status
{
public static final int STATUS_BROKEN = Integer.MIN_VALUE;
}
/**
*
*
* @param lock
* @param gtx
* @param cache TODO
* @param localAddress
* @return <code>true</code> if a lock was broken, <code>false</code> otherwise
*/
public static boolean breakTransactionLock(IdentityLock lock,
GlobalTransaction gtx,
boolean localTx,
TreeCache cache)
{
TransactionTable tx_table = cache.getTransactionTable();
TransactionManager tm = cache.getTransactionManager();
boolean broken = false;
int tryCount = 0;
int lastStatus = TransactionLockStatus.STATUS_BROKEN;
while (!broken && lock.isOwner(gtx))
{
int status = breakTransactionLock(gtx, lock, tx_table, tm, localTx, lastStatus, tryCount);
if (status == TransactionLockStatus.STATUS_BROKEN)
broken = true;
else if (status != lastStatus)
tryCount = 0;
lastStatus = status;
tryCount++;
}
return broken;
}
/**
* Forcibly acquire a read lock on the given node for the given owner,
* breaking any existing locks that prevent the read lock. If the
* existing lock is held by a GlobalTransaction, breaking the lock may
* result in a rollback of the transaction.
*
* @param node the node
* @param newOwner the new owner (usually a Thread or GlobalTransaction)
* @param cache TODO
* @param lockChildren <code>true</code> if this method should be recursively
* applied to <code>node</code>'s children.
*/
public static void forceAcquireLock(DataNode node,
Object newOwner,
TreeCache cache,
boolean lockChildren)
{
IdentityLock lock = node.getLock();
boolean acquired = lock.isOwner(newOwner);
if (!acquired && log.isDebugEnabled())
log.debug("Force acquiring lock on node " + node.getFqn());
TransactionTable tx_table = cache.getTransactionTable();
TransactionManager tm = cache.getTransactionManager();
Object localAddress = cache.getLocalAddress();
boolean serializable = cache.getConfiguration().getIsolationLevel() == IsolationLevel.SERIALIZABLE;
while (!acquired)
{
Object curOwner = null;
boolean attempted = false;
// Keep breaking write locks until we acquire a read lock
// or there are no more write locks
while (!acquired && ((curOwner = lock.getWriterOwner()) != null))
{
acquired = acquireLockFromOwner(node, lock, curOwner, newOwner, tx_table, tm, localAddress);
attempted = true;
}
// If no more write locks, but we haven't acquired, see if we
// need to break read locks as well.
if (!acquired && serializable)
{
Iterator it = lock.getReaderOwners().iterator();
if (it.hasNext())
{
curOwner = it.next();
acquired = acquireLockFromOwner(node, lock, curOwner, newOwner, tx_table, tm, localAddress);
attempted = true;
// Don't keep iterating due to the risk of
// ConcurrentModificationException if readers are removed
// Just go back through our outer loop to get the next one
}
}
if (!acquired && !attempted)
{
// We only try to acquire above if someone else has the lock.
// Seems no one is holding a lock and it's there for the taking.
try
{
acquired = node.acquire(newOwner, 1, DataNode.LOCK_TYPE_READ);
}
catch (Exception ignored)
{
}
}
}
// Recursively unlock children
if (lockChildren && node.hasChildren())
{
Collection children = node.getChildren().values();
for (Iterator it = children.iterator(); it.hasNext();)
{
forceAcquireLock((DataNode) it.next(), newOwner, cache, true);
}
}
}
/**
* Attempts to acquire a read lock on <code>node</code> for
* <code>newOwner</code>, if necessary breaking locks held by
* <code>curOwner</code>.
*
* @param node the node
* @param lock the lock
* @param curOwner the current owner
* @param newOwner the new owner
*/
private static boolean acquireLockFromOwner(DataNode node,
IdentityLock lock,
Object curOwner,
Object newOwner,
TransactionTable tx_table,
TransactionManager tm,
Object localAddress)
{
if (log.isTraceEnabled())
log.trace("Attempting to acquire lock for node " + node.getFqn() +
" from owner " + curOwner);
boolean acquired = false;
boolean broken = false;
int tryCount = 0;
int lastStatus = TransactionLockStatus.STATUS_BROKEN;
while (!broken && !acquired)
{
if (curOwner instanceof GlobalTransaction)
{
GlobalTransaction gtx = (GlobalTransaction) curOwner;
boolean local = gtx.getAddress().equals(localAddress);
int status = breakTransactionLock(gtx, lock, tx_table, tm, local, lastStatus, tryCount);
if (status == TransactionLockStatus.STATUS_BROKEN)
broken = true;
else if (status != lastStatus)
tryCount = 0;
lastStatus = status;
}
else if (tryCount > 0)
{
lock.release(curOwner);
broken = true;
}
if (broken && log.isTraceEnabled())
log.trace("Broke lock for node " + node.getFqn() +
" held by owner " + curOwner);
try
{
acquired = node.acquire(newOwner, 1, DataNode.LOCK_TYPE_READ);
}
catch (Exception ignore)
{
}
tryCount++;
}
return acquired;
}
/**
* Attempts to release the lock held by <code>gtx</code> by altering the
* underlying transaction. Different strategies will be employed
* depending on the status of the transaction and param
* <code>tryCount</code>. Transaction may be rolled back or marked
* rollback-only, or the lock may just be broken, ignoring the tx. Makes an
* effort to not affect the tx or break the lock if tx appears to be in
* the process of completion; param <code>tryCount</code> is used to help
* make decisions about this.
* <p/>
* This method doesn't guarantee to have broken the lock unless it returns
* {@link TransactionLockStatus#STATUS_BROKEN}.
*
* @param gtx the gtx holding the lock
* @param lock the lock
* @param lastStatus the return value from a previous invocation of this
* method for the same lock, or Status.STATUS_UNKNOW
* for the first invocation.
* @param tryCount number of times this method has been called with
* the same gtx, lock and lastStatus arguments. Should
* be reset to 0 anytime lastStatus changes.
* @return the current status of the Transaction associated with
* <code>gtx</code>, or {@link TransactionLockStatus#STATUS_BROKEN}
* if the lock held by gtx was forcibly broken.
*/
private static int breakTransactionLock(GlobalTransaction gtx,
IdentityLock lock,
TransactionTable tx_table,
TransactionManager tm,
boolean localTx,
int lastStatus,
int tryCount)
{
int status = Status.STATUS_UNKNOWN;
Transaction tx = tx_table.getLocalTransaction(gtx);
if (tx != null)
{
try
{
status = tx.getStatus();
if (status != lastStatus)
tryCount = 0;
switch (status)
{
case Status.STATUS_ACTIVE:
case Status.STATUS_MARKED_ROLLBACK:
case Status.STATUS_PREPARING:
case Status.STATUS_UNKNOWN:
if (tryCount == 0)
{
if (log.isTraceEnabled())
log.trace("Attempting to break transaction lock held " +
" by " + gtx + " by rolling back local tx");
// This thread has to join the tx
tm.resume(tx);
try
{
tx.rollback();
}
finally
{
tm.suspend();
}
}
else if (tryCount > 100)
{
// Something is wrong; our initial rollback call
// didn't generate a valid state change; just force it
lock.release(gtx);
status = TransactionLockStatus.STATUS_BROKEN;
}
break;
case Status.STATUS_COMMITTING:
case Status.STATUS_ROLLING_BACK:
// We'll try up to 10 times before just releasing
if (tryCount < 10)
break; // let it finish
// fall through and release
case Status.STATUS_COMMITTED:
case Status.STATUS_ROLLEDBACK:
case Status.STATUS_NO_TRANSACTION:
lock.release(gtx);
status = TransactionLockStatus.STATUS_BROKEN;
break;
case Status.STATUS_PREPARED:
// If the tx was started here, we can still abort the commit,
// otherwise we are in the middle of a remote commit() call
// and the status is just about to change
if (tryCount == 0 && localTx)
{
// We can still abort the commit
if (log.isTraceEnabled())
log.trace("Attempting to break transaction lock held " +
"by " + gtx + " by marking local tx as " +
"rollback-only");
tx.setRollbackOnly();
break;
}
else if (tryCount < 10)
{
// EITHER tx was started elsewhere (in which case we'll
// wait a bit to allow the commit() call to finish;
// same as STATUS_COMMITTING above)
// OR we marked the tx rollbackOnly above and are just
// waiting a bit for the status to change
break;
}
// fall through and release
default:
lock.release(gtx);
status = TransactionLockStatus.STATUS_BROKEN;
}
}
catch (Exception e)
{
log.error("Exception breaking locks held by " + gtx, e);
lock.release(gtx);
status = TransactionLockStatus.STATUS_BROKEN;
}
}
else
{
// Race condition; gtx was cleared from tx_table.
// Just double check if gtx still holds a lock
if (gtx == lock.getWriterOwner()
|| lock.getReaderOwners().contains(gtx))
{
// TODO should we throw an exception??
lock.release(gtx);
status = TransactionLockStatus.STATUS_BROKEN;
}
}
return status;
}
}
More information about the jboss-cvs-commits
mailing list