Author: mreutegg
Date: Mon Apr 14 15:26:38 2014
New Revision: 1587224

URL: http://svn.apache.org/r1587224
Log:
OAK-1729: DocumentNodeStore revision GC removes intermediate docs

Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreFixture.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorTest.java

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java?rev=1587224&r1=1587223&r2=1587224&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
 Mon Apr 14 15:26:38 2014
@@ -51,10 +51,12 @@ import org.slf4j.LoggerFactory;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.Iterables.filter;
 import static com.google.common.collect.Iterables.transform;
+import static java.util.Collections.disjoint;
 import static org.apache.jackrabbit.oak.plugins.document.UpdateOp.Key;
 import static org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation;
 
@@ -1339,7 +1341,7 @@ public final class NodeDocument extends 
         setSplitDocMaxRev(old, maxRev);
 
         SplitDocType type = SplitDocType.DEFAULT;
-        if(!mainDoc.hasChildren()){
+        if(!mainDoc.hasChildren() && !referencesOldDocAfterSplit(mainDoc, 
oldDoc)){
             type = SplitDocType.DEFAULT_NO_CHILD;
         } else if (oldDoc.getLocalRevisions().isEmpty()){
             type = SplitDocType.PROP_COMMIT_ONLY;
@@ -1354,6 +1356,31 @@ public final class NodeDocument extends 
     }
 
     /**
+     * Checks if the main document has changes referencing {@code oldDoc} after
+     * the split.
+     *
+     * @param mainDoc the main document before the split.
+     * @param oldDoc  the old document created by the split.
+     * @return {@code true} if the main document contains references to the
+     *         old document after the split; {@code false} otherwise.
+     */
+    private static boolean referencesOldDocAfterSplit(NodeDocument mainDoc,
+                                                      NodeDocument oldDoc) {
+        Set<Revision> revs = oldDoc.getLocalRevisions().keySet();
+        for (String property : mainDoc.data.keySet()) {
+            if (IGNORE_ON_SPLIT.contains(property)) {
+                continue;
+            }
+            Set<Revision> changes = 
Sets.newHashSet(mainDoc.getLocalMap(property).keySet());
+            changes.removeAll(oldDoc.getLocalMap(property).keySet());
+            if (!disjoint(changes, revs)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Set various properties for intermediate split document
      *
      * @param intermediate updateOp of the intermediate doc getting created

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java?rev=1587224&r1=1587223&r2=1587224&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
 Mon Apr 14 15:26:38 2014
@@ -46,8 +46,7 @@ public class VersionGarbageCollector {
      */
     private static final Set<NodeDocument.SplitDocType> GC_TYPES = EnumSet.of(
             NodeDocument.SplitDocType.DEFAULT_NO_CHILD,
-            NodeDocument.SplitDocType.PROP_COMMIT_ONLY,
-            NodeDocument.SplitDocType.INTERMEDIATE);
+            NodeDocument.SplitDocType.PROP_COMMIT_ONLY);
 
 
     VersionGarbageCollector(DocumentNodeStore nodeStore) {

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreFixture.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreFixture.java?rev=1587224&r1=1587223&r2=1587224&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreFixture.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreFixture.java
 Mon Apr 14 15:26:38 2014
@@ -51,8 +51,6 @@ public abstract class DocumentStoreFixtu
 
     public static class MemoryFixture extends DocumentStoreFixture {
 
-        DocumentStore ds = new MemoryDocumentStore();
-
         @Override
         public String getName() {
             return "Memory";
@@ -60,7 +58,7 @@ public abstract class DocumentStoreFixtu
 
         @Override
         public DocumentStore createDocumentStore() {
-            return ds;
+            return new MemoryDocumentStore();
         }
     }
 
@@ -75,7 +73,7 @@ public abstract class DocumentStoreFixtu
                 DataSource datas = RDBDataSourceFactory.forJdbcUrl(url, 
username, passwd);
                 this.ds = new RDBDocumentStore(datas, new 
DocumentMK.Builder());
             } catch (Exception ex) {
-                LOG.info("Database instance not available at " + url + ", 
skipping tests...", ex);
+                LOG.info("Database instance not available at " + url + ", 
skipping tests...");
             }
         }
 

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorTest.java?rev=1587224&r1=1587223&r2=1587224&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorTest.java
 Mon Apr 14 15:26:38 2014
@@ -20,15 +20,19 @@
 package org.apache.jackrabbit.oak.plugins.document;
 
 import java.io.IOException;
-import java.util.*;
 import java.util.Collection;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
-import static org.apache.jackrabbit.oak.plugins.document.Collection.*;
+import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
+import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.NUM_REVS_THRESHOLD;
+import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.PREV_SPLIT_FACTOR;
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.SplitDocType;
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.VersionGCStats;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -206,6 +210,53 @@ public class VersionGarbageCollectorTest
         
//assertTrue(ImmutableList.copyOf(getDoc("/test2/foo").getAllPreviousDocs()).isEmpty());
     }
 
+    // OAK-1729
+    @Test
+    public void gcIntermediateDocs() throws Exception {
+        long maxAge = 1; //hrs
+        long delta = TimeUnit.MINUTES.toMillis(10);
+
+        NodeBuilder b1 = store.getRoot().builder();
+        // adding the foo node will cause the commit root to be placed
+        // on the rood document, because the children flag is set on the
+        // root document
+        b1.child("foo");
+        store.merge(b1, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+        // adding test afterwards will use the new test document as the
+        // commit root. this what we want for the test.
+        b1.child("test");
+        store.merge(b1, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+        assertTrue(!getDoc("/test").getLocalRevisions().isEmpty());
+
+        for (int i = 0; i < PREV_SPLIT_FACTOR + 1; i++) {
+            for (int j = 0; j < NUM_REVS_THRESHOLD; j++) {
+                b1 = store.getRoot().builder();
+                b1.child("test").setProperty("prop", i * NUM_REVS_THRESHOLD + 
j);
+                store.merge(b1, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+            }
+            store.runBackgroundOperations();
+        }
+
+        Map<Revision, Range> prevRanges = getDoc("/test").getPreviousRanges();
+        boolean hasIntermediateDoc = false;
+        for (Map.Entry<Revision, Range> entry : prevRanges.entrySet()) {
+            if (entry.getValue().getHeight() > 0) {
+                hasIntermediateDoc = true;
+                break;
+            }
+        }
+        assertTrue("Test data does not have intermediate previous docs",
+                hasIntermediateDoc);
+
+        clock.waitUntil(clock.getTime() + TimeUnit.HOURS.toMillis(maxAge) + 
delta);
+        VersionGCStats stats = gc.gc(maxAge, TimeUnit.HOURS);
+        assertEquals(10, stats.splitDocGCCount);
+
+        DocumentNodeState test = getDoc("/test").getNodeAtRevision(
+                store, store.getHeadRevision(), null);
+        assertNotNull(test);
+    }
+
     private NodeDocument getDoc(String path){
         return store.getDocumentStore().find(NODES, Utils.getIdFromPath(path), 
0);
     }


Reply via email to