[exo-jcr-commits] exo-jcr SVN: r4774 - in jcr/branches/1.12.x: exo.jcr.component.core/src/test/java/org/exoplatform/services/jcr/impl/dataflow/persistent/jbosscache and 1 other directories.

do-not-reply at jboss.org do-not-reply at jboss.org
Thu Aug 18 04:47:39 EDT 2011


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
+*



More information about the exo-jcr-commits mailing list