Author: trang_vu
Date: 2011-08-18 04:47:38 -0400 (Thu, 18 Aug 2011)
New Revision: 4774
Added:
jcr/branches/1.12.x/patch/1.12.10-GA/JCR-1650/readme.txt
Modified:
jcr/branches/1.12.x/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/BufferedJBossCache.java
jcr/branches/1.12.x/exo.jcr.component.core/src/test/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/TestJBossCacheWorkspaceStorageCacheInClusterMode.java
Log:
JCR-1650: Lists stored into the cache can be inconsistent in cluster environment
Fix description
* Invalidate the list of child nodes on all cluster nodes when a new child has been added
and the local cache has now the list of child nodes.
Modified:
jcr/branches/1.12.x/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/BufferedJBossCache.java
===================================================================
---
jcr/branches/1.12.x/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/BufferedJBossCache.java 2011-08-18
08:09:15 UTC (rev 4773)
+++
jcr/branches/1.12.x/exo.jcr.component.core/src/main/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/BufferedJBossCache.java 2011-08-18
08:47:38 UTC (rev 4774)
@@ -30,6 +30,7 @@
import org.jboss.cache.NodeNotExistsException;
import org.jboss.cache.Region;
import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.eviction.ExpirationAlgorithmConfig;
import org.jboss.cache.interceptors.base.CommandInterceptor;
import org.jgroups.Address;
@@ -993,6 +994,12 @@
LOG.error("Unexpected object found by FQN:" + getFqn() + " and
key:" + key + ". Expected Set, but found:"
+ existingObject.getClass().getName());
}
+ else if (!localMode && cache.getConfiguration().getCacheMode() !=
CacheMode.LOCAL)
+ {
+ // to prevent consistency issue since we don't have the list in the local
cache, we are in cluster env
+ // and we are in a non local mode, we clear the list in order to enforce
other cluster nodes to reload it from the db
+ cache.put(fqn, key, null);
+ }
}
@Override
@@ -1022,6 +1029,13 @@
@Override
public void apply()
{
+ if (!localMode && cache.getConfiguration().getCacheMode() !=
CacheMode.LOCAL)
+ {
+ // to prevent consistency issue since we don't have the list in the local
cache, we are in cluster env
+ // and we are in a non local mode, we remove all the patterns in order to
enforce other cluster nodes to reload them from the db
+ cache.removeNode(fqn);
+ return;
+ }
// force writeLock on next read
cache.getInvocationContext().getOptionOverrides().setForceWriteLock(true);
// object found by FQN and key;
Modified:
jcr/branches/1.12.x/exo.jcr.component.core/src/test/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/TestJBossCacheWorkspaceStorageCacheInClusterMode.java
===================================================================
---
jcr/branches/1.12.x/exo.jcr.component.core/src/test/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/TestJBossCacheWorkspaceStorageCacheInClusterMode.java 2011-08-18
08:09:15 UTC (rev 4773)
+++
jcr/branches/1.12.x/exo.jcr.component.core/src/test/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache/TestJBossCacheWorkspaceStorageCacheInClusterMode.java 2011-08-18
08:47:38 UTC (rev 4774)
@@ -80,12 +80,12 @@
transactionService == null ? null : transactionService, new
ConfigurationManagerImpl());
}
- public void testRaceConditions() throws Exception
+ public void testRaceConditionsNConsistency() throws Exception
{
JBossCacheWorkspaceStorageCache cache1 = null, cache2 = null;
try
{
- MyWorkspaceStorageConnection con = new MyWorkspaceStorageConnection();
+ MyWorkspaceSC con = new MyWorkspaceSC();
WorkspaceDataContainer wdc = new MyWorkspaceDataContainer(con);
CacheableWorkspaceDataManager cwdmNode1 =
new CacheableWorkspaceDataManager(wdc, cache1 = getCacheImpl(), new
SystemDataContainerHolder(wdc));
@@ -273,6 +273,106 @@
assertEquals(2, cwdmNode1.getItemData(parentNode, qpe,
ItemType.PROPERTY).getPersistedVersion());
assertNotNull(cwdmNode2.getItemData(parentNode, qpe, ItemType.PROPERTY));
assertEquals(2, cwdmNode2.getItemData(parentNode, qpe,
ItemType.PROPERTY).getPersistedVersion());
+
+ // testConsistency
+ con = new MyWorkspaceSC(true);
+ wdc = new MyWorkspaceDataContainer(con);
+ cwdmNode1 = new CacheableWorkspaceDataManager(wdc, cache1, new
SystemDataContainerHolder(wdc));
+ cwdmNode2 = new CacheableWorkspaceDataManager(wdc, cache2, new
SystemDataContainerHolder(wdc));
+ parentNode =
+ new PersistedNodeData("parent2-id",
QPath.makeChildPath(Constants.ROOT_PATH, new InternalQName(null,
+ "parent2-node")), Constants.ROOT_UUID, 1, 0,
Constants.NT_UNSTRUCTURED, new InternalQName[0], null);
+
+ // Test getChildNodesData
+ con.setParentNode(parentNode);
+ cwdmNode2.getChildNodesData(parentNode);
+ PlainChangesLog chlog = new PlainChangesLogImpl();
+ chlog.add(ItemState.createAddedState(new PersistedNodeData("id-node" +
parentNode.getIdentifier(), QPath
+ .makeChildPath(parentNode.getQPath(), new InternalQName(null,
"node")), parentNode.getIdentifier(), 1, 0,
+ Constants.NT_UNSTRUCTURED, new InternalQName[0], null)));
+ cwdmNode1.save(chlog);
+ assertNotNull(cwdmNode1.getChildNodesData(parentNode));
+ assertEquals(2, cwdmNode1.getChildNodesData(parentNode).size());
+ assertNotNull(cwdmNode2.getChildNodesData(parentNode));
+ assertEquals(2, cwdmNode2.getChildNodesData(parentNode).size());
+ parentNode =
+ new PersistedNodeData("parent2-id2",
QPath.makeChildPath(Constants.ROOT_PATH, new InternalQName(null,
+ "parent2-node2")), Constants.ROOT_UUID, 1, 0,
Constants.NT_UNSTRUCTURED, new InternalQName[0], null);
+ con.setParentNode(parentNode);
+ cwdmNode2.getChildNodesData(parentNode);
+ chlog = new PlainChangesLogImpl();
+ chlog.add(ItemState.createDeletedState(new
PersistedNodeData("id-node2" + parentNode.getIdentifier(), QPath
+ .makeChildPath(parentNode.getQPath(), new InternalQName(null,
"node2")), parentNode.getIdentifier(), 1, 0,
+ Constants.NT_UNSTRUCTURED, new InternalQName[0], null)));
+ cwdmNode1.save(chlog);
+ assertNotNull(cwdmNode1.getChildNodesData(parentNode));
+ assertEquals(0, cwdmNode1.getChildNodesData(parentNode).size());
+ assertNotNull(cwdmNode2.getChildNodesData(parentNode));
+ assertEquals(0, cwdmNode2.getChildNodesData(parentNode).size());
+
+ // Test getChildPropertiesData
+ parentNode =
+ new PersistedNodeData("parent2-id3",
QPath.makeChildPath(Constants.ROOT_PATH, new InternalQName(null,
+ "parent2-node3")), Constants.ROOT_UUID, 1, 0,
Constants.NT_UNSTRUCTURED, new InternalQName[0], null);
+ con.setParentNode(parentNode);
+ cwdmNode2.getChildPropertiesData(parentNode);
+ chlog = new PlainChangesLogImpl();
+ chlog.add(ItemState.createAddedState(new
PersistedPropertyData("id-property" + parentNode.getIdentifier(),
+ QPath.makeChildPath(parentNode.getQPath(), new InternalQName(null,
"property")),
+ parentNode.getIdentifier(), 0, PropertyType.STRING, false, Arrays
+ .asList((ValueData)new ByteArrayPersistedValueData(0, "some
data".getBytes("UTF-8"))))));
+ cwdmNode1.save(chlog);
+ assertNotNull(cwdmNode1.getChildPropertiesData(parentNode));
+ assertEquals(2, cwdmNode1.getChildPropertiesData(parentNode).size());
+ assertNotNull(cwdmNode2.getChildPropertiesData(parentNode));
+ assertEquals(2, cwdmNode2.getChildPropertiesData(parentNode).size());
+ parentNode =
+ new PersistedNodeData("parent2-id4",
QPath.makeChildPath(Constants.ROOT_PATH, new InternalQName(null,
+ "parent2-node4")), Constants.ROOT_UUID, 1, 0,
Constants.NT_UNSTRUCTURED, new InternalQName[0], null);
+ con.setParentNode(parentNode);
+ cwdmNode2.getChildPropertiesData(parentNode);
+ chlog = new PlainChangesLogImpl();
+ chlog.add(ItemState.createDeletedState(new
PersistedPropertyData("id-property2" + parentNode.getIdentifier(),
+ QPath.makeChildPath(parentNode.getQPath(), new InternalQName(null,
"property2")), parentNode
+ .getIdentifier(), 0, PropertyType.STRING, false, Arrays
+ .asList((ValueData)new ByteArrayPersistedValueData(0, "some
data".getBytes("UTF-8"))))));
+ cwdmNode1.save(chlog);
+ assertNotNull(cwdmNode1.getChildPropertiesData(parentNode));
+ assertEquals(0, cwdmNode1.getChildPropertiesData(parentNode).size());
+ assertNotNull(cwdmNode2.getChildPropertiesData(parentNode));
+ assertEquals(0, cwdmNode2.getChildPropertiesData(parentNode).size());
+
+ // Test getReferencesData
+ parentNode =
+ new PersistedNodeData("parent2-id5",
QPath.makeChildPath(Constants.ROOT_PATH, new InternalQName(null,
+ "parent2-node5")), Constants.ROOT_UUID, 1, 0,
Constants.NT_UNSTRUCTURED, new InternalQName[0], null);
+ con.setParentNode(parentNode);
+ cwdmNode2.getReferencesData(parentNode.getIdentifier(), false);
+ chlog = new PlainChangesLogImpl();
+ chlog.add(ItemState.createAddedState(new
PersistedPropertyData("id-reference" + parentNode.getIdentifier(),
+ QPath.makeChildPath(parentNode.getQPath(), new InternalQName(null,
"reference")), parentNode
+ .getIdentifier(), 0, PropertyType.REFERENCE, false, Arrays
+ .asList((ValueData)new ByteArrayPersistedValueData(0,
parentNode.getIdentifier().getBytes("UTF-8"))))));
+ cwdmNode1.save(chlog);
+ assertNotNull(cwdmNode1.getReferencesData(parentNode.getIdentifier(), false));
+ assertEquals(2, cwdmNode1.getReferencesData(parentNode.getIdentifier(),
false).size());
+ assertNotNull(cwdmNode2.getReferencesData(parentNode.getIdentifier(), false));
+ assertEquals(2, cwdmNode2.getReferencesData(parentNode.getIdentifier(),
false).size());
+ parentNode =
+ new PersistedNodeData("parent2-id6",
QPath.makeChildPath(Constants.ROOT_PATH, new InternalQName(null,
+ "parent2-node6")), Constants.ROOT_UUID, 1, 0,
Constants.NT_UNSTRUCTURED, new InternalQName[0], null);
+ con.setParentNode(parentNode);
+ cwdmNode2.getReferencesData(parentNode.getIdentifier(), false);
+ chlog = new PlainChangesLogImpl();
+ chlog.add(ItemState.createDeletedState(new
PersistedPropertyData("id-reference2" + parentNode.getIdentifier(),
+ QPath.makeChildPath(parentNode.getQPath(), new InternalQName(null,
"reference2")), parentNode
+ .getIdentifier(), 0, PropertyType.REFERENCE, false, Arrays
+ .asList((ValueData)new ByteArrayPersistedValueData(0,
parentNode.getIdentifier().getBytes("UTF-8"))))));
+ cwdmNode1.save(chlog);
+ assertNotNull(cwdmNode1.getReferencesData(parentNode.getIdentifier(), false));
+ assertEquals(0, cwdmNode1.getReferencesData(parentNode.getIdentifier(),
false).size());
+ assertNotNull(cwdmNode2.getReferencesData(parentNode.getIdentifier(), false));
+ assertEquals(0, cwdmNode2.getReferencesData(parentNode.getIdentifier(),
false).size());
}
finally
{
@@ -308,7 +408,7 @@
* @param idNode
* @throws InterruptedException
*/
- private void executeConcurrentReadNWrite(final MyWorkspaceStorageConnection con, final
Action readAction,
+ private void executeConcurrentReadNWrite(final MyWorkspaceSC con, final Action
readAction,
final Action writeAction, final Mode mode, final NodeData parentNode) throws
InterruptedException
{
final CountDownLatch goSignal = con.initCountDownLatch();
@@ -336,6 +436,8 @@
{
if (mode == Mode.WRITE_FIRST) goSignal.countDown();
doneSignal.countDown();
+
+ con.wait.remove();
}
}
};
@@ -360,6 +462,8 @@
{
if (mode == Mode.READ_FIRST) goSignal.countDown();
doneSignal.countDown();
+
+ con.wait.remove();
}
}
};
@@ -373,22 +477,50 @@
{
protected final CacheableWorkspaceDataManager cwdm;
+
public Action(CacheableWorkspaceDataManager cwdm)
{
this.cwdm = cwdm;
}
protected abstract void execute(NodeData parentNode) throws Exception;
}
+
private static enum Mode
{
READ_FIRST, WRITE_FIRST;
}
- private static class MyWorkspaceStorageConnection implements
WorkspaceStorageConnection
+
+ public static class MyWorkspaceSC implements WorkspaceStorageConnection
{
public ThreadLocal<Boolean> wait = new ThreadLocal<Boolean>();
+
private NodeData parentNode;
+
private CountDownLatch goSignal;
+ private ItemData itemAdded;
+
+ private boolean canModify;
+
+ private boolean itemDeleted;
+
+ public MyWorkspaceSC()
+ {
+ }
+
+ public MyWorkspaceSC(boolean canModify)
+ {
+ this.canModify = canModify;
+ }
+
+ /**
+ * @param canModify the canModify to set
+ */
+ public void setCanModify(boolean canModify)
+ {
+ this.canModify = canModify;
+ }
+
public CountDownLatch initCountDownLatch()
{
return this.goSignal = new CountDownLatch(1);
@@ -397,16 +529,20 @@
public void setParentNode(NodeData parentNode)
{
this.parentNode = parentNode;
+ this.itemAdded = null;
+ this.itemDeleted = false;
}
public void add(NodeData data) throws RepositoryException,
UnsupportedOperationException,
InvalidItemStateException, IllegalStateException
{
+ this.itemAdded = data;
}
public void add(PropertyData data) throws RepositoryException,
UnsupportedOperationException,
InvalidItemStateException, IllegalStateException
{
+ this.itemAdded = data;
}
public void close() throws IllegalStateException, RepositoryException
@@ -431,11 +567,13 @@
public void delete(NodeData data) throws RepositoryException,
UnsupportedOperationException,
InvalidItemStateException, IllegalStateException
{
+ this.itemDeleted = true;
}
public void delete(PropertyData data) throws RepositoryException,
UnsupportedOperationException,
InvalidItemStateException, IllegalStateException
{
+ this.itemDeleted = true;
}
public int getChildNodesCount(NodeData parent) throws RepositoryException
@@ -457,8 +595,17 @@
}
}
List<NodeData> children = new ArrayList<NodeData>();
- children.add(new PersistedNodeData("id-node2" +
parentNode.getIdentifier(), QPath.makeChildPath(parent.getQPath(), new InternalQName(null,
"node2")), parent.getIdentifier(), 1, 0,
- Constants.NT_UNSTRUCTURED, new InternalQName[0], null));
+ if (!canModify || !itemDeleted)
+ {
+ children.add(new PersistedNodeData("id-node2" +
parentNode.getIdentifier(), QPath.makeChildPath(
+ parent.getQPath(), new InternalQName(null, "node2")),
parent.getIdentifier(), 1, 0,
+ Constants.NT_UNSTRUCTURED, new InternalQName[0], null));
+ }
+ if (canModify && itemAdded != null)
+ {
+ children.add((NodeData)itemAdded);
+ }
+
return children;
}
@@ -479,9 +626,17 @@
List<PropertyData> children = new ArrayList<PropertyData>();
try
{
- children.add(new PersistedPropertyData("id-property2" +
parentNode.getIdentifier(), QPath.makeChildPath(
- parentNode.getQPath(), new InternalQName(null, "property2")),
parentNode.getIdentifier(), 0,
- PropertyType.STRING, false, Arrays.asList((ValueData)new
ByteArrayPersistedValueData(0, "some data".getBytes("UTF-8")))));
+ if (!canModify || !itemDeleted)
+ {
+ children.add(new PersistedPropertyData("id-property2" +
parentNode.getIdentifier(), QPath.makeChildPath(
+ parentNode.getQPath(), new InternalQName(null, "property2")),
parentNode.getIdentifier(), 0,
+ PropertyType.STRING, false, Arrays.asList((ValueData)new
ByteArrayPersistedValueData(0, "some data"
+ .getBytes("UTF-8")))));
+ }
+ if (canModify && itemAdded != null)
+ {
+ children.add((PropertyData)itemAdded);
+ }
}
catch (UnsupportedEncodingException e)
{
@@ -565,9 +720,18 @@
List<PropertyData> children = new ArrayList<PropertyData>();
try
{
- children.add(new PersistedPropertyData("id-reference2" +
parentNode.getIdentifier(), QPath.makeChildPath(
- parentNode.getQPath(), new InternalQName(null, "reference2")),
parentNode.getIdentifier(), 0,
- PropertyType.REFERENCE, false, Arrays.asList((ValueData)new
ByteArrayPersistedValueData(0,
parentNode.getIdentifier().getBytes("UTF-8")))));
+ if (!canModify || !itemDeleted)
+ {
+ children
+ .add(new PersistedPropertyData("id-reference2" +
parentNode.getIdentifier(), QPath.makeChildPath(
+ parentNode.getQPath(), new InternalQName(null,
"reference2")), parentNode.getIdentifier(), 0,
+ PropertyType.REFERENCE, false, Arrays.asList((ValueData)new
ByteArrayPersistedValueData(0,
+ parentNode.getIdentifier().getBytes("UTF-8")))));
+ }
+ if (canModify && itemAdded != null)
+ {
+ children.add((PropertyData)itemAdded);
+ }
}
catch (UnsupportedEncodingException e)
{
Added: jcr/branches/1.12.x/patch/1.12.10-GA/JCR-1650/readme.txt
===================================================================
--- jcr/branches/1.12.x/patch/1.12.10-GA/JCR-1650/readme.txt (rev
0)
+++ jcr/branches/1.12.x/patch/1.12.10-GA/JCR-1650/readme.txt 2011-08-18 08:47:38 UTC (rev
4774)
@@ -0,0 +1,71 @@
+Summary
+
+ * Status: Lists stored into the cache can be inconsistent in cluster environment
+ * CCP Issue: N/A Product Jira Issue: JCR-1650.
+ * Complexity: Low
+
+The Proposal
+Problem description
+
+What is the problem to fix?
+In cluster environment for example if:
+1. we have the list of all the children nodes of a given JCR node in the cluster node 1
+2. this list has not been loaded in the cluster node 2
+3. and we add a new child node in the cluster node 2
+
+In this kind of usecase, since the list has not been loaded locally, we don't change
the list content which has for consequences that the cluster node 1 doesn't have this
new node in its list which is a consistency issue.
+Fix description
+
+How is the problem fixed?
+
+ * Invalidate the list of child nodes on all cluster nodes when a new child has been
added and the local cache has now the list of child nodes
+
+Patch file: JCR-1650.patch
+
+Tests to perform
+
+Reproduction test
+
+ * TestWorkspaceStorageCacheInClusterMode.java
+
+Tests performed at DevLevel
+
+ * functional testing jcr-core project
+
+Tests performed at QA/Support Level
+*
+Documentation changes
+
+Documentation changes:
+ * No
+
+Configuration changes
+
+Configuration changes:
+
+ * No
+
+Will previous configuration continue to work?
+
+ * Yes
+
+Risks and impacts
+
+Can this bug fix have any side effects on current client projects?
+
+ * No
+
+Is there a performance risk/cost?
+
+ * After invalidation all cluster nodes are supposed to reload the list of child nodes
if need
+
+Validation (PM/Support/QA)
+
+PM Comment
+* Patch approved.
+
+Support Comment
+*
+
+QA Feedbacks
+*