Clover coverage report -
Coverage timestamp: Thu Jul 5 2007 20:02:32 EDT
file stats: LOC: 363   Methods: 4
NCLOC: 254   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
LockUtil.java 14.8% 25.9% 50% 23%
coverage coverage
 1    package org.jboss.cache.lock;
 2   
 3    import org.apache.commons.logging.Log;
 4    import org.apache.commons.logging.LogFactory;
 5    import org.jboss.cache.CacheImpl;
 6    import org.jboss.cache.Node;
 7    import org.jboss.cache.NodeSPI;
 8    import org.jboss.cache.statetransfer.StateTransferManager;
 9    import org.jboss.cache.transaction.GlobalTransaction;
 10    import org.jboss.cache.transaction.TransactionTable;
 11   
 12    import javax.transaction.Status;
 13    import javax.transaction.Transaction;
 14    import javax.transaction.TransactionManager;
 15    import java.util.Iterator;
 16   
 17    public abstract class LockUtil
 18    {
 19    private final static Log log = LogFactory.getLog(StateTransferManager.class);
 20   
 21    private static interface TransactionLockStatus extends Status
 22    {
 23    public static final int STATUS_BROKEN = Integer.MIN_VALUE;
 24    }
 25   
 26  1 public static boolean breakTransactionLock(NodeLock lock,
 27    GlobalTransaction gtx,
 28    boolean localTx,
 29    CacheImpl cache)
 30    {
 31  1 TransactionTable tx_table = cache.getTransactionTable();
 32  1 TransactionManager tm = cache.getTransactionManager();
 33   
 34  1 boolean broken = false;
 35  1 int tryCount = 0;
 36  1 int lastStatus = TransactionLockStatus.STATUS_BROKEN;
 37   
 38  1 while (!broken && lock.isOwner(gtx))
 39    {
 40  1 int status = breakTransactionLock(gtx, lock, tx_table, tm, localTx, lastStatus, tryCount);
 41  1 if (status == TransactionLockStatus.STATUS_BROKEN)
 42    {
 43  0 broken = true;
 44    }
 45  1 else if (status != lastStatus)
 46    {
 47  1 tryCount = 0;
 48    }
 49  1 lastStatus = status;
 50   
 51  1 tryCount++;
 52    }
 53   
 54  1 return broken;
 55    }
 56   
 57    /**
 58    * Forcibly acquire a read lock on the given node for the given owner,
 59    * breaking any existing locks that prevent the read lock. If the
 60    * existing lock is held by a GlobalTransaction, breaking the lock may
 61    * result in a rollback of the transaction.
 62    *
 63    * @param node the node
 64    * @param newOwner the new owner (usually a Thread or GlobalTransaction)
 65    * @param lockChildren <code>true</code> if this method should be recursively
 66    * applied to <code>node</code>'s children.
 67    */
 68  0 public static void forceAcquireLock(NodeSPI<?, ?> node,
 69    Object newOwner,
 70    CacheImpl cache,
 71    boolean lockChildren)
 72    {
 73   
 74  0 NodeLock lock = node.getLock();
 75  0 boolean acquired = lock.isOwner(newOwner);
 76   
 77  0 if (!acquired && log.isDebugEnabled())
 78    {
 79  0 log.debug("Force acquiring lock on node " + node.getFqn());
 80    }
 81   
 82  0 TransactionTable tx_table = cache.getTransactionTable();
 83  0 TransactionManager tm = cache.getTransactionManager();
 84  0 Object localAddress = cache.getLocalAddress();
 85  0 boolean serializable = cache.getConfiguration().getIsolationLevel() == IsolationLevel.SERIALIZABLE;
 86   
 87  0 while (!acquired)
 88    {
 89  0 Object curOwner = null;
 90  0 boolean attempted = false;
 91   
 92    // Keep breaking write locks until we acquire a read lock
 93    // or there are no more write locks
 94  0 while (!acquired && ((curOwner = lock.getWriterOwner()) != null))
 95    {
 96  0 acquired = acquireLockFromOwner(node, lock, curOwner, newOwner, tx_table, tm, localAddress);
 97  0 attempted = true;
 98    }
 99   
 100    // If no more write locks, but we haven't acquired, see if we
 101    // need to break read locks as well.
 102  0 if (!acquired && serializable)
 103    {
 104  0 Iterator it = lock.getReaderOwners().iterator();
 105  0 if (it.hasNext())
 106    {
 107  0 curOwner = it.next();
 108  0 acquired = acquireLockFromOwner(node, lock, curOwner, newOwner, tx_table, tm, localAddress);
 109  0 attempted = true;
 110    // Don't keep iterating due to the risk of
 111    // ConcurrentModificationException if readers are removed
 112    // Just go back through our outer loop to get the next one
 113    }
 114    }
 115   
 116  0 if (!acquired && !attempted)
 117    {
 118    // We only try to acquire above if someone else has the lock.
 119    // Seems no one is holding a lock and it's there for the taking.
 120  0 try
 121    {
 122  0 acquired = lock.acquire(newOwner, 1, NodeLock.LockType.READ);
 123    }
 124    catch (Exception ignored)
 125    {
 126    }
 127    }
 128    }
 129   
 130    // Recursively unlock children
 131  0 if (lockChildren)
 132    {
 133  0 for (NodeSPI n : node.getChildrenDirect())
 134    {
 135  0 forceAcquireLock(n, newOwner, cache, true);
 136    }
 137    }
 138    }
 139   
 140    /**
 141    * Attempts to acquire a read lock on <code>node</code> for
 142    * <code>newOwner</code>, if necessary breaking locks held by
 143    * <code>curOwner</code>.
 144    *
 145    * @param node the node
 146    * @param lock the lock
 147    * @param curOwner the current owner
 148    * @param newOwner the new owner
 149    */
 150  0 private static boolean acquireLockFromOwner(Node node,
 151    NodeLock lock,
 152    Object curOwner,
 153    Object newOwner,
 154    TransactionTable tx_table,
 155    TransactionManager tm,
 156    Object localAddress)
 157    {
 158  0 if (log.isTraceEnabled())
 159    {
 160  0 log.trace("Attempting to acquire lock for node " + node.getFqn() +
 161    " from owner " + curOwner);
 162    }
 163   
 164  0 boolean acquired = false;
 165  0 boolean broken = false;
 166  0 int tryCount = 0;
 167  0 int lastStatus = TransactionLockStatus.STATUS_BROKEN;
 168   
 169  0 while (!broken && !acquired)
 170    {
 171  0 if (curOwner instanceof GlobalTransaction)
 172    {
 173  0 GlobalTransaction gtx = (GlobalTransaction) curOwner;
 174  0 boolean local = gtx.getAddress().equals(localAddress);
 175  0 int status = breakTransactionLock(gtx, lock, tx_table, tm, local, lastStatus, tryCount);
 176  0 if (status == TransactionLockStatus.STATUS_BROKEN)
 177    {
 178  0 broken = true;
 179    }
 180  0 else if (status != lastStatus)
 181    {
 182  0 tryCount = 0;
 183    }
 184  0 lastStatus = status;
 185    }
 186  0 else if (tryCount > 0)
 187    {
 188  0 lock.release(curOwner);
 189  0 broken = true;
 190    }
 191   
 192  0 if (broken && log.isTraceEnabled())
 193    {
 194  0 log.trace("Broke lock for node " + node.getFqn() +
 195    " held by owner " + curOwner);
 196    }
 197   
 198  0 try
 199    {
 200  0 acquired = lock.acquire(newOwner, 1, NodeLock.LockType.READ);
 201    }
 202    catch (Exception ignore)
 203    {
 204    }
 205   
 206  0 tryCount++;
 207    }
 208   
 209  0 return acquired;
 210    }
 211   
 212    /**
 213    * Attempts to release the lock held by <code>gtx</code> by altering the
 214    * underlying transaction. Different strategies will be employed
 215    * depending on the status of the transaction and param
 216    * <code>tryCount</code>. Transaction may be rolled back or marked
 217    * rollback-only, or the lock may just be broken, ignoring the tx. Makes an
 218    * effort to not affect the tx or break the lock if tx appears to be in
 219    * the process of completion; param <code>tryCount</code> is used to help
 220    * make decisions about this.
 221    * <p/>
 222    * This method doesn't guarantee to have broken the lock unless it returns
 223    * {@link TransactionLockStatus#STATUS_BROKEN}.
 224    *
 225    * @param gtx the gtx holding the lock
 226    * @param lock the lock
 227    * @param lastStatus the return value from a previous invocation of this
 228    * method for the same lock, or Status.STATUS_UNKNOW
 229    * for the first invocation.
 230    * @param tryCount number of times this method has been called with
 231    * the same gtx, lock and lastStatus arguments. Should
 232    * be reset to 0 anytime lastStatus changes.
 233    * @return the current status of the Transaction associated with
 234    * <code>gtx</code>, or {@link TransactionLockStatus#STATUS_BROKEN}
 235    * if the lock held by gtx was forcibly broken.
 236    */
 237  1 private static int breakTransactionLock(GlobalTransaction gtx,
 238    NodeLock lock,
 239    TransactionTable tx_table,
 240    TransactionManager tm,
 241    boolean localTx,
 242    int lastStatus,
 243    int tryCount)
 244    {
 245  1 int status = Status.STATUS_UNKNOWN;
 246  1 Transaction tx = tx_table.getLocalTransaction(gtx);
 247  1 if (tx != null)
 248    {
 249  1 try
 250    {
 251  1 status = tx.getStatus();
 252   
 253  1 if (status != lastStatus)
 254    {
 255  1 tryCount = 0;
 256    }
 257   
 258  1 switch (status)
 259    {
 260  1 case Status.STATUS_ACTIVE:
 261  0 case Status.STATUS_MARKED_ROLLBACK:
 262  0 case Status.STATUS_PREPARING:
 263  0 case Status.STATUS_UNKNOWN:
 264  1 if (tryCount == 0)
 265    {
 266  1 if (log.isTraceEnabled())
 267    {
 268  0 log.trace("Attempting to break transaction lock held " +
 269    " by " + gtx + " by rolling back local tx");
 270    }
 271    // This thread has to join the tx
 272  1 tm.resume(tx);
 273  1 try
 274    {
 275  1 tx.rollback();
 276    }
 277    finally
 278    {
 279  1 tm.suspend();
 280    }
 281   
 282    }
 283  0 else if (tryCount > 100)
 284    {
 285    // Something is wrong; our initial rollback call
 286    // didn't generate a valid state change; just force it
 287  0 lock.release(gtx);
 288  0 status = TransactionLockStatus.STATUS_BROKEN;
 289    }
 290  1 break;
 291   
 292  0 case Status.STATUS_COMMITTING:
 293  0 case Status.STATUS_ROLLING_BACK:
 294    // We'll try up to 10 times before just releasing
 295  0 if (tryCount < 10)
 296    {
 297  0 break;// let it finish
 298    }
 299    // fall through and release
 300   
 301  0 case Status.STATUS_COMMITTED:
 302  0 case Status.STATUS_ROLLEDBACK:
 303  0 case Status.STATUS_NO_TRANSACTION:
 304  0 lock.release(gtx);
 305  0 status = TransactionLockStatus.STATUS_BROKEN;
 306  0 break;
 307   
 308  0 case Status.STATUS_PREPARED:
 309    // If the tx was started here, we can still abort the commit,
 310    // otherwise we are in the middle of a remote commit() call
 311    // and the status is just about to change
 312  0 if (tryCount == 0 && localTx)
 313    {
 314    // We can still abort the commit
 315  0 if (log.isTraceEnabled())
 316    {
 317  0 log.trace("Attempting to break transaction lock held " +
 318    "by " + gtx + " by marking local tx as " +
 319    "rollback-only");
 320    }
 321  0 tx.setRollbackOnly();
 322  0 break;
 323    }
 324  0 else if (tryCount < 10)
 325    {
 326    // EITHER tx was started elsewhere (in which case we'll
 327    // wait a bit to allow the commit() call to finish;
 328    // same as STATUS_COMMITTING above)
 329    // OR we marked the tx rollbackOnly above and are just
 330    // waiting a bit for the status to change
 331  0 break;
 332    }
 333   
 334    // fall through and release
 335  0 default:
 336  0 lock.release(gtx);
 337  0 status = TransactionLockStatus.STATUS_BROKEN;
 338    }
 339    }
 340    catch (Exception e)
 341    {
 342  0 log.error("Exception breaking locks held by " + gtx, e);
 343  0 lock.release(gtx);
 344  0 status = TransactionLockStatus.STATUS_BROKEN;
 345    }
 346    }
 347    else
 348    {
 349    // Race condition; gtx was cleared from tx_table.
 350    // Just double check if gtx still holds a lock
 351  0 if (gtx == lock.getWriterOwner()
 352    || lock.getReaderOwners().contains(gtx))
 353    {
 354    // TODO should we throw an exception??
 355  0 lock.release(gtx);
 356  0 status = TransactionLockStatus.STATUS_BROKEN;
 357    }
 358    }
 359   
 360  1 return status;
 361    }
 362   
 363    }