Author: manik.surtani(a)jboss.com
Date: 2008-06-25 14:31:09 -0400 (Wed, 25 Jun 2008)
New Revision: 6046
Added:
core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLockingInterceptor.java
core/trunk/src/main/java/org/jboss/cache/mvcc/NodeReference.java
core/trunk/src/main/java/org/jboss/cache/mvcc/ReadCommittedNode.java
core/trunk/src/main/java/org/jboss/cache/mvcc/RepeatableReadNode.java
core/trunk/src/test/java/org/jboss/cache/api/mvcc/
core/trunk/src/test/java/org/jboss/cache/api/mvcc/CacheAPIMVCCTest.java
core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockParentForChildInsertRemoveTest.java
core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockTest.java
core/trunk/src/test/java/org/jboss/cache/api/mvcc/NodeAPIMVCCTest.java
core/trunk/src/test/java/org/jboss/cache/mvcc/MVCCFullStackTest.java
Modified:
core/trunk/src/main/java/org/jboss/cache/AbstractNode.java
core/trunk/src/main/java/org/jboss/cache/DataContainer.java
core/trunk/src/main/java/org/jboss/cache/DataContainerImpl.java
core/trunk/src/main/java/org/jboss/cache/NodeFactory.java
core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java
core/trunk/src/main/java/org/jboss/cache/VersionedNode.java
core/trunk/src/main/java/org/jboss/cache/config/Configuration.java
core/trunk/src/main/java/org/jboss/cache/factories/InterceptorChainFactory.java
core/trunk/src/main/java/org/jboss/cache/factories/LockManagerFactory.java
core/trunk/src/main/java/org/jboss/cache/invocation/NodeInvocationDelegate.java
core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java
core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java
core/trunk/src/test/java/org/jboss/cache/api/NodeAPIOptimisticTest.java
core/trunk/src/test/java/org/jboss/cache/api/NodeAPITest.java
Log:
Partially complete MVCC implementation. Still WIP.
Modified: core/trunk/src/main/java/org/jboss/cache/AbstractNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/AbstractNode.java 2008-06-25 18:29:49 UTC
(rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/AbstractNode.java 2008-06-25 18:31:09 UTC
(rev 6046)
@@ -14,7 +14,7 @@
*
* @author manik
*/
-public abstract class AbstractNode<K, V>// implements Node<K, V>
+public abstract class AbstractNode<K, V>
{
protected Map<Object, Node<K, V>> children;
protected Fqn<?> fqn;
Modified: core/trunk/src/main/java/org/jboss/cache/DataContainer.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/DataContainer.java 2008-06-25 18:29:49 UTC
(rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/DataContainer.java 2008-06-25 18:31:09 UTC
(rev 6046)
@@ -1,6 +1,7 @@
package org.jboss.cache;
import org.jboss.cache.marshall.NodeData;
+import org.jboss.cache.mvcc.InternalNode;
import org.jboss.cache.optimistic.DataVersion;
import org.jboss.cache.transaction.GlobalTransaction;
@@ -208,4 +209,19 @@
* @return see above.
*/
Object[] createNodes(Fqn fqn);
+
+ /**
+ * Similar to {@link #peek(Fqn)} except that the underlying node is NOT wrapped as a
{@link org.jboss.cache.NodeSPI}.
+ *
+ * @param f fqn to peek
+ * @return internal node
+ */
+ InternalNode peekInternalNode(Fqn f);
+
+ /**
+ * Sets a new root node
+ *
+ * @param nodeInvocationDelegate
+ */
+ void setRoot(NodeSPI nodeInvocationDelegate);
}
Modified: core/trunk/src/main/java/org/jboss/cache/DataContainerImpl.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/DataContainerImpl.java 2008-06-25 18:29:49
UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/DataContainerImpl.java 2008-06-25 18:31:09
UTC (rev 6046)
@@ -11,6 +11,7 @@
import org.jboss.cache.invocation.NodeInvocationDelegate;
import org.jboss.cache.lock.LockManager;
import org.jboss.cache.marshall.NodeData;
+import org.jboss.cache.mvcc.InternalNode;
import org.jboss.cache.optimistic.DataVersion;
import org.jboss.cache.transaction.GlobalTransaction;
@@ -527,6 +528,14 @@
return new Object[]{result, n};
}
+ public InternalNode peekInternalNode(Fqn f)
+ {
+ // Yuck!
+ NodeSPI nodeSPI = peek(f);
+ if (nodeSPI == null) return null;
+ return (InternalNode) ((NodeInvocationDelegate) nodeSPI).getDelegationTarget();
+ }
+
public void setBuddyFqnTransformer(BuddyFqnTransformer buddyFqnTransformer)
{
this.buddyFqnTransformer = buddyFqnTransformer;
Modified: core/trunk/src/main/java/org/jboss/cache/NodeFactory.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/NodeFactory.java 2008-06-25 18:29:49 UTC (rev
6045)
+++ core/trunk/src/main/java/org/jboss/cache/NodeFactory.java 2008-06-25 18:31:09 UTC (rev
6046)
@@ -7,6 +7,7 @@
package org.jboss.cache;
import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
import org.jboss.cache.factories.CommandsFactory;
import org.jboss.cache.factories.ComponentFactory;
import org.jboss.cache.factories.annotations.Inject;
@@ -15,6 +16,7 @@
import org.jboss.cache.invocation.InvocationContextContainer;
import org.jboss.cache.invocation.NodeInvocationDelegate;
import org.jboss.cache.lock.LockStrategyFactory;
+import org.jboss.cache.mvcc.InternalNode;
import org.jboss.cache.optimistic.TransactionWorkspace;
import org.jboss.cache.optimistic.WorkspaceNode;
import org.jboss.cache.optimistic.WorkspaceNodeImpl;
@@ -29,7 +31,7 @@
public class NodeFactory<K, V> extends ComponentFactory
{
private CacheSPI<K, V> cache;
- private boolean optimistic;
+ private boolean useVersionedNode;
private Configuration configuration;
private InvocationContextContainer invocationContextContainer;
private InterceptorChain interceptorChain;
@@ -62,8 +64,8 @@
@Inject
public void injectDependencies(CacheSPI<K, V> cache, Configuration
configuration,
- InvocationContextContainer
invocationContextContainer,
- InterceptorChain interceptorChain, CommandsFactory
commandsFactory, LockStrategyFactory lockStrategyFactory)
+ InvocationContextContainer invocationContextContainer,
+ InterceptorChain interceptorChain, CommandsFactory
commandsFactory, LockStrategyFactory lockStrategyFactory)
{
this.cache = cache;
this.configuration = configuration;
@@ -79,7 +81,7 @@
@Start
public void init()
{
- optimistic = configuration.isNodeLockingOptimistic();
+ useVersionedNode = configuration.getNodeLockingScheme() !=
NodeLockingScheme.PESSIMISTIC;
}
@@ -98,7 +100,7 @@
*/
public NodeSPI<K, V> createDataNode(Object childName, Fqn fqn, NodeSPI<K,
V> parent, Map<K, V> data, boolean mapSafe)
{
- UnversionedNode un = optimistic ? new VersionedNode<K, V>(fqn, parent, data,
cache) : new UnversionedNode<K, V>(childName, fqn, data, cache);
+ UnversionedNode un = useVersionedNode ? new VersionedNode<K, V>(fqn, parent,
data, cache) : new UnversionedNode<K, V>(childName, fqn, data, cache);
// always assume that new nodes do not have data loaded
un.setDataLoaded(false);
NodeInvocationDelegate<K, V> nid = new NodeInvocationDelegate(un);
@@ -136,4 +138,11 @@
return createDataNode(null, Fqn.ROOT, null, null, false);
}
+ public NodeSPI createNodeInvocationDelegate(InternalNode internalNode)
+ {
+ NodeInvocationDelegate nid = new NodeInvocationDelegate(internalNode);
+ nid.initialize(configuration, invocationContextContainer, componentRegistry,
interceptorChain);
+ nid.injectDependencies(cache);
+ return nid;
+ }
}
Modified: core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java 2008-06-25 18:29:49 UTC
(rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/UnversionedNode.java 2008-06-25 18:31:09 UTC
(rev 6046)
@@ -50,15 +50,15 @@
/**
* A reference of the CacheImpl instance.
*/
- private transient CacheSPI cache;
+ transient CacheSPI cache;
/**
* Map of general data keys to values.
*/
- private final Map data = new HashMap();
+ final Map data = new HashMap();
protected NodeSPI delegate;
- private CommandsFactory commandsFactory;
+ CommandsFactory commandsFactory;
protected LockStrategyFactory lockStrategyFactory;
/**
Modified: core/trunk/src/main/java/org/jboss/cache/VersionedNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/VersionedNode.java 2008-06-25 18:29:49 UTC
(rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/VersionedNode.java 2008-06-25 18:31:09 UTC
(rev 6046)
@@ -28,6 +28,7 @@
{
private static final String DATA_VERSION_INTERNAL_KEY =
"_JBOSS_INTERNAL_OPTIMISTIC_DATA_VERSION";
private DataVersion version; // make sure this is NOT initialized to anything, even a
null! Since the UnversionedNode constructor may set this value based on a data version
passed along in the data map.
+
static
{
log = LogFactory.getLog(VersionedNode.class);
@@ -106,4 +107,18 @@
}
super.setInternalState(state);
}
+
+ @Override
+ public VersionedNode copy()
+ {
+ VersionedNode n = new VersionedNode(fqn, getParent(), data, cache);
+ n.children = children;
+ n.commandsFactory = commandsFactory;
+ n.delegate = delegate;
+ n.flags.clear();
+ n.flags.addAll(flags);
+ n.lockStrategyFactory = lockStrategyFactory;
+ n.version = version;
+ return n;
+ }
}
Modified: core/trunk/src/main/java/org/jboss/cache/config/Configuration.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/config/Configuration.java 2008-06-25 18:29:49
UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/config/Configuration.java 2008-06-25 18:31:09
UTC (rev 6046)
@@ -7,7 +7,6 @@
package org.jboss.cache.config;
import org.jboss.cache.Version;
-import org.jboss.cache.config.parsing.XmlConfigurationParser2x;
import org.jboss.cache.config.parsing.JGroupsStackParser;
import org.jboss.cache.factories.annotations.NonVolatile;
import org.jboss.cache.lock.IsolationLevel;
@@ -15,9 +14,9 @@
import org.w3c.dom.Element;
import java.net.URL;
+import java.util.Collections;
+import java.util.List;
import java.util.Locale;
-import java.util.List;
-import java.util.Collections;
/**
* Encapsulates the configuration of a Cache.
@@ -133,12 +132,17 @@
public enum NodeLockingScheme
{
/**
+ * Data is locked using the MVCC locking scheme. This is the default locking
scheme in JBoss Cache 3.0.0.
+ *
+ * @see <a
href="http://wiki.jboss.org/wiki/JBossCacheMVCC">http://wiki...
+ */
+ MVCC,
+ /**
* Data is exclusively locked during modification.
*
* @see <a
href="http://en.wikipedia.org/wiki/Concurrency_control">http...
(pessimistic)</a>
*/
PESSIMISTIC,
-
/**
* Data is unlocked during modification, modifications merged at commit.
*
@@ -189,7 +193,7 @@
private boolean syncRollbackPhase = false;
private BuddyReplicationConfig buddyReplicationConfig;
private boolean nodeLockingOptimistic = false;
- private NodeLockingScheme nodeLockingScheme = NodeLockingScheme.PESSIMISTIC;
+ private NodeLockingScheme nodeLockingScheme = NodeLockingScheme.PESSIMISTIC; // TODO:
Make this default MVCC once MVCC is completely implemented.
private String muxStackName = null;
private boolean usingMultiplexer = false;
private transient RuntimeConfig runtimeConfig;
@@ -198,7 +202,7 @@
private boolean useLazyDeserialization = false;
private int objectInputStreamPoolSize = 50;
private int objectOutputStreamPoolSize = 50;
- private List<CustomInterceptorConfig> customInterceptors =
Collections.EMPTY_LIST;
+ private List<CustomInterceptorConfig> customInterceptors =
Collections.emptyList();
//
------------------------------------------------------------------------------------------------------------
// SETTERS - MAKE SURE ALL SETTERS PERFORM testImmutability()!!!
@@ -537,6 +541,7 @@
return this.shutdownHookBehavior;
}
+ @Deprecated
public boolean isNodeLockingOptimistic()
{
return nodeLockingOptimistic;
Modified: core/trunk/src/main/java/org/jboss/cache/factories/InterceptorChainFactory.java
===================================================================
---
core/trunk/src/main/java/org/jboss/cache/factories/InterceptorChainFactory.java 2008-06-25
18:29:49 UTC (rev 6045)
+++
core/trunk/src/main/java/org/jboss/cache/factories/InterceptorChainFactory.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -7,6 +7,7 @@
package org.jboss.cache.factories;
import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
import org.jboss.cache.config.ConfigurationException;
import org.jboss.cache.factories.annotations.DefaultFactoryFor;
import org.jboss.cache.interceptors.*;
@@ -84,8 +85,12 @@
//Nothing...
}
- if (!optimistic)
+ if (configuration.getNodeLockingScheme() == NodeLockingScheme.MVCC)
{
+
interceptorChain.appendIntereceptor(createInterceptor(MVCCLockingInterceptor.class));
+ }
+ else if (configuration.getNodeLockingScheme() == NodeLockingScheme.PESSIMISTIC)
+ {
interceptorChain.appendIntereceptor(createInterceptor(PessimisticLockInterceptor.class));
}
Modified: core/trunk/src/main/java/org/jboss/cache/factories/LockManagerFactory.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/factories/LockManagerFactory.java 2008-06-25
18:29:49 UTC (rev 6045)
+++ core/trunk/src/main/java/org/jboss/cache/factories/LockManagerFactory.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -2,6 +2,7 @@
import org.jboss.cache.factories.annotations.DefaultFactoryFor;
import org.jboss.cache.lock.LockManager;
+import org.jboss.cache.lock.MVCCLockManager;
import org.jboss.cache.lock.NodeBasedLockManager;
import org.jboss.cache.lock.PessimisticNodeBasedLockManager;
@@ -18,13 +19,15 @@
@SuppressWarnings({"unchecked", "deprecation"})
protected <T> T construct(Class<T> componentType)
{
- if (configuration.isNodeLockingOptimistic())
+ switch (configuration.getNodeLockingScheme())
{
- return (T) super.construct(NodeBasedLockManager.class);
+ case MVCC:
+ return (T) super.construct(MVCCLockManager.class);
+ case OPTIMISTIC:
+ return (T) super.construct(NodeBasedLockManager.class);
+ case PESSIMISTIC:
+ default:
+ return (T) super.construct(PessimisticNodeBasedLockManager.class);
}
- else
- {
- return (T) super.construct(PessimisticNodeBasedLockManager.class);
- }
}
}
Added: core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLockingInterceptor.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLockingInterceptor.java
(rev 0)
+++
core/trunk/src/main/java/org/jboss/cache/interceptors/MVCCLockingInterceptor.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -0,0 +1,392 @@
+package org.jboss.cache.interceptors;
+
+import org.jboss.cache.DataContainer;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.InvocationContext;
+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;
+import org.jboss.cache.commands.read.GetDataMapCommand;
+import org.jboss.cache.commands.read.GetKeyValueCommand;
+import org.jboss.cache.commands.read.GetKeysCommand;
+import org.jboss.cache.commands.read.GetNodeCommand;
+import org.jboss.cache.commands.read.GravitateDataCommand;
+import org.jboss.cache.commands.tx.CommitCommand;
+import org.jboss.cache.commands.tx.RollbackCommand;
+import org.jboss.cache.commands.write.*;
+import org.jboss.cache.factories.annotations.Inject;
+import org.jboss.cache.factories.annotations.Start;
+import org.jboss.cache.interceptors.base.PostProcessingCommandInterceptor;
+import org.jboss.cache.invocation.NodeInvocationDelegate;
+import org.jboss.cache.lock.IsolationLevel;
+import org.jboss.cache.lock.LockManager;
+import static org.jboss.cache.lock.LockType.WRITE;
+import org.jboss.cache.mvcc.InternalNode;
+import org.jboss.cache.mvcc.ReadCommittedNode;
+import org.jboss.cache.mvcc.RepeatableReadNode;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Interceptor to implement <a
href="http://wiki.jboss.org/wiki/JBossCacheMVCC">MVCC</a>
functionality.
+ *
+ * @author Manik Surtani (<a
href="mailto:manik@jboss.org">manik@jboss.org</a>)
+ * @see <a
href="http://wiki.jboss.org/wiki/JBossCacheMVCC">MVCC
designs</a>
+ * @since 3.0
+ */
+public class MVCCLockingInterceptor extends PostProcessingCommandInterceptor
+{
+ // TODO: Implement me.
+
+ // Need to think about how this will behave with 1. LockParentForChildInsertRemove
and 2. CacheLoading.
+
+ // Need to think further about cached node lookups. Walking the tree sucks ass.
+
+ // How will wrapper nodes unwrap?
+
+ boolean isUsingRepeatableRead, ignoreWriteSkew, lockParentForChildInsertRemove;
+ LockManager lockManager;
+ DataContainer dataContainer;
+ NodeFactory nodeFactory;
+
+ @Inject
+ public void setDependencies(LockManager lockManager, DataContainer dataContainer,
NodeFactory nodeFactory)
+ {
+ this.lockManager = lockManager;
+ this.dataContainer = dataContainer;
+ this.nodeFactory = nodeFactory;
+ }
+
+ @Start
+ public void start()
+ {
+ isUsingRepeatableRead = configuration.getIsolationLevel() ==
IsolationLevel.REPEATABLE_READ; // otherwise default to READ_COMMITTED
+ ignoreWriteSkew = true; // todo this should come from a cfg variable
+ lockParentForChildInsertRemove = configuration.isLockParentForChildInsertRemove();
+ }
+
+ @Override
+ public Object handlePutDataMapCommand(InvocationContext ctx, PutDataMapCommand
command) throws Throwable
+ {
+ getWrappedNode(ctx, command.getFqn(), true, true); // get the node and stick it in
the context.
+ return invokeNextInterceptor(ctx, command);
+ }
+
+ @Override
+ public Object handlePutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand
command) throws Throwable
+ {
+ getWrappedNode(ctx, command.getFqn(), true, true); // get the node and stick it in
the context.
+ return invokeNextInterceptor(ctx, command);
+ }
+
+ @Override
+ public Object handlePutForExternalReadCommand(InvocationContext ctx,
PutForExternalReadCommand command) throws Throwable
+ {
+ getWrappedNode(ctx, command.getFqn(), true, true); // get the node and stick it in
the context.
+ return invokeNextInterceptor(ctx, command);
+ }
+
+ @Override
+ public Object handleRemoveNodeCommand(InvocationContext ctx, RemoveNodeCommand
command) throws Throwable
+ {
+ return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1),
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleClearDataCommand(InvocationContext ctx, ClearDataCommand command)
throws Throwable
+ {
+ return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1),
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleEvictFqnCommand(InvocationContext ctx, EvictCommand command)
throws Throwable
+ {
+ return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1),
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleInvalidateCommand(InvocationContext ctx, InvalidateCommand
command) throws Throwable
+ {
+ return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1),
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleRemoveKeyCommand(InvocationContext ctx, RemoveKeyCommand command)
throws Throwable
+ {
+ return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1),
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleGetDataMapCommand(InvocationContext ctx, GetDataMapCommand
command) throws Throwable
+ {
+ return handleReadCommand(ctx, command,
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleExistsNodeCommand(InvocationContext ctx, ExistsCommand command)
throws Throwable
+ {
+ return handleReadCommand(ctx, command,
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand
command) throws Throwable
+ {
+ return handleReadCommand(ctx, command,
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleGetNodeCommand(InvocationContext ctx, GetNodeCommand command)
throws Throwable
+ {
+ return handleReadCommand(ctx, command,
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleGetKeysCommand(InvocationContext ctx, GetKeysCommand command)
throws Throwable
+ {
+ return handleReadCommand(ctx, command,
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleGetChildrenNamesCommand(InvocationContext ctx,
GetChildrenNamesCommand command) throws Throwable
+ {
+ return handleReadCommand(ctx, command,
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleMoveCommand(InvocationContext ctx, MoveCommand command) throws
Throwable
+ {
+ List<Fqn> list = new ArrayList<Fqn>();
+ list.add(command.getFqn());
+ list.add(command.getTo());
+ return handleWriteCommand(ctx, command, new ArrayList<Fqn>(1), list);
+ }
+
+ @Override
+ public Object handleGravitateDataCommand(InvocationContext ctx, GravitateDataCommand
command) throws Throwable
+ {
+ return handleReadCommand(ctx, command,
Collections.singletonList(command.getFqn()));
+ }
+
+ @Override
+ public Object handleCreateNodeCommand(InvocationContext ctx, CreateNodeCommand
command) throws Throwable
+ {
+ getWrappedNode(ctx, command.getFqn(), true, true); // get the node and stick it in
the context.
+ return invokeNextInterceptor(ctx, command);
+ }
+
+ @Override
+ public Object handleRollbackCommand(InvocationContext ctx, RollbackCommand command)
throws Throwable
+ {
+ Object retval = null;
+ try
+ {
+ retval = invokeNextInterceptor(ctx, command);
+ }
+ finally
+ {
+ transactionalCleanup(false, ctx);
+ }
+ return retval;
+ }
+
+ @Override
+ public Object handleCommitCommand(InvocationContext ctx, CommitCommand command) throws
Throwable
+ {
+ Object retval = null;
+ try
+ {
+ retval = invokeNextInterceptor(ctx, command);
+ }
+ finally
+ {
+ transactionalCleanup(true, ctx);
+ }
+ return retval;
+ }
+
+ protected void doAfterCall(InvocationContext ctx, VisitableCommand command)
+ {
+ // for non-transactional stuff.
+ if (ctx.getTransaction() == null)
+ {
+ List<Fqn> locks;
+ if (!(locks = ctx.getLocks()).isEmpty())
+ {
+ // clean up.
+ Fqn[] fqnsToUnlock = new Fqn[locks.size()];
+ fqnsToUnlock = locks.toArray(fqnsToUnlock);
+ Object owner = Thread.currentThread();
+
+ for (int i = fqnsToUnlock.length - 1; i > -1; i--)
+ {
+ // for each of these, swap refs
+ ReadCommittedNode rcn = (ReadCommittedNode)
ctx.lookUpNode(fqnsToUnlock[i]);
+ rcn.copyNodeForUpdate(dataContainer, ignoreWriteSkew);
+ rcn.commitUpdate(dataContainer, nodeFactory);
+ // and then unlock
+ lockManager.unlock(fqnsToUnlock[i], owner);
+ }
+ ctx.clearLocks();
+ }
+ }
+ }
+
+ protected void transactionalCleanup(boolean commit, InvocationContext ctx)
+ {
+ if (ctx.getTransaction() != null)
+ {
+ List<Fqn> locks;
+ if (!(locks = ctx.getTransactionContext().getLocks()).isEmpty())
+ {
+ // clean up.
+ Fqn[] fqnsToUnlock = new Fqn[locks.size()];
+ fqnsToUnlock = locks.toArray(fqnsToUnlock);
+ Object owner = ctx.getGlobalTransaction();
+
+ for (int i = fqnsToUnlock.length - 1; i > -1; i--)
+ {
+ ReadCommittedNode rcn = (ReadCommittedNode)
ctx.lookUpNode(fqnsToUnlock[i]);
+ if (commit)
+ {
+ // for each of these, swap refs
+ rcn.copyNodeForUpdate(dataContainer, ignoreWriteSkew);
+ rcn.commitUpdate(dataContainer, nodeFactory);
+ }
+ else
+ {
+ rcn.rollbackUpdate();
+ }
+ // and then unlock
+ lockManager.unlock(fqnsToUnlock[i], owner);
+ }
+ ctx.clearLocks();
+ }
+ }
+ }
+
+ // ----------------- actual implementation details ----------------------------
+
+ protected Object handleReadCommand(InvocationContext ctx, VisitableCommand command,
List<Fqn> fqns) throws Throwable
+ {
+ // does the node exist in the context?
+ for (Fqn f : fqns)
+ {
+ if (ctx.lookUpNode(f) == null)
+ {
+ // simple implementation. Peek the node, wrap it, put wrapped node in the
context.
+ InternalNode node = dataContainer.peekInternalNode(f);
+ if (node != null)
+ {
+ NodeSPI wrapped = isUsingRepeatableRead ? new RepeatableReadNode(node) :
new ReadCommittedNode(node);
+ ctx.putLookedUpNode(f, wrapped);
+ }
+ }
+ }
+ return invokeNextInterceptor(ctx, command);
+ }
+
+ protected Object handleWriteCommand(InvocationContext ctx, VisitableCommand command,
List<Fqn> fqnsToRead, List<Fqn> fqnsToWrite) throws Throwable
+ {
+ for (Fqn f : fqnsToRead)
+ {
+ if (ctx.lookUpNode(f) == null)
+ {
+ // simple implementation. Peek the node, wrap it, put wrapped node in the
context.
+ InternalNode node = dataContainer.peekInternalNode(f);
+ if (node != null)
+ {
+ NodeSPI wrapped = isUsingRepeatableRead ? new RepeatableReadNode(node) :
new ReadCommittedNode(node);
+ ctx.putLookedUpNode(f, wrapped);
+ }
+ }
+ }
+
+ for (Fqn f : fqnsToWrite)
+ {
+ if (ctx.lookUpNode(f) == null)
+ {
+ // simple implementation. Peek the node, wrap it, put wrapped node in the
context.
+ InternalNode node = dataContainer.peekInternalNode(f);
+ if (node != null)
+ {
+ lockManager.lock(f, WRITE, ctx);
+ NodeSPI wrapped = isUsingRepeatableRead ? new RepeatableReadNode(node) :
new ReadCommittedNode(node);
+ ctx.putLookedUpNode(f, wrapped);
+ }
+ }
+ }
+
+ 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 node, 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 as 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 lock if true, a lock will be acquired.
+ * @param createIfAbsent if true, will be created if absent.
+ * @return a NodeSPI or null.
+ */
+ protected NodeSPI getWrappedNode(InvocationContext context, Fqn fqn, boolean lock,
boolean createIfAbsent) throws InterruptedException
+ {
+ NodeSPI n = context.lookUpNode(fqn);
+ if (n != null)
+ {
+ // acquire lock if needed
+ if (lock && !isLocked(context, fqn)) lockManager.lockAndRecord(fqn,
WRITE, context);
+ if (trace) log.trace("Retrieving wrapped node " + fqn);
+ return n;
+ }
+
+ // else, fetch from dataContainer.
+ InternalNode in = dataContainer.peekInternalNode(fqn);
+ if (in != null)
+ {
+ // do we need a lock?
+ if (lock && !isLocked(context, fqn)) lockManager.lockAndRecord(fqn,
WRITE, context);
+ NodeSPI wrapped = isUsingRepeatableRead ? new RepeatableReadNode(in) : new
ReadCommittedNode(in);
+ context.putLookedUpNode(fqn, wrapped);
+ return wrapped;
+ }
+
+ // else, do we need to create one?
+ if (createIfAbsent)
+ {
+ Fqn parentFqn = fqn.getParent();
+ NodeSPI parent = getWrappedNode(context, parentFqn, false, createIfAbsent);
+ // do we need to lock the parent to create children?
+ if (lockParentForChildInsertRemove || parent.isLockForChildInsertRemove())
+ {
+ // get a lock on the parent.
+ if (!isLocked(context, parentFqn)) lockManager.lockAndRecord(parentFqn,
WRITE, context);
+ }
+
+ // now to lock and create the node.
+ if (!isLocked(context, fqn)) lockManager.lockAndRecord(fqn, WRITE, context);
+ NodeSPI temp = parent.getOrCreateChild(fqn.getLastElement(),
context.getGlobalTransaction());
+ in = (InternalNode) ((NodeInvocationDelegate) temp).getDelegationTarget();
+ NodeSPI wrapped = isUsingRepeatableRead ? new RepeatableReadNode(in) : new
ReadCommittedNode(in);
+ context.putLookedUpNode(fqn, wrapped);
+ return wrapped;
+ }
+
+ return null;
+ }
+
+ protected boolean isLocked(InvocationContext ctx, Fqn fqn)
+ {
+ // 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 that is a
problem.
+ // Better to check our records and lock again if necessary.
+ return ctx.getLocks().contains(fqn);
+ }
+}
Modified: core/trunk/src/main/java/org/jboss/cache/invocation/NodeInvocationDelegate.java
===================================================================
---
core/trunk/src/main/java/org/jboss/cache/invocation/NodeInvocationDelegate.java 2008-06-25
18:29:49 UTC (rev 6045)
+++
core/trunk/src/main/java/org/jboss/cache/invocation/NodeInvocationDelegate.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -283,16 +283,7 @@
if (child == null)
{
- Option o2;
- try
- {
- o2 = o1.clone();
- }
- catch (CloneNotSupportedException e)
- {
- // should never happen
- throw new RuntimeException(e);
- }
+ Option o2 = o1.copy();
spi.getInvocationContext().setOptionOverrides(o1);
spi.put(nf, null);
Added: core/trunk/src/main/java/org/jboss/cache/mvcc/NodeReference.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/mvcc/NodeReference.java
(rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/mvcc/NodeReference.java 2008-06-25 18:31:09
UTC (rev 6046)
@@ -0,0 +1,284 @@
+package org.jboss.cache.mvcc;
+
+import org.jboss.cache.CacheSPI;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.Node;
+import org.jboss.cache.NodeSPI;
+import org.jboss.cache.lock.NodeLock;
+import org.jboss.cache.optimistic.DataVersion;
+import org.jboss.cache.transaction.GlobalTransaction;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * // TODO Document this
+ *
+ * @author Manik Surtani (<a
href="mailto:manik@jboss.org">manik@jboss.org</a>)
+ * @since 3.0
+ */
+public class NodeReference implements InternalNode
+{
+ transient volatile InternalNode delegate;
+
+ public NodeReference(InternalNode delegate)
+ {
+ this.delegate = delegate;
+ }
+
+ public InternalNode getDelegate()
+ {
+ return delegate;
+ }
+
+ public void setDelegate(InternalNode delegate)
+ {
+ this.delegate = delegate;
+ }
+
+ public NodeSPI getParent()
+ {
+ return delegate.getParent();
+ }
+
+ public CacheSPI getCache()
+ {
+ return delegate.getCache();
+ }
+
+ public boolean isChildrenLoaded()
+ {
+ return delegate.isChildrenLoaded();
+ }
+
+ public void setChildrenLoaded(boolean flag)
+ {
+ delegate.setChildrenLoaded(flag);
+ }
+
+ public Object getDirect(Object key)
+ {
+ return delegate.getDirect(key);
+ }
+
+ public Map getDataDirect()
+ {
+ return delegate.getDataDirect();
+ }
+
+ public Object putDirect(Object key, Object value)
+ {
+ return delegate.putDirect(key, value);
+ }
+
+ public NodeSPI getOrCreateChild(Object child_name, GlobalTransaction gtx, boolean
notify)
+ {
+ return delegate.getOrCreateChild(child_name, gtx, notify);
+ }
+
+ public Object removeDirect(Object key)
+ {
+ return delegate.removeDirect(key);
+ }
+
+ public void addChildDirect(NodeSPI child)
+ {
+ delegate.addChildDirect(child);
+ }
+
+ public NodeSPI addChildDirect(Fqn f)
+ {
+ return delegate.addChildDirect(f);
+ }
+
+ public NodeSPI addChildDirect(Fqn f, boolean notify)
+ {
+ return delegate.addChildDirect(f, notify);
+ }
+
+ public NodeSPI addChildDirect(Object o, boolean notify)
+ {
+ return delegate.addChildDirect(o, notify);
+ }
+
+ public void clearDataDirect()
+ {
+ delegate.clearDataDirect();
+ }
+
+ public NodeSPI getChildDirect(Fqn fqn)
+ {
+ return delegate.getChildDirect(fqn);
+ }
+
+ public Set getChildrenNamesDirect()
+ {
+ return delegate.getChildrenNamesDirect();
+ }
+
+ public Set getKeysDirect()
+ {
+ return delegate.getKeysDirect();
+ }
+
+ public boolean removeChildDirect(Object childName)
+ {
+ return delegate.removeChildDirect(childName);
+ }
+
+ public boolean removeChildDirect(Fqn f)
+ {
+ return delegate.removeChildDirect(f);
+ }
+
+ public Map getChildrenMapDirect()
+ {
+ return delegate.getChildrenMapDirect();
+ }
+
+ public void setChildrenMapDirect(Map children)
+ {
+ delegate.setChildrenMapDirect(children);
+ }
+
+ public void putAllDirect(Map data)
+ {
+ delegate.putAllDirect(data);
+ }
+
+ public void removeChildrenDirect()
+ {
+ delegate.removeChildrenDirect();
+ }
+
+ public void setVersion(DataVersion version)
+ {
+ delegate.setVersion(version);
+ }
+
+ public DataVersion getVersion()
+ {
+ return delegate.getVersion();
+ }
+
+ public Fqn getFqn()
+ {
+ return delegate.getFqn();
+ }
+
+ public void setFqn(Fqn fqn)
+ {
+ delegate.setFqn(fqn);
+ }
+
+ public NodeSPI getChildDirect(Object childName)
+ {
+ return delegate.getChildDirect(childName);
+ }
+
+ public Set getChildrenDirect()
+ {
+ return delegate.getChildrenDirect();
+ }
+
+ public boolean hasChildrenDirect()
+ {
+ return delegate.hasChildrenDirect();
+ }
+
+ public Set getChildrenDirect(boolean includeMarkedForRemoval)
+ {
+ return delegate.getChildrenDirect(includeMarkedForRemoval);
+ }
+
+ public boolean isDataLoaded()
+ {
+ return delegate.isDataLoaded();
+ }
+
+ public void setDataLoaded(boolean dataLoaded)
+ {
+ delegate.setDataLoaded(dataLoaded);
+ }
+
+ public boolean isValid()
+ {
+ return delegate.isValid();
+ }
+
+ public void setValid(boolean valid, boolean recursive)
+ {
+ delegate.setValid(valid, recursive);
+ }
+
+ public boolean isLockForChildInsertRemove()
+ {
+ return delegate.isLockForChildInsertRemove();
+ }
+
+ public void setLockForChildInsertRemove(boolean lockForChildInsertRemove)
+ {
+ delegate.setLockForChildInsertRemove(lockForChildInsertRemove);
+ }
+
+ public void setInternalState(Map state)
+ {
+ delegate.setInternalState(state);
+ }
+
+ public Map getInternalState(boolean onlyInternalState)
+ {
+ return delegate.getInternalState(onlyInternalState);
+ }
+
+ public void releaseObjectReferences(boolean recursive)
+ {
+ delegate.releaseObjectReferences(recursive);
+ }
+
+ public boolean isDeleted()
+ {
+ return delegate.isDeleted();
+ }
+
+ public void markAsDeleted(boolean marker)
+ {
+ delegate.markAsDeleted(marker);
+ }
+
+ public void markAsDeleted(boolean marker, boolean recursive)
+ {
+ delegate.markAsDeleted(marker, recursive);
+ }
+
+ public void setResident(boolean resident)
+ {
+ delegate.setResident(resident);
+ }
+
+ public boolean isResident()
+ {
+ return delegate.isResident();
+ }
+
+ public InternalNode copy()
+ {
+ InternalNode cloneDelegate = delegate.copy();
+ return new NodeReference(cloneDelegate);
+ }
+
+ public NodeLock getLock()
+ {
+ return delegate.getLock();
+ }
+
+ public void addChild(Object nodeName, Node nodeToAdd)
+ {
+ delegate.addChild(nodeName, nodeToAdd);
+ }
+
+ public void printDetails(StringBuffer sb, int indent)
+ {
+ delegate.printDetails(sb, indent);
+ }
+}
Added: core/trunk/src/main/java/org/jboss/cache/mvcc/ReadCommittedNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/mvcc/ReadCommittedNode.java
(rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/mvcc/ReadCommittedNode.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -0,0 +1,63 @@
+package org.jboss.cache.mvcc;
+
+import org.jboss.cache.DataContainer;
+import org.jboss.cache.NodeFactory;
+import org.jboss.cache.invocation.NodeInvocationDelegate;
+import org.jboss.cache.optimistic.DataVersion;
+import org.jboss.cache.optimistic.DefaultDataVersion;
+
+/**
+ * Repeatable read would do a simple delegation to the underlying node.
+ *
+ * @author Manik Surtani (<a
href="mailto:manik@jboss.org">manik@jboss.org</a>)
+ * @since 3.0
+ */
+public class ReadCommittedNode extends NodeInvocationDelegate
+{
+ protected volatile InternalNode backup;
+ protected boolean changed;
+
+ public ReadCommittedNode(InternalNode node)
+ {
+ super(node);
+ }
+
+ public void copyNodeForUpdate(DataContainer container, boolean ignoreWriteSkew)
+ {
+ changed = true;
+ backup = node;
+ node = backup.copy();
+ // TODO: Make sure this works with custom versions as well!
+ DataVersion newVersion = ((DefaultDataVersion) node.getVersion()).increment();
+ node.setVersion(newVersion);
+ }
+
+ public void commitUpdate(DataContainer container, NodeFactory nf)
+ {
+ if (changed)
+ {
+ updateNode(container, nf);
+ changed = false;
+ backup = null;
+ }
+ }
+
+ protected void updateNode(DataContainer container, NodeFactory nf)
+ {
+ // TODO: Deal with removes and moves
+ // TODO: Deal with creating - what if children is null?
+
+ ((NodeReference) backup).setDelegate(((NodeReference) node).getDelegate());
+ node = backup;
+ }
+
+ public void rollbackUpdate()
+ {
+ if (changed)
+ {
+ node = backup;
+ backup = null;
+ changed = false;
+ }
+ }
+}
Added: core/trunk/src/main/java/org/jboss/cache/mvcc/RepeatableReadNode.java
===================================================================
--- core/trunk/src/main/java/org/jboss/cache/mvcc/RepeatableReadNode.java
(rev 0)
+++ core/trunk/src/main/java/org/jboss/cache/mvcc/RepeatableReadNode.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -0,0 +1,57 @@
+package org.jboss.cache.mvcc;
+
+import org.jboss.cache.CacheException;
+import org.jboss.cache.DataContainer;
+import org.jboss.cache.NodeFactory;
+import org.jboss.cache.NodeSPI;
+import org.jboss.cache.optimistic.DataVersion;
+import org.jboss.cache.optimistic.DefaultDataVersion;
+
+/**
+ * // TODO Document this
+ *
+ * @author Manik Surtani (<a
href="mailto:manik@jboss.org">manik@jboss.org</a>)
+ * @since 3.0
+ */
+public class RepeatableReadNode extends ReadCommittedNode
+{
+ public RepeatableReadNode(InternalNode node)
+ {
+ super(node);
+ }
+
+ @Override
+ public void copyNodeForUpdate(DataContainer container, boolean ignoreWriteSkew)
+ {
+ changed = true;
+ backup = node;
+ // write skew check
+ DataVersion underlyingNodeVersion = container.peek(getFqn()).getVersion();
+ if (ignoreWriteSkew || backup.getVersion().equals(underlyingNodeVersion))
+ {
+ node = backup.copy();
+ // TODO: Make sure this works with custom versions as well!
+ DataVersion newVersion = ((DefaultDataVersion) node.getVersion()).increment();
+ node.setVersion(newVersion);
+ }
+ else
+ {
+ throw new CacheException("Detected write skew. Attempting to overwrite
version " + backup.getVersion() + " but current version has progressed to "
+ underlyingNodeVersion);
+ }
+ }
+
+ @Override
+ protected void updateNode(DataContainer dataContainer, NodeFactory nf)
+ {
+ // TODO: Deal with removes and moves
+ if (getFqn().isRoot())
+ {
+ dataContainer.setRoot(nf.createNodeInvocationDelegate(node));
+ }
+ else
+ {
+ NodeSPI parent = dataContainer.peek(getFqn().getParent());
+ parent.addChildDirect(nf.createNodeInvocationDelegate(node));
+ }
+ }
+}
Modified: core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java 2008-06-25
18:29:49 UTC (rev 6045)
+++ core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -1,5 +1,6 @@
package org.jboss.cache.api;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
import org.testng.annotations.Test;
@@ -9,8 +10,10 @@
@Test(groups = "functional")
public class CacheAPIOptimisticTest extends CacheAPITest
{
- public CacheAPIOptimisticTest()
+ @Override
+ protected NodeLockingScheme getNodeLockingScheme()
{
- optimistic = true;
+ return NodeLockingScheme.OPTIMISTIC;
}
+
}
Modified: core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java 2008-06-25 18:29:49 UTC
(rev 6045)
+++ core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java 2008-06-25 18:31:09 UTC
(rev 6046)
@@ -8,6 +8,7 @@
import org.jboss.cache.Node;
import org.jboss.cache.Region;
import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
import org.jboss.cache.config.ConfigurationException;
import org.jboss.cache.notifications.annotation.CacheListener;
import org.jboss.cache.notifications.annotation.NodeCreated;
@@ -35,16 +36,15 @@
public class CacheAPITest
{
private Cache<String, String> cache;
- protected boolean optimistic;
final List<String> events = new ArrayList<String>();
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception
{
// start a single cache instance
- CacheFactory<String, String> cf = new DefaultCacheFactory();
+ CacheFactory<String, String> cf = new DefaultCacheFactory<String,
String>();
cache = cf.createCache("META-INF/conf-test/local-tx-service.xml",
false);
- cache.getConfiguration().setNodeLockingScheme(optimistic ?
Configuration.NodeLockingScheme.OPTIMISTIC :
Configuration.NodeLockingScheme.PESSIMISTIC);
+ cache.getConfiguration().setNodeLockingScheme(getNodeLockingScheme());
cache.start();
events.clear();
}
@@ -55,6 +55,10 @@
if (cache != null) cache.stop();
}
+ protected NodeLockingScheme getNodeLockingScheme()
+ {
+ return NodeLockingScheme.PESSIMISTIC;
+ }
/**
* Tests that the configuration contains the values expected, as well as immutability
of certain elements
Modified: core/trunk/src/test/java/org/jboss/cache/api/NodeAPIOptimisticTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/NodeAPIOptimisticTest.java 2008-06-25
18:29:49 UTC (rev 6045)
+++ core/trunk/src/test/java/org/jboss/cache/api/NodeAPIOptimisticTest.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -1,5 +1,9 @@
package org.jboss.cache.api;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import org.jboss.cache.interceptors.OptimisticNodeInterceptor;
+import org.jboss.cache.interceptors.PessimisticLockInterceptor;
+import org.jboss.cache.interceptors.base.CommandInterceptor;
import org.testng.annotations.Test;
/**
@@ -8,8 +12,22 @@
@Test(groups = "functional")
public class NodeAPIOptimisticTest extends NodeAPITest
{
- public NodeAPIOptimisticTest()
+ protected NodeLockingScheme getNodeLockingScheme()
{
- optimistic = true;
+ return NodeLockingScheme.OPTIMISTIC;
}
+
+ protected void assertNodeLockingScheme()
+ {
+ assert cache.getConfiguration().getNodeLockingScheme() ==
NodeLockingScheme.OPTIMISTIC;
+ boolean interceptorChainOK = false;
+
+ for (CommandInterceptor i : cache.getInterceptorChain())
+ {
+ if (i instanceof PessimisticLockInterceptor) assert false : "Not an
optimistic locking chain!!";
+ if (i instanceof OptimisticNodeInterceptor) interceptorChainOK = true;
+ }
+
+ assert interceptorChainOK : "Not an optimistic locking chain!!";
+ }
}
Modified: core/trunk/src/test/java/org/jboss/cache/api/NodeAPITest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/NodeAPITest.java 2008-06-25 18:29:49 UTC
(rev 6045)
+++ core/trunk/src/test/java/org/jboss/cache/api/NodeAPITest.java 2008-06-25 18:31:09 UTC
(rev 6046)
@@ -4,7 +4,9 @@
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
-import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import static org.jboss.cache.config.Configuration.NodeLockingScheme.OPTIMISTIC;
+import org.jboss.cache.interceptors.MVCCLockingInterceptor;
import org.jboss.cache.interceptors.OptimisticNodeInterceptor;
import org.jboss.cache.interceptors.PessimisticLockInterceptor;
import org.jboss.cache.interceptors.base.CommandInterceptor;
@@ -33,21 +35,19 @@
{
private Node<Object, Object> rootNode;
- private CacheSPI<Object, Object> cache;
+ protected CacheSPI<Object, Object> cache;
private TransactionManager tm;
private static final Fqn<String> A = Fqn.fromString("/a"), B =
Fqn.fromString("/b"), C = Fqn.fromString("/c"), D = Fqn
.fromString("/d");
- protected boolean optimistic = false;
-
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception
{
// start a single cache instance
cache = (CacheSPI<Object, Object>) new
DefaultCacheFactory().createCache("META-INF/conf-test/local-tx-service.xml",
false);
- cache.getConfiguration().setNodeLockingScheme(optimistic ?
Configuration.NodeLockingScheme.OPTIMISTIC :
Configuration.NodeLockingScheme.PESSIMISTIC);
+ cache.getConfiguration().setNodeLockingScheme(getNodeLockingScheme());
cache.start();
rootNode = cache.getRoot();
tm = cache.getTransactionManager();
@@ -77,23 +77,29 @@
}
}
- private void assertOptimistic()
+ protected NodeLockingScheme getNodeLockingScheme()
{
- assert cache.getConfiguration().isNodeLockingOptimistic();
+ return NodeLockingScheme.PESSIMISTIC;
+ }
+
+ protected void assertNodeLockingScheme()
+ {
+ assert cache.getConfiguration().getNodeLockingScheme() ==
NodeLockingScheme.PESSIMISTIC;
boolean interceptorChainOK = false;
for (CommandInterceptor i : cache.getInterceptorChain())
{
- if (i instanceof PessimisticLockInterceptor) assert false : "Not an
optimistic locking chain!!";
- if (i instanceof OptimisticNodeInterceptor) interceptorChainOK = true;
+ if (i instanceof PessimisticLockInterceptor) interceptorChainOK = true;
+ if (i instanceof OptimisticNodeInterceptor) assert false : "Not a
pessimistic locking chain!!";
+ if (i instanceof MVCCLockingInterceptor) assert false : "Not a pessimistic
locking chain!!";
}
- assert interceptorChainOK : "Not an optimistic locking chain!!";
+ assert interceptorChainOK : "Not a pessimistic locking chain!!";
}
public void testAddingData()
{
- if (optimistic) assertOptimistic();
+ assertNodeLockingScheme();
Node<Object, Object> nodeA = rootNode.addChild(A);
nodeA.put("key", "value");
@@ -177,7 +183,7 @@
Node<Object, Object> nodeB = nodeA.addChild(B);
Node<Object, Object> nodeC = nodeB.addChild(C);
- if (!optimistic)
+ if (getNodeLockingScheme() != OPTIMISTIC)
{
assertEquals(3, cache.getNumberOfNodes());
assertEquals(4, cache.getNumberOfLocksHeld());
@@ -187,7 +193,7 @@
tm.begin();
assertEquals(0, cache.getNumberOfLocksHeld());
nodeC.put("key", "value");
- if (!optimistic) assertEquals(4, cache.getNumberOfLocksHeld());
+ if (getNodeLockingScheme() != OPTIMISTIC) assertEquals(4,
cache.getNumberOfLocksHeld());
tm.commit();
}
@@ -289,7 +295,7 @@
cache.put(A_B, "1", "1");
cache.put(A_C, "2", "2");
- if (!optimistic)
+ if (getNodeLockingScheme() != OPTIMISTIC)
{
assertEquals(3, cache.getNumberOfNodes());
assertEquals(4, cache.getNumberOfLocksHeld());
@@ -329,7 +335,8 @@
public void testGetChildAPI()
{
// creates a Node<Object, Object> with fqn /a/b/c
- rootNode.addChild(A).addChild(B).addChild(C);
+ Node childA = rootNode.addChild(A);
+ childA.addChild(B).addChild(C);
rootNode.getChild(A).put("key", "value");
rootNode.getChild(A).getChild(B).put("key", "value");
Copied: core/trunk/src/test/java/org/jboss/cache/api/mvcc/CacheAPIMVCCTest.java (from rev
6034, core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java)
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/mvcc/CacheAPIMVCCTest.java
(rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/api/mvcc/CacheAPIMVCCTest.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -0,0 +1,20 @@
+package org.jboss.cache.api.mvcc;
+
+import org.jboss.cache.api.CacheAPITest;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import org.testng.annotations.Test;
+
+
+/**
+ * MVCC version of {@link org.jboss.cache.api.CacheAPITest}
+ */
+@Test(groups = "functional")
+public class CacheAPIMVCCTest extends CacheAPITest
+{
+ @Override
+ protected NodeLockingScheme getNodeLockingScheme()
+ {
+ return NodeLockingScheme.MVCC;
+ }
+
+}
\ No newline at end of file
Property changes on:
core/trunk/src/test/java/org/jboss/cache/api/mvcc/CacheAPIMVCCTest.java
___________________________________________________________________
Name: svn:keywords
+ Author Date Id Revision
Name: svn:eol-style
+ native
Added:
core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockParentForChildInsertRemoveTest.java
===================================================================
---
core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockParentForChildInsertRemoveTest.java
(rev 0)
+++
core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockParentForChildInsertRemoveTest.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -0,0 +1,12 @@
+package org.jboss.cache.api.mvcc;
+
+import org.testng.annotations.Test;
+
+@Test(groups = "functional")
+public class LockParentForChildInsertRemoveTest extends LockTest
+{
+ public LockParentForChildInsertRemoveTest()
+ {
+ lockParentForInsertRemove = true;
+ }
+}
Added: core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockTest.java
(rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/api/mvcc/LockTest.java 2008-06-25 18:31:09
UTC (rev 6046)
@@ -0,0 +1,153 @@
+package org.jboss.cache.api.mvcc;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.DefaultCacheFactory;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.config.Configuration.CacheMode;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
+import org.jboss.cache.invocation.InvocationContextContainer;
+import org.jboss.cache.lock.LockManager;
+import org.jboss.cache.lock.MVCCLockManager.LockContainer;
+import org.jboss.cache.transaction.DummyTransactionManagerLookup;
+import org.jboss.cache.util.TestingUtil;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import javax.transaction.TransactionManager;
+import java.util.Collections;
+
+/**
+ * @author Manik Surtani (<a
href="mailto:manik@jboss.org">manik@jboss.org</a>)
+ * @since 3.0
+ */
+@Test(groups = "functional")
+public class LockTest
+{
+ Cache<String, String> cache;
+ TransactionManager tm;
+ Fqn A = Fqn.fromString("/a");
+ Fqn AB = Fqn.fromString("/a/b");
+ Fqn ABC = Fqn.fromString("/a/b/c");
+ LockManager lockManager;
+ InvocationContextContainer icc;
+ boolean lockParentForInsertRemove = false;
+
+ @BeforeMethod
+ public void setUp()
+ {
+ cache = new DefaultCacheFactory<String,
String>().createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.LOCAL),
false);
+ cache.getConfiguration().setNodeLockingScheme(NodeLockingScheme.MVCC);
+
cache.getConfiguration().setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName());
+
cache.getConfiguration().setLockParentForChildInsertRemove(lockParentForInsertRemove);
+ cache.start();
+ lockManager =
TestingUtil.extractComponentRegistry(cache).getComponent(LockManager.class);
+ icc =
TestingUtil.extractComponentRegistry(cache).getComponent(InvocationContextContainer.class);
+ tm =
TestingUtil.extractComponentRegistry(cache).getComponent(TransactionManager.class);
+ }
+
+ @AfterMethod
+ public void tearDown()
+ {
+ TestingUtil.killCaches(cache);
+ }
+
+ private void assertLocked(Fqn fqn)
+ {
+ assert lockManager.isLocked(fqn) : fqn + " not locked!";
+ assert icc.get().getLocks().contains(fqn) : "Lock not recorded for " +
fqn;
+ }
+
+ private void assertNotLocked(Fqn fqn)
+ {
+ // can't rely on the negative test since other nodes may share the same lock
with lock striping.
+// assert !lockManager.isLocked(fqn) : fqn + " is locked!";
+ assert !icc.get().getLocks().contains(fqn) : fqn + " lock recorded!";
+ }
+
+ private void assertNoLocks()
+ {
+ LockContainer lc = (LockContainer) TestingUtil.extractField(lockManager,
"lockContainer");
+ assert lc.getNumLocksHeld() == 0 : "Stale locks exist!" +
lockManager.printLockInfo();
+ assert icc.get().getLocks().isEmpty() : "Stale (?) locks recorded! " +
icc.get().getLocks();
+ }
+
+ public void testLocksOnPutKeyVal() throws Exception
+ {
+ tm.begin();
+ cache.put(AB, "k", "v");
+ if (lockParentForInsertRemove)
+ assertLocked(Fqn.ROOT);
+ else
+ assertNotLocked(Fqn.ROOT);
+ assertLocked(A);
+ assertLocked(AB);
+ assertNotLocked(ABC);
+ tm.commit();
+
+ assertNoLocks();
+
+ tm.begin();
+ assert cache.get(AB, "k").equals("v");
+ assertNotLocked(Fqn.ROOT);
+ assertNotLocked(A);
+ assertNotLocked(AB);
+ assertNotLocked(ABC);
+ tm.commit();
+
+ assertNoLocks();
+
+ tm.begin();
+ cache.put(ABC, "k", "v");
+ assertNotLocked(Fqn.ROOT);
+ assertNotLocked(A);
+ if (lockParentForInsertRemove)
+ assertLocked(AB);
+ else
+ assertNotLocked(AB);
+ assertLocked(ABC);
+ tm.commit();
+
+ assertNoLocks();
+ }
+
+ public void testLocksOnPutData() throws Exception
+ {
+ tm.begin();
+ cache.put(AB, Collections.singletonMap("k", "v"));
+ if (lockParentForInsertRemove)
+ assertLocked(Fqn.ROOT);
+ else
+ assertNotLocked(Fqn.ROOT);
+ assertLocked(A);
+ assertLocked(AB);
+ assertNotLocked(ABC);
+ tm.commit();
+
+ assertNoLocks();
+
+ tm.begin();
+ assert cache.get(AB, "k").equals("v");
+ assertNotLocked(Fqn.ROOT);
+ assertNotLocked(A);
+ assertNotLocked(AB);
+ assertNotLocked(ABC);
+ tm.commit();
+
+ assertNoLocks();
+
+ tm.begin();
+ cache.put(ABC, Collections.singletonMap("k", "v"));
+ assertNotLocked(Fqn.ROOT);
+ assertNotLocked(A);
+ if (lockParentForInsertRemove)
+ assertLocked(AB);
+ else
+ assertNotLocked(AB);
+ assertLocked(ABC);
+ tm.commit();
+
+ assertNoLocks();
+ }
+}
Copied: core/trunk/src/test/java/org/jboss/cache/api/mvcc/NodeAPIMVCCTest.java (from rev
6034, core/trunk/src/test/java/org/jboss/cache/api/NodeAPIOptimisticTest.java)
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/mvcc/NodeAPIMVCCTest.java
(rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/api/mvcc/NodeAPIMVCCTest.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -0,0 +1,37 @@
+package org.jboss.cache.api.mvcc;
+
+import org.jboss.cache.api.NodeAPITest;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import org.jboss.cache.interceptors.MVCCLockingInterceptor;
+import org.jboss.cache.interceptors.OptimisticNodeInterceptor;
+import org.jboss.cache.interceptors.PessimisticLockInterceptor;
+import org.jboss.cache.interceptors.base.CommandInterceptor;
+import org.testng.annotations.Test;
+
+/**
+ * An MVCC version of {@link org.jboss.cache.api.NodeAPITest}
+ */
+@Test(groups = "functional")
+public class NodeAPIMVCCTest extends NodeAPITest
+{
+ protected NodeLockingScheme getNodeLockingScheme()
+ {
+ return NodeLockingScheme.MVCC;
+ }
+
+ protected void assertNodeLockingScheme()
+ {
+ assert cache.getConfiguration().getNodeLockingScheme() == NodeLockingScheme.MVCC;
+ boolean interceptorChainOK = false;
+
+ for (CommandInterceptor i : cache.getInterceptorChain())
+ {
+ if (i instanceof PessimisticLockInterceptor) assert false : "Not an MVCC
locking chain!!";
+ if (i instanceof OptimisticNodeInterceptor) assert false : "Not an MVCC
locking chain!!";
+ if (i instanceof MVCCLockingInterceptor) interceptorChainOK = true;
+
+ }
+
+ assert interceptorChainOK : "Not an MVCC locking chain!!";
+ }
+}
\ No newline at end of file
Added: core/trunk/src/test/java/org/jboss/cache/mvcc/MVCCFullStackTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/mvcc/MVCCFullStackTest.java
(rev 0)
+++ core/trunk/src/test/java/org/jboss/cache/mvcc/MVCCFullStackTest.java 2008-06-25
18:31:09 UTC (rev 6046)
@@ -0,0 +1,33 @@
+package org.jboss.cache.mvcc;
+
+import org.jboss.cache.CacheSPI;
+import org.jboss.cache.DefaultCacheFactory;
+import org.jboss.cache.config.Configuration.NodeLockingScheme;
+import org.jboss.cache.interceptors.MVCCLockingInterceptor;
+import org.jboss.cache.util.TestingUtil;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+/**
+ * @author Manik Surtani (<a
href="mailto:manik@jboss.org">manik@jboss.org</a>)
+ * @since 3.0
+ */
+@Test(groups = "functional")
+public class MVCCFullStackTest
+{
+ CacheSPI<Object, Object> cache;
+
+ @AfterMethod
+ public void tearDown()
+ {
+ TestingUtil.killCaches(cache);
+ }
+
+ public void testDefaultConfiguration()
+ {
+ cache = (CacheSPI<Object, Object>) new DefaultCacheFactory<Object,
Object>().createCache();
+
+ assert TestingUtil.findInterceptor(cache, MVCCLockingInterceptor.class) != null :
"MVCC interceptor should be in stack";
+ assert cache.getConfiguration().getNodeLockingScheme() == NodeLockingScheme.MVCC;
+ }
+}