From jbosscache-commits at lists.jboss.org Wed Jun 25 09:21:39 2008 Content-Type: multipart/mixed; boundary="===============8174534553060917324==" MIME-Version: 1.0 From: jbosscache-commits at lists.jboss.org To: jbosscache-commits at lists.jboss.org Subject: [jbosscache-commits] JBoss Cache SVN: r6027 - core/trunk/src/main/java/org/jboss/cache/lock. Date: Wed, 25 Jun 2008 09:21:39 -0400 Message-ID: --===============8174534553060917324== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Author: manik.surtani(a)jboss.com Date: 2008-06-25 09:21:38 -0400 (Wed, 25 Jun 2008) New Revision: 6027 Added: core/trunk/src/main/java/org/jboss/cache/lock/MVCCLockManager.java Log: First-cut implementation of the mvcc lock manager Added: core/trunk/src/main/java/org/jboss/cache/lock/MVCCLockManager.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- core/trunk/src/main/java/org/jboss/cache/lock/MVCCLockManager.java = (rev 0) +++ core/trunk/src/main/java/org/jboss/cache/lock/MVCCLockManager.java 2008= -06-25 13:21:38 UTC (rev 6027) @@ -0,0 +1,378 @@ +package org.jboss.cache.lock; + +import net.jcip.annotations.ThreadSafe; +import org.jboss.cache.CacheSPI; +import org.jboss.cache.DataContainer; +import org.jboss.cache.Fqn; +import org.jboss.cache.InvocationContext; +import org.jboss.cache.Node; +import org.jboss.cache.NodeSPI; +import org.jboss.cache.factories.annotations.Inject; +import org.jboss.cache.factories.annotations.Start; +import org.jboss.cache.invocation.InvocationContextContainer; +import org.jboss.cache.util.concurrent.locks.OwnableReentrantLock; + +import javax.transaction.TransactionManager; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * This lock manager acquires and releases locks based on the Fqn passed i= n and not on the node itself. The main benefit + * here is that locks can be acquired and held even for nonexistent nodes. + *

+ * Uses lock striping so that a fixed number of locks are maintained for t= he entire cache, and not a single lock per node. + *

+ * This implementation only acquires exclusive WRITE locks, and throws an = exception if you attempt to use it to acquire a + * READ lock. JBoss Cache's MVCC design doesn't use read locks at all. + *

+ * The concept of lock owners is implicit in this implementation and any o= wners passed in as parameters (where required) + * will be ignored. See {@link org.jboss.cache.util.concurrent.locks.Owna= bleReentrantLock} for details on how ownership + * is determined. + *

+ * + * @author Manik Surtani (manik(a)jbo= ss.org) + * @see org.jboss.cache.util.concurrent.locks.OwnableReentrantLock + * @since 3.0 + */ +public class MVCCLockManager extends FqnLockManager +{ + LockContainer lockContainer; + DataContainer dataContainer; + private Set internalFqns; + private CacheSPI cache; + private TransactionManager transactionManager; + private InvocationContextContainer invocationContextContainer; + private static final int DEFAULT_CONCURRENCY =3D 20; + + @Inject + public void injectDataContainer(DataContainer dataContainer, CacheSPI c= ache, TransactionManager transactionManager, InvocationContextContainer inv= ocationContextContainer) + { + this.dataContainer =3D dataContainer; + this.cache =3D cache; + this.transactionManager =3D transactionManager; + this.invocationContextContainer =3D invocationContextContainer; + } + + @Start + public void startLockManager() + { + lockContainer =3D transactionManager =3D=3D null ? new ReentrantLock= Container() : new OwnableReentrantLockContainer(); + } + + @Start + @SuppressWarnings("unchecked") + public void setInternalFqns() + { + internalFqns =3D cache.getInternalFqns(); + } + + + protected void assertIsWriteLock(LockType lockType) + { + if (lockType !=3D LockType.WRITE) + throw new UnsupportedOperationException(getClass().getName() + " = only supports write locks."); + } + + public boolean lock(Fqn fqn, LockType lockType, Object owner) throws In= terruptedException + { + assertIsWriteLock(lockType); + Lock lock =3D lockContainer.getLock(fqn); + return lock.tryLock(lockAcquisitionTimeout, MILLISECONDS); + } + + public boolean lock(Fqn fqn, LockType lockType, Object owner, long time= outMillis) throws InterruptedException + { + assertIsWriteLock(lockType); + Lock lock =3D lockContainer.getLock(fqn); + return lock.tryLock(timeoutMillis, MILLISECONDS); + } + + public boolean lockAndRecord(Fqn fqn, LockType lockType, InvocationCont= ext ctx) throws InterruptedException + { + assertIsWriteLock(lockType); + Lock lock =3D lockContainer.getLock(fqn); + if (lock.tryLock(ctx.getContextLockAcquisitionTimeout(lockAcquisitio= nTimeout), MILLISECONDS)) + { + ctx.addLock(fqn); + return true; + } + + // couldn't acquire lock! + return false; + } + + public void unlock(Fqn fqn, Object owner) + { + Lock lock =3D lockContainer.getLock(fqn); + lock.unlock(); + } + + public void unlock(InvocationContext ctx) + { + List locks =3D ctx.getLocks(); + if (!locks.isEmpty()) + { + // unlocking needs to be done in reverse order. + Fqn[] fqns =3D new Fqn[locks.size()]; + fqns =3D locks.toArray(fqns); + for (int i =3D fqns.length - 1; i > -1; i--) lockContainer.getLoc= k(fqns[i]).unlock(); + } + } + + @SuppressWarnings("unchecked") + protected boolean lockRecursively(Node node, long timeoutMillis, boolea= n excludeInternalFqns, InvocationContext ctx) throws InterruptedException + { + if (excludeInternalFqns && internalFqns.contains(node.getFqn())) + return true; // this will stop the recursion from proceeding down= this subtree. + + boolean locked =3D ctx =3D=3D null ? + lock(node.getFqn(), LockType.WRITE, null, timeoutMillis) : + lockAndRecord(node.getFqn(), LockType.WRITE, ctx); + + if (!locked) return false; + boolean needToUnlock =3D false; + + // need to recursively walk through the node's children and acquire = locks. This needs to happen using API methods + // since any cache loading will need to happen. + Set children =3D node.getChildren(); + try + { + if (children !=3D null) + { + for (Node child : children) + { + locked =3D lockRecursively(child, timeoutMillis, excludeInt= ernalFqns, ctx); + if (!locked) + { + needToUnlock =3D true; + break; + } + } + } + } + finally + { + if (needToUnlock) + { + for (Node child : children) + { + Fqn childFqn =3D child.getFqn(); + unlock(childFqn, null); + if (ctx !=3D null) ctx.removeLock(childFqn); + } + unlock(node.getFqn(), null); + if (ctx !=3D null) ctx.removeLock(node.getFqn()); + } + } + return locked; + } + + public boolean lockAll(NodeSPI node, LockType lockType, Object owner) t= hrows InterruptedException + { + assertIsWriteLock(lockType); + return lockRecursively(node, lockAcquisitionTimeout, false, null); + } + + public boolean lockAll(NodeSPI node, LockType lockType, Object owner, l= ong timeout) throws InterruptedException + { + assertIsWriteLock(lockType); + return lockRecursively(node, timeout, false, null); + } + + public boolean lockAll(NodeSPI node, LockType lockType, Object owner, l= ong timeout, boolean excludeInternalFqns) throws InterruptedException + { + assertIsWriteLock(lockType); + return lockRecursively(node, timeout, excludeInternalFqns, null); + } + + public boolean lockAllAndRecord(NodeSPI node, LockType lockType, Invoca= tionContext ctx) throws InterruptedException + { + assertIsWriteLock(lockType); + return lockRecursively(node, ctx.getContextLockAcquisitionTimeout(lo= ckAcquisitionTimeout), false, ctx); + } + + public boolean lockAllAndRecord(Fqn fqn, LockType lockType, InvocationC= ontext ctx) throws InterruptedException + { + return lockAllAndRecord(dataContainer.peek(fqn, false), lockType, ct= x); + } + + @SuppressWarnings("unchecked") + public void unlockAll(NodeSPI node, Object owner) + { + // depth first. + Set children =3D node.getChildren(); + if (children !=3D null) + { + for (Node child : children) unlockAll((NodeSPI) child, null); + } + unlock(node.getFqn(), null); + } + + public void unlockAll(NodeSPI node) + { + throw new UnsupportedOperationException("Not supported in this impl.= "); + } + + public boolean ownsLock(Fqn fqn, LockType lockType, Object owner) + { + assertIsWriteLock(lockType); + return lockContainer.ownsLock(fqn, owner); + } + + public boolean ownsLock(Fqn fqn, Object owner) + { + return lockContainer.ownsLock(fqn, owner); + } + + public boolean isLocked(NodeSPI n) + { + return lockContainer.isLocked(n.getFqn()); + } + + public boolean isLocked(NodeSPI n, LockType lockType) + { + assertIsWriteLock(lockType); + return lockContainer.isLocked(n.getFqn()); + } + + public Object getWriteOwner(Fqn f) + { + throw new UnsupportedOperationException("Not supported in this impl.= "); + } + + public Collection getReadOwners(Fqn f) + { + throw new UnsupportedOperationException("Not supported in this impl.= "); + } + + public String printLockInfo(NodeSPI node) + { + return "Not supported in this impl."; + } + + @ThreadSafe + abstract class LockContainer + { + private final int lockSegmentMask; + private final int lockSegmentShift; + + /** + * This constructor just calls {@link #LockContainer(int)} with a de= fault concurrency value of 20. + */ + LockContainer() + { + this(DEFAULT_CONCURRENCY); + } + + /** + * Creates a new LockContainer which uses a certain number of shared= locks across all elements that need to be locked. + * + * @param concurrency number of threads expected to use this class c= oncurrently. + */ + LockContainer(int concurrency) + { + int tempLockSegShift =3D 0; + int numLocks =3D 1; + while (numLocks < concurrency) + { + ++tempLockSegShift; + numLocks <<=3D 1; + } + lockSegmentShift =3D 32 - tempLockSegShift; + lockSegmentMask =3D numLocks - 1; + + initLocks(numLocks); + } + + int hashToIndex(Fqn fqn) + { + return (hash(fqn) >>> lockSegmentShift) & lockSegmentMask; + } + + /** + * Returns a hash code for non-null Object x. + * Uses the same hash code spreader as most other java.util hash tab= les, except that this uses the string representation + * of the object passed in. + * + * @param x the object serving as a key + * @return the hash code + */ + int hash(Object x) + { + int h =3D x.toString().hashCode(); + h +=3D ~(h << 9); + h ^=3D (h >>> 14); + h +=3D (h << 4); + h ^=3D (h >>> 10); + return h; + } + + abstract void initLocks(int numLocks); + + abstract boolean ownsLock(Fqn fqn, Object owner); + + abstract boolean isLocked(Fqn fqn); + + abstract Lock getLock(Fqn fqn); + } + + class ReentrantLockContainer extends LockContainer + { + ReentrantLock[] sharedLocks; + + void initLocks(int numLocks) + { + sharedLocks =3D new ReentrantLock[numLocks]; + for (int i =3D 0; i < numLocks; i++) sharedLocks[i] =3D new Reent= rantLock(); + } + + ReentrantLock getLock(Fqn fqn) + { + return sharedLocks[hashToIndex(fqn)]; + } + + boolean ownsLock(Fqn fqn, Object owner) + { + ReentrantLock lock =3D getLock(fqn); + return lock.isHeldByCurrentThread(); + } + + boolean isLocked(Fqn fqn) + { + ReentrantLock lock =3D getLock(fqn); + return lock.isLocked(); + } + } + + class OwnableReentrantLockContainer extends LockContainer + { + OwnableReentrantLock[] sharedLocks; + + void initLocks(int numLocks) + { + sharedLocks =3D new OwnableReentrantLock[numLocks]; + for (int i =3D 0; i < numLocks; i++) sharedLocks[i] =3D new Ownab= leReentrantLock(invocationContextContainer); + } + + OwnableReentrantLock getLock(Fqn fqn) + { + return sharedLocks[hashToIndex(fqn)]; + } + + boolean ownsLock(Fqn fqn, Object owner) + { + OwnableReentrantLock lock =3D getLock(fqn); + return owner.equals(lock.getOwner()); + } + + boolean isLocked(Fqn fqn) + { + OwnableReentrantLock lock =3D getLock(fqn); + return lock.isLocked(); + } + } +} --===============8174534553060917324==--