From jbosscache-commits at lists.jboss.org Mon Jul 7 19:39:39 2008 Content-Type: multipart/mixed; boundary="===============3262622121169124850==" MIME-Version: 1.0 From: jbosscache-commits at lists.jboss.org To: jbosscache-commits at lists.jboss.org Subject: [jbosscache-commits] JBoss Cache SVN: r6202 - in core/trunk/src/main/java/org/jboss/cache: interceptors and 1 other directories. Date: Mon, 07 Jul 2008 19:39:39 -0400 Message-ID: --===============3262622121169124850== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Author: manik.surtani(a)jboss.com Date: 2008-07-07 19:39:38 -0400 (Mon, 07 Jul 2008) New Revision: 6202 Added: core/trunk/src/main/java/org/jboss/cache/mvcc/MVCCNodeHelper.java Modified: core/trunk/src/main/java/org/jboss/cache/factories/EmptyConstructorFacto= ry.java core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLockingInterce= ptor.java Log: Abstracted MVCC locking and wrapping logic into separate helper Modified: core/trunk/src/main/java/org/jboss/cache/factories/EmptyConstruct= orFactory.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/factories/EmptyConstructorFact= ory.java 2008-07-07 23:13:35 UTC (rev 6201) +++ core/trunk/src/main/java/org/jboss/cache/factories/EmptyConstructorFact= ory.java 2008-07-07 23:39:38 UTC (rev 6202) @@ -10,6 +10,7 @@ import org.jboss.cache.lock.LockStrategyFactory; import org.jboss.cache.marshall.Marshaller; import org.jboss.cache.marshall.VersionAwareMarshaller; +import org.jboss.cache.mvcc.MVCCNodeHelper; import org.jboss.cache.notifications.Notifier; import org.jboss.cache.remoting.jgroups.ChannelMessageListener; import org.jboss.cache.transaction.TransactionTable; @@ -20,7 +21,7 @@ * @author Manik Surtani (manik(a)jbo= ss.org) * @since 2.1.0 */ -(a)DefaultFactoryFor(classes =3D {Notifier.class, +(a)DefaultFactoryFor(classes =3D {Notifier.class, MVCCNodeHelper.class, ChannelMessageListener.class, CacheLoaderManager.class, Marshaller.c= lass, InvocationContextContainer.class, CacheInvocationDelegate.class, TransactionTable.class, DataContainer= .class, LockStrategyFactory.class, BuddyFqnTransformer.class}) Modified: core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLocking= Interceptor.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/interceptors/MVCCLockingInterc= eptor.java 2008-07-07 23:13:35 UTC (rev 6201) +++ core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLockingInterc= eptor.java 2008-07-07 23:39:38 UTC (rev 6202) @@ -1,11 +1,8 @@ package org.jboss.cache.interceptors; = -import org.jboss.cache.CacheException; import org.jboss.cache.DataContainer; import org.jboss.cache.Fqn; -import org.jboss.cache.InternalNode; import org.jboss.cache.NodeFactory; -import org.jboss.cache.NodeSPI; import org.jboss.cache.commands.VisitableCommand; import org.jboss.cache.commands.read.ExistsCommand; import org.jboss.cache.commands.read.GetChildrenNamesCommand; @@ -30,17 +27,11 @@ import org.jboss.cache.factories.annotations.Start; import org.jboss.cache.interceptors.base.PrePostProcessingCommandIntercept= or; import org.jboss.cache.invocation.InvocationContext; -import org.jboss.cache.invocation.NodeInvocationDelegate; import org.jboss.cache.lock.LockManager; -import static org.jboss.cache.lock.LockType.WRITE; -import org.jboss.cache.lock.TimeoutException; -import org.jboss.cache.mvcc.NullMarkerNode; +import org.jboss.cache.mvcc.MVCCNodeHelper; import org.jboss.cache.mvcc.ReadCommittedNode; = -import java.util.Collections; -import java.util.LinkedList; import java.util.List; -import java.util.Map; = /** * Interceptor to implement MVCC functionality. @@ -55,35 +46,23 @@ LockManager lockManager; DataContainer dataContainer; NodeFactory nodeFactory; - private long defaultLockAcquisitionTimeout; - private boolean lockParentForChildInsertRemove; + MVCCNodeHelper helper; = @Inject - public void setDependencies(LockManager lockManager, DataContainer data= Container, NodeFactory nodeFactory) + public void setDependencies(LockManager lockManager, DataContainer data= Container, NodeFactory nodeFactory, MVCCNodeHelper helper) { this.lockManager =3D lockManager; this.dataContainer =3D dataContainer; this.nodeFactory =3D nodeFactory; + this.helper =3D helper; } = @Start public void start() { allowWriteSkew =3D configuration.isAllowWriteSkew(); - defaultLockAcquisitionTimeout =3D configuration.getLockAcquisitionTi= meout(); - lockParentForChildInsertRemove =3D configuration.isLockParentForChil= dInsertRemove(); } = - private boolean parentLockNeeded(NodeSPI parent) - { - return lockParentForChildInsertRemove || (parent !=3D null && parent= .isLockForChildInsertRemove()); - } - - private boolean parentLockNeeded(Fqn parent) - { - return parentLockNeeded(dataContainer.peek(parent, true, true)); - } - @Override protected boolean doBeforeCall(InvocationContext ctx, VisitableCommand = command) { @@ -97,131 +76,35 @@ @Override public Object handlePutDataMapCommand(InvocationContext ctx, PutDataMap= Command command) throws Throwable { - getWrappedNode(ctx, command.getFqn(), true, true, false); // get the= node and stick it in the context. + helper.wrapNodeForWriting(ctx, command.getFqn(), true, true, false, = false, false); // get the node and stick it in the context. return invokeNextInterceptor(ctx, command); } = @Override public Object handlePutKeyValueCommand(InvocationContext ctx, PutKeyVal= ueCommand command) throws Throwable { - getWrappedNode(ctx, command.getFqn(), true, true, false); // get the= node and stick it in the context. + helper.wrapNodeForWriting(ctx, command.getFqn(), true, true, false, = false, false); // get the node and stick it in the context. return invokeNextInterceptor(ctx, command); } = @Override public Object handlePutForExternalReadCommand(InvocationContext ctx, Pu= tForExternalReadCommand command) throws Throwable { - getWrappedNode(ctx, command.getFqn(), true, true, false); // get the= node and stick it in the context. + helper.wrapNodeForWriting(ctx, command.getFqn(), true, true, false, = false, false); // get the node and stick it in the context. return invokeNextInterceptor(ctx, command); } = @Override public Object handleRemoveNodeCommand(InvocationContext ctx, RemoveNode= Command command) throws Throwable { - addNodeAndParentForRemoval(ctx, command.getFqn(), true); + helper.wrapNodesForWriting(ctx, command.getFqn()); return invokeNextInterceptor(ctx, command); } = - private List addNodeAndParentForRemoval(InvocationContext ctx, Fqn= nodeFqn, boolean recursive) throws InterruptedException - { - // when removing a node we want to get a lock on the Fqn anyway and = return the wrapped node. - if (nodeFqn.isRoot()) throw new CacheException("Attempting to remove= Fqn.ROOT!"); - - Fqn parentFqn =3D nodeFqn.getParent(); - // inspect parent - boolean needToCopyParent =3D false; - boolean parentLockNeeded =3D parentLockNeeded(parentFqn); - if (parentLockNeeded) - { - needToCopyParent =3D lock(ctx, parentFqn); - // Ensure the node is in the context. - putNodeInContext(ctx, parentFqn, needToCopyParent); - } - - boolean needToCopyNode =3D lock(ctx, nodeFqn); - - // Ensure the node is in the context. - putNodeInContext(ctx, nodeFqn, needToCopyNode); - - ReadCommittedNode node =3D (ReadCommittedNode) ctx.lookUpNode(nodeFq= n); - - // update child ref on parent to point to child as this is now a cop= y. - if (node !=3D null && !(node instanceof NullMarkerNode)) - { - if (parentLockNeeded && (needToCopyNode || needToCopyParent)) - { - ReadCommittedNode parent =3D (ReadCommittedNode) ctx.lookUpNod= e(parentFqn); - parent.addChildDirect(nodeFactory.createNodeInvocationDelegate= ((InternalNode) node.getDelegationTarget())); - } - - // now deal with children. - if (recursive) - { - Map childMap =3D node.getChildrenMapDirect(); - List fqnsToBeRemoved =3D new LinkedList(); - fqnsToBeRemoved.add(nodeFqn); - if (childMap =3D=3D null || childMap.isEmpty()) return fqnsToB= eRemoved; - - for (Object n : childMap.values()) - { - NodeSPI child =3D (NodeSPI) n; - lockForRemoval(child.getFqn(), recursive, ctx, fqnsToBeRemo= ved); - } - - return fqnsToBeRemoved; - } - } - - return null; - } - - private void lockForRemoval(Fqn fqn, boolean isRecursive, InvocationCon= text ctx, List fqnList) throws InterruptedException - { - lock(ctx, fqn); // lock node - if (fqnList !=3D null) fqnList.add(fqn); - - // now wrap and add to the context - ReadCommittedNode rcn =3D (ReadCommittedNode) getWrappedNode(ctx, fq= n, true, false, true); - if (rcn !=3D null) - { - rcn.copyNodeForUpdate(dataContainer, allowWriteSkew, ctx, nodeFac= tory); - ReadCommittedNode parent =3D (ReadCommittedNode) ctx.lookUpNode(f= qn.getParent()); - parent.addChildDirect(nodeFactory.createNodeInvocationDelegate((I= nternalNode) rcn.getDelegationTarget())); - - if (isRecursive) - { - Map children =3D rcn.getChildrenMapDirect(); - if (children !=3D null) - { - for (NodeSPI child : children.values()) - { - lockForRemoval(child.getFqn(), isRecursive, ctx, fqnList= ); - } - } - } - } - } - - private void putNodeInContext(InvocationContext ctx, Fqn fqn, boolean n= eedToCopyNode) - { - ReadCommittedNode node =3D (ReadCommittedNode) ctx.lookUpNode(fqn); - if (node =3D=3D null) - { - InternalNode in =3D dataContainer.peekInternalNode(fqn, false); - node =3D nodeFactory.createMvccNode(in); - ctx.putLookedUpNode(fqn, node); - } - - if (needToCopyNode && node !=3D null && !node.isChanged()) // node c= ould be null if using read-committed - { - node.copyNodeForUpdate(dataContainer, allowWriteSkew, ctx, nodeFa= ctory); - } - } - @Override public Object handleClearDataCommand(InvocationContext ctx, ClearDataCo= mmand command) throws Throwable { - getWrappedNode(ctx, command.getFqn(), true, false, false); + helper.wrapNodeForWriting(ctx, command.getFqn(), true, false, false,= false, false); return invokeNextInterceptor(ctx, command); } = @@ -230,12 +113,15 @@ { // set lock acquisition timeout to 0 - we need to fail fast. ctx.getOptionOverrides().setLockAcquisitionTimeout(0); - List fqnsToEvict =3D addNodeAndParentForRemoval(ctx, command.ge= tFqn(), command.isRecursive()); - - if (fqnsToEvict !=3D null) // add this set to the command + if (command.isRecursive()) { + List fqnsToEvict =3D helper.wrapNodesForWriting(ctx, command= .getFqn()); command.setNodesToEvict(fqnsToEvict); } + else + { + helper.wrapNodeForWriting(ctx, command.getFqn(), true, false, fal= se, true, true); + } = return invokeNextInterceptor(ctx, command); } @@ -245,52 +131,57 @@ { // this should be handled the same as a recursive evict command. ctx.getOptionOverrides().setLockAcquisitionTimeout(0); - addNodeAndParentForRemoval(ctx, command.getFqn(), true); - + helper.wrapNodesForWriting(ctx, command.getFqn()); return invokeNextInterceptor(ctx, command); } = @Override public Object handleRemoveKeyCommand(InvocationContext ctx, RemoveKeyCo= mmand command) throws Throwable { - getWrappedNode(ctx, command.getFqn(), true, false, false); + helper.wrapNodeForWriting(ctx, command.getFqn(), true, false, false,= false, false); return invokeNextInterceptor(ctx, command); } = @Override public Object handleGetDataMapCommand(InvocationContext ctx, GetDataMap= Command command) throws Throwable { - return handleReadCommand(ctx, command, Collections.singletonList(com= mand.getFqn())); + helper.wrapNodeForReading(ctx, command.getFqn()); + return invokeNextInterceptor(ctx, command); } = @Override public Object handleExistsNodeCommand(InvocationContext ctx, ExistsComm= and command) throws Throwable { - return handleReadCommand(ctx, command, Collections.singletonList(com= mand.getFqn())); + helper.wrapNodeForReading(ctx, command.getFqn()); + return invokeNextInterceptor(ctx, command); } = @Override public Object handleGetKeyValueCommand(InvocationContext ctx, GetKeyVal= ueCommand command) throws Throwable { - return handleReadCommand(ctx, command, Collections.singletonList(com= mand.getFqn())); + helper.wrapNodeForReading(ctx, command.getFqn()); + return invokeNextInterceptor(ctx, command); } = @Override public Object handleGetNodeCommand(InvocationContext ctx, GetNodeComman= d command) throws Throwable { - return handleReadCommand(ctx, command, Collections.singletonList(com= mand.getFqn())); + helper.wrapNodeForReading(ctx, command.getFqn()); + return invokeNextInterceptor(ctx, command); } = @Override public Object handleGetKeysCommand(InvocationContext ctx, GetKeysComman= d command) throws Throwable { - return handleReadCommand(ctx, command, Collections.singletonList(com= mand.getFqn())); + helper.wrapNodeForReading(ctx, command.getFqn()); + return invokeNextInterceptor(ctx, command); } = @Override public Object handleGetChildrenNamesCommand(InvocationContext ctx, GetC= hildrenNamesCommand command) throws Throwable { - return handleReadCommand(ctx, command, Collections.singletonList(com= mand.getFqn())); + helper.wrapNodeForReading(ctx, command.getFqn()); + return invokeNextInterceptor(ctx, command); } = @Override @@ -298,13 +189,13 @@ { // nodes we need to get WLs for: // node we are moving FROM (and it's parent and children.) Same as = removeNode. - List nodeAndChildren =3D addNodeAndParentForRemoval(ctx, comman= d.getFqn(), true); + List nodeAndChildren =3D helper.wrapNodesForWriting(ctx, comman= d.getFqn()); = Fqn newParent =3D command.getTo(); Fqn oldParent =3D command.getFqn().getParent(); = // now lock the new parent. - getWrappedNode(ctx, newParent, true, true, false); + helper.wrapNodeForWriting(ctx, newParent, true, true, false, false, = false); = if (!oldParent.equals(newParent) && nodeAndChildren !=3D null) { @@ -313,7 +204,7 @@ for (Fqn f : nodeAndChildren) { Fqn newChildFqn =3D f.replaceAncestor(oldParent, newParent); - getWrappedNode(ctx, newChildFqn, true, true, true); + helper.wrapNodeForWriting(ctx, newChildFqn, true, true, true, = false, false); } } = @@ -324,13 +215,14 @@ @Override public Object handleGravitateDataCommand(InvocationContext ctx, Gravita= teDataCommand command) throws Throwable { - return handleReadCommand(ctx, command, Collections.singletonList(com= mand.getFqn())); + helper.wrapNodeForReading(ctx, command.getFqn()); + return invokeNextInterceptor(ctx, command); } = @Override public Object handleCreateNodeCommand(InvocationContext ctx, CreateNode= Command command) throws Throwable { - getWrappedNode(ctx, command.getFqn(), true, true, false); // get the= node and stick it in the context. + helper.wrapNodeForWriting(ctx, command.getFqn(), true, true, false, = false, false); // get the node and stick it in the context. return invokeNextInterceptor(ctx, command); } = @@ -439,148 +331,4 @@ throw new IllegalStateException("Attempting to do a commit or rol= lback but there is no transactional context in scope. " + ctx); } } - - // ----------------- actual implementation details --------------------= -------- - - protected Object handleReadCommand(InvocationContext ctx, VisitableComm= and command, List fqns) throws Throwable - { - boolean forceWriteLock =3D ctx.getOptionOverrides().isForceWriteLock= (); - - // does the node exist in the context? - for (Fqn f : fqns) - { - if (forceWriteLock) - { - if (trace) log.trace("Forcing lock on reading node " + f); - getWrappedNode(ctx, f, true, false, false); - } - else if (ctx.lookUpNode(f) =3D=3D null) - { - if (trace) log.trace("Node " + f + " is not in context, fetchi= ng from container."); - // simple implementation. Peek the node, wrap it, put wrapped= node in the context. - InternalNode node =3D dataContainer.peekInternalNode(f, false); - NodeSPI wrapped =3D nodeFactory.createMvccNode(node); - if (wrapped !=3D null) ctx.putLookedUpNode(f, wrapped); - } - else - { - if (trace) log.trace("Node " + f + " is already in context."); - } - } - return invokeNextInterceptor(ctx, command); - } - - /** - * First checks in contexts for the existence of the node. If it does = exist, it will return it, acquiring a lock if - * necessary. Otherwise, it will peek in the dataContainer, wrap the n= ode, lock if necessary, and add it to the context. - * If it doesn't even exist in the dataContainer and createIfAbsent is = true, it will create a new node and add it to the - * data structure. It will lock the node, and potentially the parent a= s well, if necessary. If the parent is locked, - * it too will be added to the context if it wasn't there already. - * - * @param fqn to retrieve - * @param lockForWriting if true, a lock will be acquired. - * @param createIfAbsent if true, will be created if absent. - * @param includeInvalidNodes - * @return a NodeSPI or null. - */ - protected NodeSPI getWrappedNode(InvocationContext context, Fqn fqn, bo= olean lockForWriting, boolean createIfAbsent, boolean includeInvalidNodes) = throws InterruptedException - { - ReadCommittedNode n =3D (ReadCommittedNode) context.lookUpNode(fqn); - if (createIfAbsent && n !=3D null && n.isNullNode()) n =3D null; - if (n !=3D null) - { - // acquire lock if needed - if (lockForWriting && lock(context, fqn)) - { - // create a copy of the underlying node - n.copyNodeForUpdate(dataContainer, allowWriteSkew, context, no= deFactory); - } - if (trace) log.trace("Retrieving wrapped node " + fqn); - if (n.isDeleted() && createIfAbsent) - { - if (trace) log.trace("Node is deleted in current scope. Need = to un-delete."); - n.markAsDeleted(false); - n.setValid(true, false); - } - return n; - } - - // else, fetch from dataContainer. - InternalNode in =3D dataContainer.peekInternalNode(fqn, includeInval= idNodes); - if (in !=3D null) - { - // do we need a lock? - boolean needToCopy =3D false; - if (lockForWriting && lock(context, fqn)) - { - needToCopy =3D true; - } - ReadCommittedNode wrapped =3D nodeFactory.createMvccNode(in); - context.putLookedUpNode(fqn, wrapped); - if (needToCopy) - wrapped.copyNodeForUpdate(dataContainer, allowWriteSkew, conte= xt, nodeFactory); - return wrapped; - } - - // else, do we need to create one? - if (createIfAbsent) - { - Fqn parentFqn =3D fqn.getParent(); - NodeSPI parent =3D getWrappedNode(context, parentFqn, false, crea= teIfAbsent, false); - // do we need to lock the parent to create children? - boolean parentLockNeeded =3D parentLockNeeded(parent); - // get a lock on the parent. - if (parentLockNeeded && lock(context, parentFqn)) - { - ReadCommittedNode parentRCN =3D (ReadCommittedNode) context.lo= okUpNode(parentFqn); - parentRCN.copyNodeForUpdate(dataContainer, allowWriteSkew, con= text, nodeFactory); - } - - // now to lock and create the node. - lock(context, fqn); - - NodeSPI temp =3D parent.getOrCreateChild(fqn.getLastElement(), co= ntext.getGlobalTransaction()); - // TODO: warning, hack! There is a race condition here. Add a w= ay to create nodes without attaching to a parent. - parent.removeChildDirect(fqn.getLastElement()); - - in =3D (InternalNode) ((NodeInvocationDelegate) temp).getDelegati= onTarget(); - ReadCommittedNode wrapped =3D nodeFactory.createMvccNode(in); - wrapped.setCreated(true); - context.putLookedUpNode(fqn, wrapped); - wrapped.copyNodeForUpdate(dataContainer, allowWriteSkew, context,= nodeFactory); -// if (parentLockNeeded) -// { -// // since we copied the child make sure we update the parent'= s ref -// parent.addChildDirect(nodeFactory.createNodeInvocationDelega= te(wrapped.getNode())); -// } - - return wrapped; - } - - return null; - } - - /** - * Attempts to lock a node if the lock isn't already held in the curren= t scope. - * - * @param ctx context - * @param fqn Fqn to lock - * @return true if a lock was needed and acquired, false if it didn't n= eed to acquire the lock (i.e., lock was already held) - * @throws InterruptedException - * @throws TimeoutException if we are unable to acquire the lock af= ter a specified timeout. - */ - protected boolean lock(InvocationContext ctx, Fqn fqn) throws Interrupt= edException - { - // don't EVER use lockManager.isLocked() since with lock striping it= may be the case that we hold the relevant - // lock which may be shared with another Fqn that we have a lock for= already. - // nothing wrong, just means that we fail to record the lock. And t= hat is a problem. - // Better to check our records and lock again if necessary. - if (!ctx.getLocks().contains(fqn)) - { - if (!lockManager.lockAndRecord(fqn, WRITE, ctx)) - throw new TimeoutException("Unable to acquire lock on Fqn [" += fqn + "] after [" + ctx.getLockAcquisitionTimeout(defaultLockAcquisitionTi= meout) + "] milliseconds!"); - return true; - } - return false; - } } Added: core/trunk/src/main/java/org/jboss/cache/mvcc/MVCCNodeHelper.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/mvcc/MVCCNodeHelper.java = (rev 0) +++ core/trunk/src/main/java/org/jboss/cache/mvcc/MVCCNodeHelper.java 2008-= 07-07 23:39:38 UTC (rev 6202) @@ -0,0 +1,345 @@ +package org.jboss.cache.mvcc; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jboss.cache.CacheException; +import org.jboss.cache.DataContainer; +import org.jboss.cache.Fqn; +import org.jboss.cache.InternalNode; +import org.jboss.cache.NodeFactory; +import org.jboss.cache.NodeSPI; +import org.jboss.cache.config.Configuration; +import org.jboss.cache.factories.annotations.Inject; +import org.jboss.cache.factories.annotations.NonVolatile; +import org.jboss.cache.factories.annotations.Start; +import org.jboss.cache.invocation.InvocationContext; +import org.jboss.cache.invocation.NodeInvocationDelegate; +import org.jboss.cache.lock.LockManager; +import static org.jboss.cache.lock.LockType.WRITE; +import org.jboss.cache.lock.TimeoutException; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Utility functions to manipulate wrapping {@link org.jboss.cache.Interna= lNode}s as {@link org.jboss.cache.mvcc.ReadCommittedNode} + * or {@link org.jboss.cache.mvcc.RepeatableReadNode}s. Would also entail= locking, if necessary. + * + * @author Manik Surtani (manik(a)jbo= ss.org) + * @since 3.0 + */ +(a)NonVolatile +public class MVCCNodeHelper +{ + DataContainer dataContainer; + NodeFactory nodeFactory; + private static final Log log =3D LogFactory.getLog(MVCCNodeHelper.class= ); + private static final boolean trace =3D log.isTraceEnabled(); + private long defaultLockAcquisitionTimeout; + private LockManager lockManager; + private Configuration configuration; + private boolean allowWriteSkew; + private boolean lockParentForChildInsertRemove; + + @Inject + public void injectDependencies(DataContainer dataContainer, NodeFactory= nodeFactory, LockManager lockManager, Configuration configuration) + { + this.nodeFactory =3D nodeFactory; + this.dataContainer =3D dataContainer; + this.configuration =3D configuration; + this.lockManager =3D lockManager; + } + + @Start + public void start() + { + defaultLockAcquisitionTimeout =3D configuration.getLockAcquisitionTi= meout(); + allowWriteSkew =3D configuration.isAllowWriteSkew(); + lockParentForChildInsertRemove =3D configuration.isLockParentForChil= dInsertRemove(); + } + + + /** + * Attempts to provide the context with a set of wrapped nodes based on= the Collection of fqns passed in. If the nodes + * already exist in the context then the node is not wrapped again. + *

+ * {@link InternalNode}s are wrapped using {@link NodeFactory#createMvc= cNode(org.jboss.cache.InternalNode)} and as such, + * null internal nodes are treated according to isolation level used. = See {@link NodeFactory#createMvccNode(org.jboss.cache.InternalNode)} + * for details on this behaviour. + *

+ * Note that if the context has the {@link org.jboss.cache.config.Optio= n#isForceWriteLock()} option set, then write locks are + * acquired and the node is copied. + *

+ * + * @param ctx current invocation context + * @param fqns collection of Fqns. Should not be null. + * @throws InterruptedException if write locks are forced and the lock = manager is interrupted. + */ + public void wrapNodesForReading(InvocationContext ctx, Collection = fqns) throws InterruptedException + { + boolean forceWriteLock =3D ctx.getOptionOverrides().isForceWriteLock= (); + + // does the node exist in the context? + for (Fqn f : fqns) wrapNodeForReading(ctx, f, forceWriteLock); + } + + /** + * Similar to {@link #wrapNodesForReading(org.jboss.cache.invocation.In= vocationContext, java.util.Collection)} except + * that this version takes a single Fqn parameter to wrap a single node. + * + * @param ctx current invocation context + * @param fqn fqn to fetch and wrap + * @throws InterruptedException if write locks are forced and the lock = manager is interrupted. + */ + public void wrapNodeForReading(InvocationContext ctx, Fqn fqn) throws I= nterruptedException + { + wrapNodeForReading(ctx, fqn, ctx.getOptionOverrides().isForceWriteLo= ck()); + } + + private void wrapNodeForReading(InvocationContext ctx, Fqn f, boolean w= riteLockForced) throws InterruptedException + { + if (writeLockForced) + { + if (trace) log.trace("Forcing lock on reading node " + f); + wrapNodeForWriting(ctx, f, true, false, false, false, false); + } + else if (ctx.lookUpNode(f) =3D=3D null) + { + if (trace) log.trace("Node " + f + " is not in context, fetching = from container."); + // simple implementation. Peek the node, wrap it, put wrapped no= de in the context. + InternalNode node =3D dataContainer.peekInternalNode(f, false); + ReadCommittedNode wrapped =3D nodeFactory.createMvccNode(node); + if (wrapped !=3D null) ctx.putLookedUpNode(f, wrapped); + } + else + { + if (trace) log.trace("Node " + f + " is already in context."); + } + } + + /** + * Attempts to lock a node if the lock isn't already held in the curren= t scope, and records the lock in the context. + * + * @param ctx context + * @param fqn Fqn to lock + * @return true if a lock was needed and acquired, false if it didn't n= eed to acquire the lock (i.e., lock was already held) + * @throws InterruptedException if interrupted + * @throws TimeoutException if we are unable to acquire the lock af= ter a specified timeout. + */ + private boolean acquireLock(InvocationContext ctx, Fqn fqn) throws Inte= rruptedException, TimeoutException + { + // don't EVER use lockManager.isLocked() since with lock striping it= may be the case that we hold the relevant + // lock which may be shared with another Fqn that we have a lock for= already. + // nothing wrong, just means that we fail to record the lock. And t= hat is a problem. + // Better to check our records and lock again if necessary. + if (!ctx.getLocks().contains(fqn)) + { + if (!lockManager.lockAndRecord(fqn, WRITE, ctx)) + throw new TimeoutException("Unable to acquire lock on Fqn [" += fqn + "] after [" + ctx.getLockAcquisitionTimeout(defaultLockAcquisitionTi= meout) + "] milliseconds!"); + return true; + } + return false; + } + + /** + * First checks in contexts for the existence of the node. If it does = exist, it will return it, acquiring a lock if + * necessary. Otherwise, it will peek in the dataContainer, wrap the n= ode, lock if necessary, and add it to the context. + * If it doesn't even exist in the dataContainer and createIfAbsent is = true, it will create a new node and add it to the + * data structure. It will lock the node, and potentially the parent a= s well, if necessary. If the parent is locked, + * it too will be added to the context if it wasn't there already. + * + * @param fqn to retrieve + * @param lockForWriting if true, a lock will be acquired. + * @param createIfAbsent if true, will be created if absent. + * @param includeInvalidNodes if true, invalid nodes are included. + * @param forRemoval if true, the parent may also be locked if= locking parents for removal is necessary. + * @param force if true, will force the write lock even i= f the node is null. + * @return a wrapped node, or null. + */ + public ReadCommittedNode wrapNodeForWriting(InvocationContext context, = Fqn fqn, boolean lockForWriting, boolean createIfAbsent, boolean includeInv= alidNodes, boolean forRemoval, boolean force) throws InterruptedException + { + ReadCommittedNode n =3D (ReadCommittedNode) context.lookUpNode(fqn); + if (createIfAbsent && n !=3D null && n.isNullNode()) n =3D null; + if (n !=3D null) + { + // acquire lock if needed + if (lockForWriting && acquireLock(context, fqn)) + { + // create a copy of the underlying node + n.copyNodeForUpdate(dataContainer, allowWriteSkew, context, no= deFactory); + } + if (trace) log.trace("Retrieving wrapped node " + fqn); + if (n.isDeleted() && createIfAbsent) + { + if (trace) log.trace("Node is deleted in current scope. Need = to un-delete."); + n.markAsDeleted(false); + n.setValid(true, false); + } + + } + else + { + // else, fetch from dataContainer. + InternalNode in =3D dataContainer.peekInternalNode(fqn, includeIn= validNodes); + if (in !=3D null) + { + // do we need a lock? + boolean needToCopy =3D false; + if (lockForWriting && acquireLock(context, fqn)) + { + needToCopy =3D true; + } + n =3D nodeFactory.createMvccNode(in); + context.putLookedUpNode(fqn, n); + if (needToCopy) n.copyNodeForUpdate(dataContainer, allowWriteS= kew, context, nodeFactory); + } + else if (createIfAbsent) // else, do we need to create one? + { + Fqn parentFqn =3D fqn.getParent(); + NodeSPI parent =3D wrapNodeForWriting(context, parentFqn, fals= e, createIfAbsent, false, false, false); + // do we need to lock the parent to create children? + boolean parentLockNeeded =3D isParentLockNeeded(parent); + // get a lock on the parent. + if (parentLockNeeded && acquireLock(context, parentFqn)) + { + ReadCommittedNode parentRCN =3D (ReadCommittedNode) context= .lookUpNode(parentFqn); + parentRCN.copyNodeForUpdate(dataContainer, allowWriteSkew, = context, nodeFactory); + } + + // now to lock and create the node. + acquireLock(context, fqn); + + NodeSPI temp =3D parent.getOrCreateChild(fqn.getLastElement(),= context.getGlobalTransaction()); + // TODO: warning, hack! There is a race condition here. Add = a way to create nodes without attaching to a parent. + parent.removeChildDirect(fqn.getLastElement()); + + in =3D (InternalNode) ((NodeInvocationDelegate) temp).getDeleg= ationTarget(); + n =3D nodeFactory.createMvccNode(in); + n.setCreated(true); + context.putLookedUpNode(fqn, n); + n.copyNodeForUpdate(dataContainer, allowWriteSkew, context, no= deFactory); + } + } + + // see if we need to force the lock on nonexistent nodes. + if (n =3D=3D null && force) acquireLock(context, fqn); + + // now test if we need to lock the parent as well. + if ((n !=3D null || force) && forRemoval && isParentLockNeeded(fqn.g= etParent(), context)) + wrapNodeForWriting(context, fqn.getParent(), true, false, include= InvalidNodes, false, force); + + return n; + } + + /** + * Wraps a node and all its subnodes and adds them to the context, acqu= iring write locks for them all. + * + * @param ctx context + * @param fqn fqn to wrap + * @return a list of Fqns of locks acquired in this call. + * @throws InterruptedException if the lock manager is interrupted. + */ + public List wrapNodesForWriting(InvocationContext ctx, Fqn fqn) th= rows InterruptedException + { + // when removing a node we want to get a lock on the Fqn anyway and = return the wrapped node. + if (fqn.isRoot()) throw new CacheException("Attempting to remove Fqn= .ROOT!"); + + Fqn parentFqn =3D fqn.getParent(); + // inspect parent + boolean needToCopyParent =3D false; + boolean parentLockNeeded =3D isParentLockNeeded(parentFqn, ctx); + if (parentLockNeeded) + { + needToCopyParent =3D acquireLock(ctx, parentFqn); + // Ensure the node is in the context. + putNodeInContext(ctx, parentFqn, needToCopyParent); + } + + boolean needToCopyNode =3D acquireLock(ctx, fqn); + + // Ensure the node is in the context. + putNodeInContext(ctx, fqn, needToCopyNode); + + ReadCommittedNode node =3D (ReadCommittedNode) ctx.lookUpNode(fqn); + + // update child ref on parent to point to child as this is now a cop= y. + if (node !=3D null && !(node instanceof NullMarkerNode)) + { + if (parentLockNeeded && (needToCopyNode || needToCopyParent)) + { + ReadCommittedNode parent =3D (ReadCommittedNode) ctx.lookUpNod= e(parentFqn); + parent.addChildDirect(nodeFactory.createNodeInvocationDelegate= ((InternalNode) node.getDelegationTarget())); + } + + // now deal with children. + Map childMap =3D node.getChildrenMapDirect(); + List fqnsToBeRemoved =3D new LinkedList(); + fqnsToBeRemoved.add(fqn); + if (childMap =3D=3D null || childMap.isEmpty()) return fqnsToBeRe= moved; + + for (Object n : childMap.values()) + { + NodeSPI child =3D (NodeSPI) n; + lockForWritingRecursive(child.getFqn(), ctx, fqnsToBeRemoved); + } + + return fqnsToBeRemoved; + } + + return null; + } + + private void lockForWritingRecursive(Fqn fqn, InvocationContext ctx, Li= st fqnList) throws InterruptedException + { + acquireLock(ctx, fqn); // lock node + if (fqnList !=3D null) fqnList.add(fqn); + + // now wrap and add to the context + ReadCommittedNode rcn =3D wrapNodeForWriting(ctx, fqn, true, false, = true, false, false); + if (rcn !=3D null) + { + rcn.copyNodeForUpdate(dataContainer, allowWriteSkew, ctx, nodeFac= tory); + ReadCommittedNode parent =3D (ReadCommittedNode) ctx.lookUpNode(f= qn.getParent()); + parent.addChildDirect(nodeFactory.createNodeInvocationDelegate((I= nternalNode) rcn.getDelegationTarget())); + + Map children =3D rcn.getChildrenMapDirect(); + if (children !=3D null) + { + for (NodeSPI child : children.values()) + { + lockForWritingRecursive(child.getFqn(), ctx, fqnList); + } + } + } + } + + private void putNodeInContext(InvocationContext ctx, Fqn fqn, boolean n= eedToCopyNode) + { + ReadCommittedNode node =3D (ReadCommittedNode) ctx.lookUpNode(fqn); + if (node =3D=3D null) + { + InternalNode in =3D dataContainer.peekInternalNode(fqn, false); + node =3D nodeFactory.createMvccNode(in); + ctx.putLookedUpNode(fqn, node); + } + + if (needToCopyNode && node !=3D null && !node.isChanged()) // node c= ould be null if using read-committed + { + node.copyNodeForUpdate(dataContainer, allowWriteSkew, ctx, nodeFa= ctory); + } + } + + private boolean isParentLockNeeded(NodeSPI parent) + { + return lockParentForChildInsertRemove || (parent !=3D null && parent= .isLockForChildInsertRemove()); + } + + private boolean isParentLockNeeded(Fqn parent, InvocationContext ctx) + { + NodeSPI parentNode =3D ctx.lookUpNode(parent); + if (parentNode =3D=3D null) parentNode =3D dataContainer.peek(parent= , true, true); + return isParentLockNeeded(parentNode); + } +} --===============3262622121169124850==--