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());