You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ba...@apache.org on 2016/09/27 13:34:26 UTC

svn commit: r1762477 - in /jackrabbit/oak/branches/1.4: ./ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/

Author: baedke
Date: Tue Sep 27 13:34:26 2016
New Revision: 1762477

URL: http://svn.apache.org/viewvc?rev=1762477&view=rev
Log:
OAK-4528: diff calculation in DocumentNodeStore should try to re-use journal info on diff cache miss

Added:
    jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java
      - copied unchanged from r1752596, jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java
    jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JsopNodeStateDiffer.java
      - copied unchanged from r1752596, jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JsopNodeStateDiffer.java
    jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoaderTest.java
      - copied unchanged from r1752596, jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoaderTest.java
Modified:
    jackrabbit/oak/branches/1.4/   (props changed)
    jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
    jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
    jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalGarbageCollector.java
    jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranches.java
    jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java
    jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
    jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalGCTest.java
    jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java

Propchange: jackrabbit/oak/branches/1.4/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Sep 27 13:34:26 2016
@@ -1,3 +1,3 @@
 /jackrabbit/oak/branches/1.0:1665962
-/jackrabbit/oak/trunk:1733615,1733875,1733913,1733929,1734230,1734254,1734279,1734941,1735052,1735081,1735141,1735267,1735405,1735484,1735549,1735564,1735588,1735622,1735638,1735919,1735983,1736176,1737309-1737310,1737334,1737349,1737998,1738004,1738136,1738138,1738207,1738234,1738252,1738775,1738795,1738833,1738950,1738957,1738963,1739712,1739760,1739867,1739894,1739959-1739960,1740114,1740116,1740250,1740333,1740360,1740625-1740626,1740774,1740837,1740971,1741016,1741032,1741339,1741343,1742077,1742117,1742363,1742520,1742888,1742916,1743097,1743172,1743343,1743674,1744265,1744292,1744589,1744670,1744672,1744959,1745038,1745127,1745197,1745336,1745368,1746086,1746117,1746342,1746345,1746408,1746696,1746981,1747198,1747200,1747341-1747342,1747380,1747387,1747406,1747492,1747512,1747654,1748505,1748553,1748722,1748870,1749275,1749350,1749424,1749443,1749464,1749475,1749645,1749662,1749815,1749872,1749875,1749899,1750052,1750076-1750077,1750287,1750457,1750462,1750465,1750495,1750626
 ,1750809,1750886,1751410,1751445-1751446,1751478,1751755,1751871,1752198,1752202,1752259,1752273-1752274,1752283,1752292,1752438,1752447-1752448,1752508,1752616,1752659,1752672,1753262,1753331-1753332,1753355,1753444,1754117,1754239,1755157,1756520,1756580,1757119,1757166,1759433,1760340,1760373,1760387,1760661-1760662,1761412,1761444,1761571,1761762,1761787
+/jackrabbit/oak/trunk:1733615,1733875,1733913,1733929,1734230,1734254,1734279,1734941,1735052,1735081,1735141,1735267,1735405,1735484,1735549,1735564,1735588,1735622,1735638,1735919,1735983,1736176,1737309-1737310,1737334,1737349,1737998,1738004,1738136,1738138,1738207,1738234,1738252,1738775,1738795,1738833,1738950,1738957,1738963,1739712,1739760,1739867,1739894,1739959-1739960,1740114,1740116,1740250,1740333,1740360,1740625-1740626,1740774,1740837,1740971,1741016,1741032,1741339,1741343,1742077,1742117,1742363,1742520,1742888,1742916,1743097,1743172,1743343,1743674,1744265,1744292,1744589,1744670,1744672,1744959,1745038,1745127,1745197,1745336,1745368,1746086,1746117,1746342,1746345,1746408,1746696,1746981,1747198,1747200,1747341-1747342,1747380,1747387,1747406,1747492,1747512,1747654,1748505,1748553,1748722,1748870,1749275,1749350,1749424,1749443,1749464,1749475,1749645,1749662,1749815,1749872,1749875,1749899,1750052,1750076-1750077,1750287,1750457,1750462,1750465,1750495,1750626
 ,1750809,1750886,1751410,1751445-1751446,1751478,1751755,1751871,1752198,1752202,1752259,1752273-1752274,1752283,1752292,1752438,1752447-1752448,1752508,1752596,1752616,1752659,1752672,1753262,1753331-1753332,1753355,1753444,1754117,1754239,1755157,1756520,1756580,1757119,1757166,1759433,1760340,1760373,1760387,1760661-1760662,1761412,1761444,1761571,1761762,1761787
 /jackrabbit/trunk:1345480

Modified: jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java?rev=1762477&r1=1762476&r2=1762477&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java Tue Sep 27 13:34:26 2016
@@ -147,6 +147,12 @@ public final class DocumentNodeStore
             Boolean.parseBoolean(System.getProperty("oak.fairBackgroundOperationLock", "true"));
 
     /**
+     * Feature flag to disable the journal diff mechanism. See OAK-4528.
+     */
+    private boolean disableJournalDiff =
+            Boolean.getBoolean("oak.disableJournalDiff");
+
+    /**
      * The document store (might be used by multiple node stores).
      */
     protected final DocumentStore store;
@@ -1454,22 +1460,22 @@ public final class DocumentNodeStore
      */
     @Override
     public boolean compare(@Nonnull final AbstractDocumentNodeState node,
-                    @Nonnull final AbstractDocumentNodeState base,
-                    @Nonnull NodeStateDiff diff) {
+                           @Nonnull final AbstractDocumentNodeState base,
+                           @Nonnull NodeStateDiff diff) {
         if (!AbstractNodeState.comparePropertiesAgainstBaseState(node, base, diff)) {
             return false;
         }
         if (node.hasNoChildren() && base.hasNoChildren()) {
             return true;
         }
-        return dispatch(diffCache.getChanges(base.getRootRevision(),
+        return new JsopNodeStateDiffer(diffCache.getChanges(base.getRootRevision(),
                 node.getRootRevision(), node.getPath(),
                 new DiffCache.Loader() {
                     @Override
                     public String call() {
                         return diffImpl(base, node);
                     }
-                }), node, base, diff);
+                })).withoutPropertyChanges().compare(node, base, diff);
     }
 
     /**
@@ -2165,47 +2171,6 @@ public final class DocumentNodeStore
         }
     }
 
-    private boolean dispatch(@Nonnull final String jsonDiff,
-                             @Nonnull final AbstractDocumentNodeState node,
-                             @Nonnull final AbstractDocumentNodeState base,
-                             @Nonnull final NodeStateDiff diff) {
-        return DiffCache.parseJsopDiff(jsonDiff, new DiffCache.Diff() {
-            @Override
-            public boolean childNodeAdded(String name) {
-                return diff.childNodeAdded(name,
-                        node.getChildNode(name));
-            }
-
-            @Override
-            public boolean childNodeChanged(String name) {
-                boolean continueComparison = true;
-                NodeState baseChild = base.getChildNode(name);
-                NodeState nodeChild = node.getChildNode(name);
-                if (baseChild.exists()) {
-                    if (nodeChild.exists()) {
-                        continueComparison = diff.childNodeChanged(name,
-                                baseChild, nodeChild);
-                    } else {
-                        continueComparison = diff.childNodeDeleted(name,
-                                baseChild);
-                    }
-                } else {
-                    if (nodeChild.exists()) {
-                        continueComparison = diff.childNodeAdded(name,
-                                nodeChild);
-                    }
-                }
-                return continueComparison;
-            }
-
-            @Override
-            public boolean childNodeDeleted(String name) {
-                return diff.childNodeDeleted(name,
-                        base.getChildNode(name));
-            }
-        });
-    }
-
     /**
      * Search for presence of child node as denoted by path in the children cache of parent
      *
@@ -2248,44 +2213,54 @@ public final class DocumentNodeStore
 
     private String diffImpl(AbstractDocumentNodeState from, AbstractDocumentNodeState to)
             throws DocumentStoreException {
-        JsopWriter w = new JsopStream();
-        // TODO this does not work well for large child node lists
-        // use a document store index instead
         int max = MANY_CHILDREN_THRESHOLD;
 
         final boolean debug = LOG.isDebugEnabled();
         final long start = debug ? now() : 0;
+        long getChildrenDoneIn = start;
 
-        DocumentNodeState.Children fromChildren, toChildren;
-        fromChildren = getChildren(from, null, max);
-        toChildren = getChildren(to, null, max);
-
-        final long getChildrenDoneIn = debug ? now() : 0;
-
+        String diff;
         String diffAlgo;
         RevisionVector fromRev = from.getLastRevision();
         RevisionVector toRev = to.getLastRevision();
-        if (!fromChildren.hasMore && !toChildren.hasMore) {
-            diffAlgo = "diffFewChildren";
-            diffFewChildren(w, from.getPath(), fromChildren,
-                    fromRev, toChildren, toRev);
+        long minTimestamp = Utils.getMinTimestampForDiff(
+                fromRev, toRev, getMinExternalRevisions());
+
+        // use journal if possible
+        Revision tailRev = journalGarbageCollector.getTailRevision();
+        if (!disableJournalDiff
+                && tailRev.getTimestamp() < minTimestamp) {
+            diffAlgo = "diffJournalChildren";
+            diff = new JournalDiffLoader(from, to, this).call();
         } else {
-            if (FAST_DIFF) {
-                diffAlgo = "diffManyChildren";
-                fromRev = from.getRootRevision();
-                toRev = to.getRootRevision();
-                diffManyChildren(w, from.getPath(), fromRev, toRev);
-            } else {
-                diffAlgo = "diffAllChildren";
-                max = Integer.MAX_VALUE;
-                fromChildren = getChildren(from, null, max);
-                toChildren = getChildren(to, null, max);
+            DocumentNodeState.Children fromChildren, toChildren;
+            fromChildren = getChildren(from, null, max);
+            toChildren = getChildren(to, null, max);
+            getChildrenDoneIn = debug ? now() : 0;
+
+            JsopWriter w = new JsopStream();
+            if (!fromChildren.hasMore && !toChildren.hasMore) {
+                diffAlgo = "diffFewChildren";
                 diffFewChildren(w, from.getPath(), fromChildren,
                         fromRev, toChildren, toRev);
+            } else {
+                if (FAST_DIFF) {
+                    diffAlgo = "diffManyChildren";
+                    fromRev = from.getRootRevision();
+                    toRev = to.getRootRevision();
+                    diffManyChildren(w, from.getPath(), fromRev, toRev);
+                } else {
+                    diffAlgo = "diffAllChildren";
+                    max = Integer.MAX_VALUE;
+                    fromChildren = getChildren(from, null, max);
+                    toChildren = getChildren(to, null, max);
+                    diffFewChildren(w, from.getPath(), fromChildren,
+                            fromRev, toChildren, toRev);
+                }
             }
+            diff = w.toString();
         }
 
-        String diff = w.toString();
         if (debug) {
             long end = now();
             LOG.debug("Diff performed via '{}' at [{}] between revisions [{}] => [{}] took {} ms ({} ms), diff '{}', external '{}",

Modified: jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java?rev=1762477&r1=1762476&r2=1762477&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java Tue Sep 27 13:34:26 2016
@@ -175,7 +175,9 @@ public final class JournalEntry extends
     /**
      * Reads all external changes between the two given revisions (with the same
      * clusterId) from the journal and appends the paths therein to the provided
-     * sorter.
+     * sorter. If there is no exact match of a journal entry for the given
+     * {@code to} revision, this method will fill external changes from the
+     * next higher journal entry that contains the revision.
      *
      * @param sorter the StringSort to which all externally changed paths
      *               between the provided revisions will be added
@@ -191,6 +193,10 @@ public final class JournalEntry extends
             throws IOException {
         checkArgument(checkNotNull(from).getClusterId() == checkNotNull(to).getClusterId());
 
+        if (from.compareRevisionTime(to) >= 0) {
+            return;
+        }
+
         // to is inclusive, but DocumentStore.query() toKey is exclusive
         final String inclusiveToId = asId(to);
         to = new Revision(to.getTimestamp(), to.getCounter() + 1,
@@ -203,6 +209,8 @@ public final class JournalEntry extends
         // limit, then loop and do subsequent queries
         final String toId = asId(to);
         String fromId = asId(from);
+        int numEntries = 0;
+        JournalEntry lastEntry = null;
         while (true) {
             if (fromId.equals(inclusiveToId)) {
                 // avoid query if from and to are off by just 1 counter (which
@@ -212,6 +220,10 @@ public final class JournalEntry extends
                 break;
             }
             List<JournalEntry> partialResult = store.query(JOURNAL, fromId, toId, READ_CHUNK_SIZE);
+            numEntries += partialResult.size();
+            if (!partialResult.isEmpty()) {
+                lastEntry = partialResult.get(partialResult.size() - 1);
+            }
 
             for (JournalEntry d : partialResult) {
                 d.addTo(sorter);
@@ -224,6 +236,16 @@ public final class JournalEntry extends
             // include the from which we'd otherwise double-process)
             fromId = partialResult.get(partialResult.size() - 1).getId();
         }
+        // check if last processed journal entry covers toId, otherwise
+        // read next document. also read next journal entry when none
+        // were read so far
+        if (numEntries == 0
+                || (lastEntry != null && !lastEntry.getId().equals(inclusiveToId))) {
+            String maxId = asId(new Revision(Long.MAX_VALUE, 0, to.getClusterId()));
+            for (JournalEntry d : store.query(JOURNAL, inclusiveToId, maxId, 1)) {
+                d.addTo(sorter);
+            }
+        }
     }
 
     long getRevisionTimestamp() {

Modified: jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalGarbageCollector.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalGarbageCollector.java?rev=1762477&r1=1762476&r2=1762477&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalGarbageCollector.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalGarbageCollector.java Tue Sep 27 13:34:26 2016
@@ -22,11 +22,14 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Stopwatch;
 
+import static org.apache.jackrabbit.oak.plugins.document.Collection.SETTINGS;
+
 /**
  * The JournalGarbageCollector can clean up JournalEntries that are older than a
  * particular age.
@@ -38,12 +41,27 @@ import com.google.common.base.Stopwatch;
  */
 public class JournalGarbageCollector {
 
+    private static final Logger log = LoggerFactory.getLogger(JournalGarbageCollector.class);
+
+    /**
+     * ID of the journalGC document in the settings collection.
+     */
+    private static final String JOURNAL_GC_ID = "journalGC";
+
+    /**
+     * Key name of the entry that contains the timestamp of the journal tail.
+     */
+    private static final String TAIL_TIMESTAMP = "tailTimestamp";
+
     private final DocumentNodeStore ns;
 
-    private static final Logger log = LoggerFactory.getLogger(JournalGarbageCollector.class);
+    private volatile long lastTailTimestampRefresh = Long.MIN_VALUE;
+
+    private Revision tailRevision;
 
     public JournalGarbageCollector(DocumentNodeStore nodeStore) {
         this.ns = nodeStore;
+        this.tailRevision = new Revision(0, 0, ns.getClusterId());
     }
 
     /**
@@ -89,6 +107,10 @@ public class JournalGarbageCollector {
         // will compete at deletion, which is not optimal
         // due to performance, but does not harm.
 
+        // update the tail timestamp in the journalGC document
+        // of the settings collection
+        updateTailTimestamp(gcOlderThan);
+
         // 1. get the list of cluster node ids
         final List<ClusterNodeInfoDocument> clusterNodeInfos = ClusterNodeInfoDocument.all(ds);
         int numDeleted = 0;
@@ -139,6 +161,36 @@ public class JournalGarbageCollector {
         return numDeleted;
     }
 
+    private void updateTailTimestamp(long gcOlderThan) {
+        UpdateOp op = new UpdateOp(JOURNAL_GC_ID, true);
+        op.max(TAIL_TIMESTAMP, gcOlderThan);
+        ns.getDocumentStore().createOrUpdate(SETTINGS, op);
+    }
+
+    public Revision getTailRevision() {
+        refreshTailRevisionIfNecessary();
+        return tailRevision;
+    }
+
+    private void refreshTailRevisionIfNecessary() {
+        // refresh once a minute
+        long now = ns.getClock().getTime();
+        if (lastTailTimestampRefresh + TimeUnit.MINUTES.toMillis(1) > now) {
+            return;
+        }
+        lastTailTimestampRefresh = now;
+
+        Document doc = ns.getDocumentStore().find(SETTINGS, JOURNAL_GC_ID);
+        if (doc == null) {
+            // no gc yet
+            return;
+        }
+        Long ts = Utils.asLong((Number) doc.get(TAIL_TIMESTAMP));
+        if (ts != null) {
+            tailRevision = Utils.max(tailRevision, new Revision(ts, 0, ns.getClusterId()));
+        }
+    }
+
     private List<String> asKeys(List<JournalEntry> deletionBatch) {
         final List<String> keys = new ArrayList<String>(deletionBatch.size());
         for (JournalEntry e : deletionBatch) {

Modified: jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranches.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranches.java?rev=1762477&r1=1762476&r2=1762477&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranches.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranches.java Tue Sep 27 13:34:26 2016
@@ -138,6 +138,26 @@ class UnmergedBranches {
     }
 
     /**
+     * Returns {@code true} if the given revision is the base of an unmerged
+     * branch.
+     *
+     * @param r the base revision of a branch.
+     * @return {@code true} if such a branch exists, {@code false} otherwise.
+     */
+    boolean isBranchBase(@Nonnull RevisionVector r) {
+        if (!r.isBranch()) {
+            return false;
+        }
+        RevisionVector base = r.asTrunkRevision();
+        for (Branch b : branches) {
+            if (b.getBase().equals(base)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Returns the branch commit with the given revision or {@code null} if
      * it doesn't exists.
      *

Modified: jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java?rev=1762477&r1=1762476&r2=1762477&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java Tue Sep 27 13:34:26 2016
@@ -2173,6 +2173,10 @@ public class DocumentNodeStoreTest {
     // OAK-1970
     @Test
     public void diffMany() throws Exception {
+        // make sure diffMany is used and not the new
+        // journal diff introduced with OAK-4528
+        System.setProperty("oak.disableJournalDiff", "true");
+
         Clock clock = new Clock.Virtual();
         clock.waitUntil(System.currentTimeMillis());
         Revision.setClock(clock);
@@ -2233,6 +2237,8 @@ public class DocumentNodeStoreTest {
         // startValue must be based on the revision of the before state
         // and not when '/test' was last modified
         assertEquals(beforeModified, (long) startValues.get(0));
+
+        System.clearProperty("oak.disableJournalDiff");
     }
 
     // OAK-2620

Modified: jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java?rev=1762477&r1=1762476&r2=1762477&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java Tue Sep 27 13:34:26 2016
@@ -136,6 +136,76 @@ public class JournalEntryTest {
     }
 
     @Test
+    public void fillExternalChanges2() throws Exception {
+        Revision r1 = new Revision(1, 0, 1);
+        Revision r2 = new Revision(2, 0, 1);
+        Revision r3 = new Revision(3, 0, 1);
+        Revision r4 = new Revision(4, 0, 1);
+        DocumentStore store = new MemoryDocumentStore();
+        JournalEntry entry = JOURNAL.newDocument(store);
+        entry.modified("/");
+        entry.modified("/foo");
+        UpdateOp op = entry.asUpdateOp(r2);
+        assertTrue(store.create(JOURNAL, Collections.singletonList(op)));
+
+        entry = JOURNAL.newDocument(store);
+        entry.modified("/");
+        entry.modified("/bar");
+        op = entry.asUpdateOp(r4);
+        assertTrue(store.create(JOURNAL, Collections.singletonList(op)));
+
+        StringSort sort = JournalEntry.newSorter();
+        JournalEntry.fillExternalChanges(sort, r1, r1, store);
+        assertEquals(0, sort.getSize());
+        sort.close();
+
+        sort = JournalEntry.newSorter();
+        JournalEntry.fillExternalChanges(sort, r1, r2, store);
+        assertEquals(Sets.newHashSet("/", "/foo"), Sets.newHashSet(sort));
+        sort.close();
+
+        sort = JournalEntry.newSorter();
+        JournalEntry.fillExternalChanges(sort, r1, r3, store);
+        assertEquals(Sets.newHashSet("/", "/foo", "/bar"), Sets.newHashSet(sort));
+        sort.close();
+
+        sort = JournalEntry.newSorter();
+        JournalEntry.fillExternalChanges(sort, r1, r4, store);
+        assertEquals(Sets.newHashSet("/", "/foo", "/bar"), Sets.newHashSet(sort));
+        sort.close();
+
+        sort = JournalEntry.newSorter();
+        JournalEntry.fillExternalChanges(sort, r2, r2, store);
+        assertEquals(0, sort.getSize());
+        sort.close();
+
+        sort = JournalEntry.newSorter();
+        JournalEntry.fillExternalChanges(sort, r2, r3, store);
+        assertEquals(Sets.newHashSet("/", "/bar"), Sets.newHashSet(sort));
+        sort.close();
+
+        sort = JournalEntry.newSorter();
+        JournalEntry.fillExternalChanges(sort, r2, r4, store);
+        assertEquals(Sets.newHashSet("/", "/bar"), Sets.newHashSet(sort));
+        sort.close();
+
+        sort = JournalEntry.newSorter();
+        JournalEntry.fillExternalChanges(sort, r3, r3, store);
+        assertEquals(0, sort.getSize());
+        sort.close();
+
+        sort = JournalEntry.newSorter();
+        JournalEntry.fillExternalChanges(sort, r3, r4, store);
+        assertEquals(Sets.newHashSet("/", "/bar"), Sets.newHashSet(sort));
+        sort.close();
+
+        sort = JournalEntry.newSorter();
+        JournalEntry.fillExternalChanges(sort, r4, r4, store);
+        assertEquals(0, sort.getSize());
+        sort.close();
+    }
+
+    @Test
     public void getRevisionTimestamp() throws Exception {
         DocumentStore store = new MemoryDocumentStore();
         JournalEntry entry = JOURNAL.newDocument(store);

Modified: jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalGCTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalGCTest.java?rev=1762477&r1=1762476&r2=1762477&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalGCTest.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalGCTest.java Tue Sep 27 13:34:26 2016
@@ -28,6 +28,7 @@ import org.junit.Rule;
 import org.junit.Test;
 
 import static org.apache.jackrabbit.oak.plugins.document.Collection.JOURNAL;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
@@ -74,4 +75,41 @@ public class JournalGCTest {
         entry = ns.getDocumentStore().find(JOURNAL, JournalEntry.asId(head));
         assertNull(entry);
     }
+
+    @Test
+    public void getTailRevision() throws Exception {
+        Clock c = new Clock.Virtual();
+        c.waitUntil(System.currentTimeMillis());
+        DocumentNodeStore ns = builderProvider.newBuilder()
+                .clock(c).setAsyncDelay(0).getNodeStore();
+
+        JournalGarbageCollector jgc = ns.getJournalGarbageCollector();
+        assertEquals(new Revision(0, 0, ns.getClusterId()), jgc.getTailRevision());
+
+        NodeBuilder builder = ns.getRoot().builder();
+        builder.child("foo");
+        ns.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+        ns.runBackgroundOperations();
+
+        assertEquals(0, jgc.gc(1, 10, TimeUnit.HOURS));
+
+        // current time, but without the increment done by getTime()
+        long now = c.getTime() - 1;
+        Revision tail = new Revision(now - TimeUnit.HOURS.toMillis(1), 0, ns.getClusterId());
+
+        c.waitUntil(c.getTime() + TimeUnit.MINUTES.toMillis(1));
+        assertEquals(tail, jgc.getTailRevision());
+
+        c.waitUntil(c.getTime() + TimeUnit.HOURS.toMillis(1));
+
+        // must collect all journal entries. the first created when
+        // DocumentNodeStore was initialized and the second created
+        // by the background update
+        assertEquals(2, jgc.gc(1, 10, TimeUnit.HOURS));
+
+        // current time, but without the increment done by getTime()
+        now = c.getTime() - 1;
+        tail = new Revision(now - TimeUnit.HOURS.toMillis(1), 0, ns.getClusterId());
+        assertEquals(tail, jgc.getTailRevision());
+    }
 }

Modified: jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java?rev=1762477&r1=1762476&r2=1762477&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java (original)
+++ jackrabbit/oak/branches/1.4/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java Tue Sep 27 13:34:26 2016
@@ -16,10 +16,13 @@
  */
 package org.apache.jackrabbit.oak.plugins.document;
 
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.equalTo;
 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.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -162,7 +165,7 @@ public class SimpleTest {
         String diff23 = mk.diff(rev2, rev3, "/", 0).trim();
         assertEquals("+\"/t3\":{}", diff23);
         String diff13 = mk.diff(rev1, rev3, "/", 0).trim();
-        assertEquals("+\"/t2\":{}+\"/t3\":{}", diff13);
+        assertThat(diff13, anyOf(equalTo("+\"/t2\":{}+\"/t3\":{}"), equalTo("+\"/t3\":{}+\"/t2\":{}")));
         String diff34 = mk.diff(rev3, rev4, "/", 0).trim();
         assertEquals("^\"/t3\":{}", diff34);
     }