[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