Author: mreutegg
Date: Tue Apr 18 08:33:51 2017
New Revision: 1791762

URL: http://svn.apache.org/viewvc?rev=1791762&view=rev
Log:
OAK-3712: Clean up old and uncommitted changes

Consider sweep revision when resolving a commit value

Added:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolver.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolverTest.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentTest.java

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolver.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolver.java?rev=1791762&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolver.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolver.java
 Tue Apr 18 08:33:51 2017
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.document;
+
+import java.util.List;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+import com.google.common.base.Supplier;
+import com.google.common.cache.Cache;
+
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+
+import static com.google.common.cache.CacheBuilder.newBuilder;
+import static com.google.common.collect.ImmutableList.of;
+
+/**
+ * Resolves the commit value for a given change revision on a document.
+ */
+final class CommitValueResolver {
+
+    private static final List<String> COMMIT_ROOT_OR_REVISIONS
+            = of(NodeDocument.COMMIT_ROOT, NodeDocument.REVISIONS);
+
+    private final Cache<Revision, String> commitValueCache;
+
+    private final Supplier<RevisionVector> sweepRevisions;
+
+    CommitValueResolver(int cacheSize, Supplier<RevisionVector> 
sweepRevisions) {
+        this.commitValueCache = newBuilder().maximumSize(cacheSize).build();
+        this.sweepRevisions = sweepRevisions;
+    }
+
+    String resolve(@Nonnull Revision changeRevision,
+                   @Nonnull NodeDocument doc) {
+        // check cache first
+        String value = commitValueCache.getIfPresent(changeRevision);
+        if (value != null) {
+            return value;
+        }
+        // need to determine the commit value
+        doc = resolveDocument(doc, changeRevision);
+        if (doc == null) {
+            // the document including its history does not contain a change
+            // for the given revision
+            return null;
+        }
+        // at this point 'doc' is guaranteed to have a local entry
+        // for the given change revision
+        if (sweepRevisions.get().isRevisionNewer(changeRevision)) {
+            // change revision is newer than sweep revision
+            // resolve the commit value without any short cuts
+            value = doc.resolveCommitValue(changeRevision);
+        } else {
+            // change revision is equal or older than sweep revision
+            // there are different cases:
+            // - doc is a main document and we have a guarantee that a
+            //   potential branch commit is marked accordingly
+            // - doc is a split document and the revision is guaranteed
+            //   to be committed. the remaining question is whether the
+            //   revision is from a branch commit
+            NodeDocument.SplitDocType sdt = doc.getSplitDocType();
+            if (sdt == NodeDocument.SplitDocType.NONE) {
+                // sweeper ensures that all changes on main document
+                // before the sweep revision are properly marked with
+                // branch commit entry if applicable
+                if (doc.getLocalBranchCommits().contains(changeRevision)) {
+                    // resolve the commit value the classic way
+                    value = doc.resolveCommitValue(changeRevision);
+                } else {
+                    value = "c";
+                }
+            } else if (sdt == NodeDocument.SplitDocType.DEFAULT_NO_BRANCH) {
+                // split document without branch commits, we don't have
+                // to check the commit root, the commit value is always 'c'
+                value = "c";
+            } else {
+                // some other split document type introduced
+                // before Oak 1.8 and we don't know if this is a branch
+                // commit. first try to resolve
+                value = doc.resolveCommitValue(changeRevision);
+                if (value == null) {
+                    // then it must be a non-branch commit and the
+                    // split document with the commit value was
+                    // already garbage collected
+                    value = "c";
+                }
+            }
+        }
+        if (Utils.isCommitted(value)) {
+            // only cache committed states
+            // e.g. branch commits may be merged later and
+            // the commit value will change
+            commitValueCache.put(changeRevision, value);
+        }
+        return value;
+    }
+
+    /**
+     * Resolves to the document that contains the change with the given
+     * revision. If the given document contains a local change for the given
+     * revision, then the passed document is returned. Otherwise this method
+     * looks up previous documents and returns one with a change for the given
+     * revision. This method returns {@code null} if neither the passed 
document
+     * nor any of its previous documents contains a change for the given
+     * revision.
+     *
+     * @param doc the document to resolve for the given change revision.
+     * @param changeRevision the revision of a change.
+     * @return the document with the change or {@code null} if there is no
+     *      document with such a change.
+     */
+    @CheckForNull
+    private NodeDocument resolveDocument(@Nonnull NodeDocument doc,
+                                         @Nonnull Revision changeRevision) {
+        // check if the document contains the change or we need to
+        // look up previous documents for the actual change
+        if (doc.getLocalCommitRoot().containsKey(changeRevision)
+                || doc.getLocalRevisions().containsKey(changeRevision)) {
+            return doc;
+        }
+        // find the previous document with this change
+        // first check if there is a commit root entry for this revision
+        NodeDocument d = null;
+        for (String p : COMMIT_ROOT_OR_REVISIONS) {
+            for (NodeDocument prev : doc.getPreviousDocs(p, changeRevision)) {
+                d = prev;
+                break;
+            }
+            if (d != null) {
+                break;
+            }
+        }
+        return d;
+    }
+
+}

Propchange: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolver.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java?rev=1791762&r1=1791761&r2=1791762&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
 Tue Apr 18 08:33:51 2017
@@ -592,6 +592,7 @@ public class DocumentMK {
         private JournalPropertyHandlerFactory journalPropertyHandlerFactory =
                 new JournalPropertyHandlerFactory();
         private int updateLimit = UPDATE_LIMIT;
+        private int commitValueCacheSize = 10000;
 
         public Builder() {
         }
@@ -1112,6 +1113,15 @@ public class DocumentMK {
             return updateLimit;
         }
 
+        public Builder setCommitValueCacheSize(int cacheSize) {
+            this.commitValueCacheSize = cacheSize;
+            return this;
+        }
+
+        public int getCommitValueCacheSize() {
+            return commitValueCacheSize;
+        }
+
         VersionGCSupport createVersionGCSupport() {
             DocumentStore store = getDocumentStore();
             if (store instanceof MongoDocumentStore) {

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java?rev=1791762&r1=1791761&r2=1791762&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
 Tue Apr 18 08:33:51 2017
@@ -86,7 +86,6 @@ import com.google.common.util.concurrent
 
 import org.apache.jackrabbit.api.stats.TimeSeries;
 import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.cache.CacheLIRS;
 import org.apache.jackrabbit.oak.commons.jmx.AnnotatedStandardMBean;
 import org.apache.jackrabbit.oak.core.SimpleCommitContext;
 import org.apache.jackrabbit.oak.plugins.blob.BlobStoreBlob;
@@ -390,10 +389,9 @@ public final class DocumentNodeStore
     private final DiffCache diffCache;
 
     /**
-     * A fixed size cache for commit values.
+     * The commit value resolver for this node store.
      */
-    private final Cache<Revision, String> commitValueCache
-            = new CacheLIRS<Revision, String>(10000);
+    private final CommitValueResolver commitValueResolver;
 
     /**
      * The blob store.
@@ -515,6 +513,13 @@ public final class DocumentNodeStore
 
     public DocumentNodeStore(DocumentMK.Builder builder) {
         this.updateLimit = builder.getUpdateLimit();
+        this.commitValueResolver = new 
CommitValueResolver(builder.getCommitValueCacheSize(),
+                new Supplier<RevisionVector>() {
+            @Override
+            public RevisionVector get() {
+                return getSweepRevisions();
+            }
+        });
         this.blobStore = builder.getBlobStore();
         this.statisticsProvider = builder.getStatisticsProvider();
         this.nodeStoreStatsCollector = builder.getNodeStoreStatsCollector();
@@ -1926,15 +1931,7 @@ public final class DocumentNodeStore
     @Override
     public String getCommitValue(@Nonnull Revision changeRevision,
                                  @Nonnull NodeDocument doc) {
-        String value = commitValueCache.getIfPresent(changeRevision);
-        if (value != null) {
-            return value;
-        }
-        value = doc.resolveCommitValue(changeRevision);
-        if (Utils.isCommitted(value)) {
-            commitValueCache.put(changeRevision, value);
-        }
-        return value;
+        return commitValueResolver.resolve(changeRevision, doc);
     }
 
     //----------------------< background operations 
>---------------------------

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=1791762&r1=1791761&r2=1791762&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
 Tue Apr 18 08:33:51 2017
@@ -877,6 +877,14 @@ public final class NodeDocument extends
         if (validRevisions.containsKey(rev)) {
             return true;
         }
+        if (Utils.isCommitted(commitValue) && !readRevision.isBranch()) {
+            // no need to load commit root document, we can simply
+            // tell by looking at the commit revision whether the
+            // revision is valid/visible
+            Revision commitRev = Utils.resolveCommitRevision(rev, commitValue);
+            return !readRevision.isRevisionNewer(commitRev);
+        }
+
         NodeDocument doc = getCommitRoot(rev);
         if (doc == null) {
             return false;

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolverTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolverTest.java?rev=1791762&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolverTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolverTest.java
 Tue Apr 18 08:33:51 2017
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.document;
+
+import com.google.common.base.Supplier;
+
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
+import static 
org.apache.jackrabbit.oak.plugins.document.util.Utils.getIdFromPath;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class CommitValueResolverTest {
+
+    @Rule
+    public DocumentMKBuilderProvider builderProvider = new 
DocumentMKBuilderProvider();
+
+    private DocumentNodeStore ns;
+
+    private CommitValueResolver resolver;
+
+    @Before
+    public void setup() {
+        ns = 
builderProvider.newBuilder().setUpdateLimit(20).setAsyncDelay(0).getNodeStore();
+        resolver = new CommitValueResolver(0, new Supplier<RevisionVector>() {
+            @Override
+            public RevisionVector get() {
+                return ns.getSweepRevisions();
+            }
+        });
+    }
+
+    @Test
+    public void unknownRevision() throws Exception {
+        Revision oldRevision = ns.newRevision();
+        addNode("/foo");
+        Revision newRevision = ns.newRevision();
+        NodeDocument foo = getDocument("/foo");
+        NodeDocument root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        assertNull(resolver.resolve(oldRevision, foo));
+        assertNull(resolver.resolve(oldRevision, root));
+        assertNull(resolver.resolve(newRevision, foo));
+        assertNull(resolver.resolve(newRevision, root));
+
+        // trigger sweeper
+        ns.runBackgroundOperations();
+
+        // must still not report as committed
+        foo = getDocument("/foo");
+        root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        assertNull(resolver.resolve(oldRevision, foo));
+        assertNull(resolver.resolve(oldRevision, root));
+        assertNull(resolver.resolve(newRevision, foo));
+        assertNull(resolver.resolve(newRevision, root));
+    }
+
+    @Test
+    public void committedTrunkCommit() throws Exception {
+        Revision r = addNode("/foo");
+        NodeDocument foo = getDocument("/foo");
+        NodeDocument root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        assertEquals("c", resolver.resolve(r, foo));
+        assertEquals("c", resolver.resolve(r, root));
+
+        // trigger sweeper
+        ns.runBackgroundOperations();
+
+        // must still report as committed
+        foo = getDocument("/foo");
+        root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        assertEquals("c", resolver.resolve(r, foo));
+        assertEquals("c", resolver.resolve(r, root));
+    }
+
+    @Test
+    public void committedTrunkCommitValueMovedToPreviousDoc() throws Exception 
{
+        Revision r = addNode("/foo");
+        // add changes until the revision moves to a previous document
+        assertTrue(getDocument("/").getLocalRevisions().containsKey(r));
+        while (getDocument("/").getLocalRevisions().containsKey(r)) {
+            someChange("/");
+            ns.runBackgroundUpdateOperations();
+        }
+        NodeDocument foo = getDocument("/foo");
+        NodeDocument root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        assertEquals("c", resolver.resolve(r, foo));
+        assertEquals("c", resolver.resolve(r, root));
+
+        // trigger sweeper
+        ns.runBackgroundOperations();
+
+        // must still report as committed
+        foo = getDocument("/foo");
+        root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        assertEquals("c", resolver.resolve(r, foo));
+        assertEquals("c", resolver.resolve(r, root));
+    }
+
+    @Test
+    public void committedTrunkCommitMovedToPreviousDoc() throws Exception {
+        String path = "/foo";
+        Revision r = addNode(path);
+        removeNode(path);
+        addNode(path);
+        // add changes until the revision moves to a previous document
+        assertTrue(getDocument("/foo").getLocalCommitRoot().containsKey(r));
+        while (getDocument("/foo").getLocalCommitRoot().containsKey(r)) {
+            someChange("/foo");
+            ns.runBackgroundUpdateOperations();
+        }
+        NodeDocument foo = getDocument("/foo");
+        NodeDocument root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        assertEquals("c", resolver.resolve(r, foo));
+        assertEquals("c", resolver.resolve(r, root));
+
+        // trigger sweeper
+        ns.runBackgroundOperations();
+
+        // must still report as committed
+        foo = getDocument("/foo");
+        root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        assertEquals("c", resolver.resolve(r, foo));
+        assertEquals("c", resolver.resolve(r, root));
+    }
+
+    @Test
+    public void branchCommit() throws Exception {
+        String path = "/foo";
+        NodeBuilder builder = addNodeBranched(path);
+        Revision r = getDocument("/").getLocalRevisions().firstKey();
+        String value = getDocument("/").getLocalRevisions()
+                .entrySet().iterator().next().getValue();
+        NodeDocument foo = getDocument(path);
+        NodeDocument root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        assertEquals(value, resolver.resolve(r, foo));
+        assertEquals(value, resolver.resolve(r, root));
+
+        // add another commit and run the sweeper
+        addNode("/bar");
+        ns.runBackgroundUpdateOperations();
+
+        // must still report the same value
+        foo = getDocument(path);
+        root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        assertEquals(value, resolver.resolve(r, foo));
+        assertEquals(value, resolver.resolve(r, root));
+
+        // now merge the branch
+        TestUtils.merge(ns, builder);
+
+        // now must report the committed revision
+        foo = getDocument(path);
+        root = getDocument("/");
+        assertNotNull(foo);
+        assertNotNull(root);
+        value = foo.resolveCommitValue(r);
+        assertTrue(Utils.isCommitted(value));
+        assertEquals(value, root.resolveCommitValue(r));
+        assertEquals(value, resolver.resolve(r, foo));
+        assertEquals(value, resolver.resolve(r, root));
+    }
+
+    private Revision addNode(String path) throws Exception {
+        NodeBuilder builder = ns.getRoot().builder();
+        NodeBuilder nb = builder;
+        for (String name : PathUtils.elements(path)) {
+            nb = nb.child(name);
+        }
+        TestUtils.merge(ns, builder);
+        return ns.getHeadRevision().getRevision(ns.getClusterId());
+    }
+
+    private NodeBuilder addNodeBranched(String path) {
+        NodeBuilder builder = ns.getRoot().builder();
+        NodeBuilder nb = builder;
+        for (String name : PathUtils.elements(path)) {
+            nb = nb.child(name);
+        }
+        int numRevEntries = getNumRevisions("/");
+        int i = 0;
+        while (numRevEntries == getNumRevisions("/")) {
+            nb.setProperty("p-" + i++, "v");
+        }
+        return builder;
+    }
+
+    private Revision removeNode(String path) throws Exception {
+        NodeBuilder builder = ns.getRoot().builder();
+        NodeBuilder nb = builder;
+        for (String name : PathUtils.elements(path)) {
+            nb = nb.child(name);
+        }
+        nb.remove();
+        TestUtils.merge(ns, builder);
+        return ns.getHeadRevision().getRevision(ns.getClusterId());
+    }
+
+    private NodeDocument getDocument(String path) {
+        return ns.getDocumentStore().find(NODES, getIdFromPath(path));
+    }
+
+    private void someChange(String path) throws Exception {
+        NodeBuilder builder = ns.getRoot().builder();
+        NodeBuilder nb = builder;
+        for (String name : PathUtils.elements(path)) {
+            nb = nb.child(name);
+        }
+        long value = 0;
+        if (nb.hasProperty("p")) {
+            value = nb.getProperty("p").getValue(Type.LONG) + 1;
+        }
+        nb.setProperty("p", value);
+        TestUtils.merge(ns, builder);
+    }
+
+    private int getNumRevisions(String path) {
+        NodeDocument doc = getDocument(path);
+        return doc != null ? doc.getLocalRevisions().size() : 0;
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitValueResolverTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentTest.java?rev=1791762&r1=1791761&r2=1791762&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentTest.java
 Tue Apr 18 08:33:51 2017
@@ -42,9 +42,11 @@ import org.junit.Test;
 
 import static com.google.common.collect.Maps.newLinkedHashMap;
 import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Collections.singletonList;
 import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.COLLISIONS;
 import static org.apache.jackrabbit.oak.plugins.document.TestUtils.NO_BINARY;
+import static 
org.apache.jackrabbit.oak.plugins.document.util.Utils.getIdFromPath;
 import static 
org.apache.jackrabbit.oak.plugins.document.util.Utils.getRootDocument;
 import static org.hamcrest.CoreMatchers.everyItem;
 import static org.hamcrest.Matchers.lessThan;
@@ -889,15 +891,60 @@ public class NodeDocumentTest {
         assertEquals(new RevisionVector(r1, r2), sweepRev);
     }
 
+    @Test
+    public void noPreviousDocAccessAfterSweep() throws Exception {
+        final Set<String> findCalls = newHashSet();
+        DocumentStore ds = new MemoryDocumentStore() {
+            @Override
+            public <T extends Document> T find(Collection<T> collection,
+                                               String key) {
+                findCalls.add(key);
+                return super.find(collection, key);
+            }
+        };
+        DocumentNodeStore ns = createTestStore(ds, 0, 0, 0);
+        // create test nodes with the root document as commit root
+        NodeBuilder builder = ns.getRoot().builder();
+        builder.child("foo");
+        builder.child("bar");
+        merge(ns, builder);
+        // now add many changes to the root document, which will
+        // move the commit information to a previous document
+        createTestData(singletonList(ns), new Random(), 200);
+        ns.runBackgroundUpdateOperations();
+
+        NodeDocument doc = ds.find(NODES, getIdFromPath("/foo"));
+        assertNotNull(doc);
+        findCalls.clear();
+        doc.getNodeAtRevision(ns, ns.getHeadRevision(), null);
+        // with an old sweep revision, there will be find calls
+        // to look up the commit root document
+        assertTrue(findCalls.size() > 0);
+
+        // run sweeper
+        ns.runBackgroundSweepOperation();
+
+        // now number of find calls must be zero
+        doc = ds.find(NODES, getIdFromPath("/foo"));
+        assertNotNull(doc);
+        findCalls.clear();
+        doc.getNodeAtRevision(ns, ns.getHeadRevision(), null);
+        assertEquals(0, findCalls.size());
+
+        ns.dispose();
+    }
+
     private DocumentNodeStore createTestStore(int numChanges) throws Exception 
{
         return createTestStore(new MemoryDocumentStore(), 0, numChanges);
     }
 
     private DocumentNodeStore createTestStore(DocumentStore store,
                                               int clusterId,
-                                              int numChanges) throws Exception 
{
+                                              int numChanges,
+                                              int commitValueCacheSize)
+            throws Exception {
         DocumentNodeStore ns = new DocumentMK.Builder()
-                .setDocumentStore(store)
+                
.setDocumentStore(store).setCommitValueCacheSize(commitValueCacheSize)
                 .setAsyncDelay(0).setClusterId(clusterId).getNodeStore();
         for (int i = 0; i < numChanges; i++) {
             NodeBuilder builder = ns.getRoot().builder();
@@ -915,6 +962,12 @@ public class NodeDocumentTest {
         return ns;
     }
 
+    private DocumentNodeStore createTestStore(DocumentStore store,
+                                              int clusterId,
+                                              int numChanges) throws Exception 
{
+        return createTestStore(store, clusterId, numChanges, 10000);
+    }
+
     private List<RevisionVector> createTestData(List<DocumentNodeStore> 
nodeStores,
                                                 Random random,
                                                 int numChanges)
@@ -930,12 +983,14 @@ public class NodeDocumentTest {
         List<RevisionVector> headRevisions = Lists.newArrayList();
         for (int i = startValue; i < numChanges + startValue; i++) {
             DocumentNodeStore ns = 
nodeStores.get(random.nextInt(nodeStores.size()));
-            ns.runBackgroundOperations();
+            ns.runBackgroundUpdateOperations();
+            ns.runBackgroundReadOperations();
             NodeBuilder builder = ns.getRoot().builder();
             builder.setProperty("p", i);
             merge(ns, builder);
             headRevisions.add(ns.getHeadRevision());
-            ns.runBackgroundOperations();
+            ns.runBackgroundUpdateOperations();
+            ns.runBackgroundReadOperations();
             if (random.nextDouble() < 0.2) {
                 DocumentStore store = ns.getDocumentStore();
                 RevisionVector head = ns.getHeadRevision();


Reply via email to