Author: mreutegg
Date: Tue May  7 10:24:15 2019
New Revision: 1858838

URL: http://svn.apache.org/viewvc?rev=1858838&view=rev
Log:
OAK-8300: Revision GC may remove previous document without removing reference

Added:
    
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BackgroundSplitFailureTest.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
    
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
    
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
    
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java

Modified: 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java?rev=1858838&r1=1858837&r2=1858838&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
 Tue May  7 10:24:15 2019
@@ -2367,6 +2367,7 @@ public final class DocumentNodeStore
     }
 
     private void backgroundSplit() {
+        Set<Path> invalidatedPaths = new HashSet<>();
         RevisionVector head = getHeadRevision();
         for (Iterator<String> it = splitCandidates.keySet().iterator(); 
it.hasNext();) {
             String id = it.next();
@@ -2376,6 +2377,30 @@ public final class DocumentNodeStore
             }
             cleanCollisions(doc, collisionGarbageBatchSize);
             for (UpdateOp op : doc.split(this, head, binarySize)) {
+                Path path = doc.getPath();
+                // add an invalidation journal entry, unless the path
+                // already has a pending _lastRev update or an invalidation
+                // entry was already added in this backgroundSplit() call
+                if (unsavedLastRevisions.get(path) == null
+                        && invalidatedPaths.add(path)) {
+                    // create journal entry for cache invalidation
+                    JournalEntry entry = 
JOURNAL.newDocument(getDocumentStore());
+                    entry.modified(path);
+                    Revision r = newRevision().asBranchRevision();
+                    UpdateOp journalOp = entry.asUpdateOp(r);
+                    if (store.create(JOURNAL, singletonList(journalOp))) {
+                        changes.invalidate(singletonList(r));
+                        LOG.debug("Journal entry {} created for split of 
document {}",
+                                journalOp.getId(), path);
+                    } else {
+                        String msg = "Unable to create journal entry " +
+                                journalOp.getId() + " for document 
invalidation. " +
+                                "Will be retried with next background split " +
+                                "operation.";
+                        throw new DocumentStoreException(msg);
+                    }
+                }
+                // apply the split operations
                 NodeDocument before = null;
                 if (!op.isNew() ||
                         !store.create(Collection.NODES, 
Collections.singletonList(op))) {

Modified: 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java?rev=1858838&r1=1858837&r2=1858838&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
 Tue May  7 10:24:15 2019
@@ -527,14 +527,32 @@ public final class JournalEntry extends
     }
 
     /**
-     * @return if this entry has some changes to be pushed
+     * Returns {@code true} if this entry contains any changes to be written
+     * to the journal. The following information is considered a change:
+     * <ul>
+     *     <li>Documents that have been recorded as modified with either
+     *      {@link #modified(Path)} or {@link #modified(Iterable)}.</li>
+     *     <li>Branch commit journal references added with
+     *      {@link #branchCommit(Iterable)}.</li>
+     *     <li>Documents that must be invalidated and have been recorded
+     *      with {@link #invalidate(Iterable)}.</li>
+     * </ul>
+     *
+     * @return if this entry has some changes to be pushed.
      */
     boolean hasChanges() {
-        return numChangedNodes > 0 || hasBranchCommits;
+        return numChangedNodes > 0
+                || hasBranchCommits
+                || hasInvalidateOnlyReferences();
     }
 
     //-----------------------------< internal 
>---------------------------------
 
+    private boolean hasInvalidateOnlyReferences() {
+        String value = (String) get(INVALIDATE_ONLY);
+        return value != null && !value.isEmpty();
+    }
+
     private void addInvalidateOnlyTo(final StringSort sort) throws IOException 
{
         TraversingVisitor v = new TraversingVisitor() {
 

Added: 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BackgroundSplitFailureTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BackgroundSplitFailureTest.java?rev=1858838&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BackgroundSplitFailureTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BackgroundSplitFailureTest.java
 Tue May  7 10:24:15 2019
@@ -0,0 +1,132 @@
+/*
+ * 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 java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.stats.Clock;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.apache.jackrabbit.oak.plugins.document.TestUtils.merge;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class BackgroundSplitFailureTest {
+
+    @Rule
+    public DocumentMKBuilderProvider builderProvider = new 
DocumentMKBuilderProvider();
+
+    private Clock clock = new Clock.Virtual();
+
+    @Before
+    public void before() throws Exception {
+        clock.waitUntil(System.currentTimeMillis());
+        Revision.setClock(clock);
+        ClusterNodeInfo.setClock(clock);
+    }
+
+    @AfterClass
+    public static void after() {
+        Revision.resetClockToDefault();
+        ClusterNodeInfo.resetClockToDefault();
+    }
+
+    @Test
+    public void journalException() throws Exception {
+        DocumentStore store = new MemoryDocumentStore();
+        FailingDocumentStore failingStore = new FailingDocumentStore(store);
+        DocumentNodeStore ns = new DocumentNodeStoreBuilder<>()
+                
.setDocumentStore(failingStore).setAsyncDelay(0).clock(clock).build();
+        int clusterId = ns.getClusterId();
+        Path fooPath = new Path(Path.ROOT, "foo");
+        String fooId = Utils.getIdFromPath(fooPath);
+
+        NodeBuilder builder = ns.getRoot().builder();
+        builder.child(fooPath.getName()).setProperty("p", -1);
+        merge(ns, builder);
+        for (int i = 0; i <= NodeDocument.NUM_REVS_THRESHOLD; i++) {
+            builder = ns.getRoot().builder();
+            builder.child(fooPath.getName()).setProperty("p", i);
+            merge(ns, builder);
+        }
+
+        // shut down without running background ops
+        failingStore.fail().after(0).eternally();
+        try {
+            ns.dispose();
+            fail("dispose is expected to fail");
+        } catch (Exception e) {
+            // expected
+        }
+
+        // must not have previous documents yet
+        NodeDocument foo = store.find(Collection.NODES, fooId);
+        assertNotNull(foo);
+        assertEquals(0, foo.getPreviousRanges().size());
+
+        // start again with test store
+        final AtomicBoolean falseOnJournalEntryCreate = new AtomicBoolean();
+        DocumentStore testStore = new DocumentStoreWrapper(store) {
+            @Override
+            public <T extends Document> boolean create(Collection<T> 
collection,
+                                                       List<UpdateOp> 
updateOps) {
+                if (collection == Collection.JOURNAL
+                        && falseOnJournalEntryCreate.get()) {
+                    return false;
+                }
+                return super.create(collection, updateOps);
+            }
+        };
+        ns = builderProvider.newBuilder().setClusterId(clusterId)
+                
.setDocumentStore(testStore).setAsyncDelay(0).clock(clock).build();
+        ns.addSplitCandidate(Utils.getIdFromPath(new Path(Path.ROOT, "foo")));
+        falseOnJournalEntryCreate.set(true);
+        try {
+            ns.runBackgroundOperations();
+            fail("background operations are expected to fail");
+        } catch (DocumentStoreException e) {
+            assertThat(e.getMessage(), containsString("Unable to create 
journal entry"));
+        }
+
+        // must still not have previous documents
+        foo = store.find(Collection.NODES, fooId);
+        assertNotNull(foo);
+        assertEquals(0, foo.getPreviousRanges().size());
+        assertTrue(ns.getSplitCandidates().contains(fooId));
+
+        falseOnJournalEntryCreate.set(false);
+        ns.runBackgroundOperations();
+
+        // now there must be a split document
+        foo = store.find(Collection.NODES, fooId);
+        assertNotNull(foo);
+        assertEquals(1, foo.getPreviousRanges().size());
+        assertFalse(ns.getSplitCandidates().contains(fooId));
+    }
+}

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

Modified: 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java?rev=1858838&r1=1858837&r2=1858838&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
 Tue May  7 10:24:15 2019
@@ -398,6 +398,20 @@ public class JournalEntryTest {
     }
 
     @Test
+    public void invalidationPathsMarksChanges() {
+        DocumentStore store = new MemoryDocumentStore();
+        JournalEntry entry = JOURNAL.newDocument(store);
+
+        assertFalse("Incorrect hasChanges", entry.hasChanges());
+
+        entry.invalidate(Collections.emptyList());
+        assertFalse("Incorrect hasChanges", entry.hasChanges());
+
+        
entry.invalidate(Collections.singleton(Revision.fromString("r123-0-1")));
+        assertTrue("Incorrect hasChanges", entry.hasChanges());
+    }
+
+    @Test
     public void emptyBranchCommit() {
         DocumentStore store = new MemoryDocumentStore();
         JournalEntry entry = JOURNAL.newDocument(store);

Modified: 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java?rev=1858838&r1=1858837&r2=1858838&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java
 Tue May  7 10:24:15 2019
@@ -83,7 +83,6 @@ import org.apache.jackrabbit.oak.stats.C
 import org.jetbrains.annotations.NotNull;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -880,7 +879,6 @@ public class VersionGarbageCollectorIT {
         assertEquals(value, 
store.getRoot().getChildNode("foo").getString("prop"));
     }
 
-    @Ignore("OAK-8300")
     @Test
     public void gcOnStaleDocument() throws Exception {
         assumeTrue(fixture.hasSinglePersistence());


Reply via email to