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 mr...@apache.org on 2013/07/04 16:35:33 UTC

svn commit: r1499769 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/mongomk/ test/java/org/apache/jackrabbit/oak/plugins/mongomk/

Author: mreutegg
Date: Thu Jul  4 14:35:32 2013
New Revision: 1499769

URL: http://svn.apache.org/r1499769
Log:
OAK-619 Lock-free MongoMK implementation
- More branch isolation tests
- Do not update _lastRev for branch commits
- Apply _lastRev of branch commits on merge

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnsavedModifications.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Branch.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Commit.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMK.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Node.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnmergedBranches.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/ClusterTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMKBranchTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMKTestBase.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Branch.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Branch.java?rev=1499769&r1=1499768&r2=1499769&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Branch.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Branch.java Thu Jul  4 14:35:32 2013
@@ -17,8 +17,10 @@
 package org.apache.jackrabbit.oak.plugins.mongomk;
 
 import java.util.SortedSet;
+import java.util.TreeMap;
 import java.util.TreeSet;
 
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -32,17 +34,19 @@ class Branch {
     /**
      * The commits to the branch
      */
-    private final SortedSet<Revision> commits;
+    private final TreeMap<Revision, UnsavedModifications> commits;
 
     private final Revision base;
 
-    Branch(@Nonnull SortedSet<Revision> commits, @Nonnull Revision base) {
-        this.commits = checkNotNull(commits);
-        this.base = base;
-    }
-
-    synchronized Revision getHead() {
-        return commits.last();
+    Branch(@Nonnull SortedSet<Revision> commits,
+           @Nonnull Revision base,
+           @Nonnull Revision.RevisionComparator comparator) {
+        this.base = checkNotNull(base);
+        this.commits = new TreeMap<Revision, UnsavedModifications>(
+                checkNotNull(comparator));
+        for (Revision r : commits) {
+            this.commits.put(r, new UnsavedModifications());
+        }
     }
 
     Revision getBase() {
@@ -50,19 +54,83 @@ class Branch {
     }
 
     synchronized void addCommit(@Nonnull Revision r) {
-        checkArgument(commits.comparator().compare(r, commits.last()) > 0);
-        commits.add(r);
+        checkArgument(commits.comparator().compare(r, commits.lastKey()) > 0);
+        commits.put(r, new UnsavedModifications());
     }
 
     synchronized SortedSet<Revision> getCommits() {
-        return new TreeSet<Revision>(commits);
+        SortedSet<Revision> revisions = new TreeSet<Revision>(commits.comparator());
+        revisions.addAll(commits.keySet());
+        return revisions;
+    }
+
+    synchronized boolean hasCommits() {
+        return !commits.isEmpty();
     }
 
     synchronized boolean containsCommit(@Nonnull Revision r) {
-        return commits.contains(r);
+        return commits.containsKey(r);
     }
 
     public synchronized void removeCommit(@Nonnull Revision rev) {
         commits.remove(rev);
     }
+
+    /**
+     * Gets the unsaved modifications for the given branch commit revision.
+     *
+     * @param r a branch commit revision.
+     * @return the unsaved modification for the given branch commit.
+     * @throws IllegalArgumentException if there is no commit with the given
+     *                                  revision.
+     */
+    @Nonnull
+    public synchronized UnsavedModifications getModifications(@Nonnull Revision r) {
+        UnsavedModifications modifications = commits.get(r);
+        if (modifications == null) {
+            throw new IllegalArgumentException(
+                    "Revision " + r + " is not a commit in this branch");
+        }
+        return modifications;
+    }
+
+    /**
+     * Applies all unsaved modification of this branch to the given collection
+     * of unsaved trunk modifications. A modification is only applied if there
+     * is no modification in <code>trunk</code> for a given path or if the
+     * <code>trunk</code> modification is earlier.
+     *
+     * @param trunk the unsaved trunk modifications.
+     */
+    public synchronized void applyTo(@Nonnull UnsavedModifications trunk) {
+        checkNotNull(trunk);
+        for (UnsavedModifications modifications : commits.values()) {
+            modifications.applyTo(trunk);
+        }
+    }
+
+    /**
+     * Gets the most recent unsaved last revision at <code>readRevision</code>
+     * or earlier in this branch for the given <code>path</code>.
+     *
+     * @param path         the path of a node.
+     * @param readRevision the read revision.
+     * @return the most recent unsaved last revision or <code>null</code> if
+     *         there is none in this branch.
+     */
+    @CheckForNull
+    public synchronized Revision getUnsavedLastRevision(String path,
+                                                        Revision readRevision) {
+        for (Revision r : commits.descendingKeySet()) {
+            if (readRevision.compareRevisionTime(r) < 0) {
+                continue;
+            }
+            UnsavedModifications modifications = commits.get(r);
+            Revision modRevision = modifications.get(path);
+            if (modRevision != null) {
+                return modRevision;
+            }
+        }
+        return null;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Commit.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Commit.java?rev=1499769&r1=1499768&r2=1499769&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Commit.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Commit.java Thu Jul  4 14:35:32 2013
@@ -117,7 +117,6 @@ public class Commit {
         UpdateOp op = getUpdateOperationForNode(path);
         String key = Utils.escapePropertyName(propertyName);
         op.setMapEntry(key, revision.toString(), value);
-        op.setMapEntry(UpdateOp.LAST_REV, "" + revision.getClusterId(), revision.toString());        
     }
 
     void addNode(Node n) {
@@ -136,14 +135,14 @@ public class Commit {
     void apply() {
         if (!operations.isEmpty()) {
             applyToDocumentStore();
-            applyToCache();
+            applyToCache(false);
         }
     }
 
     void prepare(Revision baseRevision) {
         if (!operations.isEmpty()) {
             applyToDocumentStore(baseRevision);
-            applyToCache();
+            applyToCache(true);
         }
     }
 
@@ -197,7 +196,11 @@ public class Commit {
         UpdateOp commitRoot = getUpdateOperationForNode(commitRootPath);
         for (String p : operations.keySet()) {
             UpdateOp op = operations.get(p);
-            op.setMapEntry(UpdateOp.LAST_REV, "" + revision.getClusterId(), revision.toString());
+            if (baseBranchRevision == null) {
+                // only apply _lastRev for trunk commits, _lastRev for
+                // branch commits only become visible on merge
+                op.setMapEntry(UpdateOp.LAST_REV, "" + revision.getClusterId(), revision.toString());
+            }
             if (op.isNew) {
                 op.setMapEntry(UpdateOp.DELETED, revision.toString(), "false");
             }
@@ -465,7 +468,7 @@ public class Commit {
     /**
      * Apply the changes to the MongoMK (to update the cache).
      */
-    public void applyToCache() {
+    public void applyToCache(boolean isBranchCommit) {
         HashMap<String, ArrayList<String>> nodesWithChangedChildren = new HashMap<String, ArrayList<String>>();
         ArrayList<String> addOrRemove = new ArrayList<String>();
         addOrRemove.addAll(addedNodes);
@@ -497,7 +500,7 @@ public class Commit {
             boolean isWritten = op != null;
             boolean isDelete = op != null && op.isDelete;
             mk.applyChanges(revision, path, 
-                    isNew, isDelete, isWritten, 
+                    isNew, isDelete, isWritten, isBranchCommit,
                     added, removed);
         }
     }
@@ -536,7 +539,6 @@ public class Commit {
         UpdateOp op = getUpdateOperationForNode(path);
         op.setDelete(true);
         op.setMapEntry(UpdateOp.DELETED, revision.toString(), "true");
-        op.setMapEntry(UpdateOp.LAST_REV, "" + revision.getClusterId(), revision.toString());
     }
 
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMK.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMK.java?rev=1499769&r1=1499768&r2=1499769&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMK.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMK.java Thu Jul  4 14:35:32 2013
@@ -21,6 +21,7 @@ import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -171,8 +172,7 @@ public class MongoMK implements MicroKer
      * 
      * Key: path, value: revision.
      */
-    private final Map<String, Revision> unsavedLastRevisions = 
-            new ConcurrentHashMap<String, Revision>();
+    private final UnsavedModifications unsavedLastRevisions = new UnsavedModifications();
     
     /**
      * The last known revision for each cluster instance.
@@ -365,10 +365,10 @@ public class MongoMK implements MicroKer
     }
     
     void backgroundWrite() {
-        if (unsavedLastRevisions.size() == 0) {
+        if (unsavedLastRevisions.getPaths().size() == 0) {
             return;
         }
-        ArrayList<String> paths = new ArrayList<String>(unsavedLastRevisions.keySet());
+        ArrayList<String> paths = new ArrayList<String>(unsavedLastRevisions.getPaths());
         // sort by depth (high depth first), then path
         Collections.sort(paths, new Comparator<String>() {
 
@@ -655,43 +655,19 @@ public class MongoMK implements MicroKer
         return c;
     }
 
-    private Node readNode(String path, Revision rev) {
+    private Node readNode(String path, Revision readRevision) {
         String id = Utils.getIdFromPath(path);
         Map<String, Object> map = store.find(DocumentStore.Collection.NODES, id);
         if (map == null) {
             return null;
         }
-        Revision min = getLiveRevision(map, rev);
+        Revision min = getLiveRevision(map, readRevision);
         if (min == null) {
             // deleted
             return null;
         }
-        Node n = new Node(path, rev);
-        Revision lastRevision = null;
-        Revision revision =  unsavedLastRevisions.get(path);
-        if (revision != null) {
-            if (isRevisionNewer(revision, rev)) {
-                // at most the read revision
-                revision = rev;
-            }
-            lastRevision = revision;
-        }
+        Node n = new Node(path, readRevision);
         for (String key : map.keySet()) {
-            if (key.equals(UpdateOp.LAST_REV)) {
-                Object v = map.get(key);
-                @SuppressWarnings("unchecked")
-                Map<String, String> valueMap = (Map<String, String>) v;
-                for (String r : valueMap.keySet()) {
-                    revision = Revision.fromString(valueMap.get(r));
-                    if (isRevisionNewer(revision, rev)) {
-                        // at most the read revision
-                        revision = rev;
-                    }
-                    if (lastRevision == null || isRevisionNewer(revision, lastRevision)) {
-                        lastRevision = revision;
-                    }
-                }
-            }
             if (!Utils.isPropertyName(key)) {
                 continue;
             }
@@ -704,11 +680,65 @@ public class MongoMK implements MicroKer
                     // use descending keys (newest first) if map is sorted
                     valueMap = ((TreeMap<String, String>) valueMap).descendingMap();
                 }
-                String value = getLatestValue(valueMap, min, rev);
+                String value = getLatestValue(valueMap, min, readRevision);
                 String propertyName = Utils.unescapePropertyName(key);
                 n.setProperty(propertyName, value);
             }
         }
+
+        // when was this node last modified?
+        Revision lastRevision = null;
+        Map<String, Revision> lastRevs = new HashMap<String, Revision>();
+        @SuppressWarnings("unchecked")
+        Map<String, String> valueMap = (Map<String, String>) map.get(UpdateOp.LAST_REV);
+        if (valueMap != null) {
+            for (String clusterId : valueMap.keySet()) {
+                lastRevs.put(clusterId, Revision.fromString(valueMap.get(clusterId)));
+            }
+        }
+        // overlay with unsaved last modified from this instance
+        Revision lastModified = unsavedLastRevisions.get(path);
+        Branch branch = branches.getBranch(readRevision);
+        if (lastModified != null) {
+            if (branch != null) {
+                // visible from this branch if revision is
+                // not newer than base of branch
+                if (!isRevisionNewer(lastModified, branch.getBase())) {
+                    lastRevs.put(String.valueOf(clusterId), lastModified);
+                }
+            } else {
+                // non-branch read -> check if newer
+                if (isRevisionNewer(lastModified, readRevision)) {
+                    // at most readRevision
+                    lastRevs.put(String.valueOf(clusterId), readRevision);
+                } else {
+                    lastRevs.put(String.valueOf(clusterId), lastModified);
+                }
+            }
+        }
+        if (branch != null) {
+            // read from a branch
+            // -> overlay with unsaved last revs from branch
+            Revision r = branch.getUnsavedLastRevision(path, readRevision);
+            if (r != null) {
+                lastRevs.put(String.valueOf(clusterId), r);
+            }
+        }
+
+        for (String r : lastRevs.keySet()) {
+            lastModified = lastRevs.get(r);
+            if (isRevisionNewer(lastModified, readRevision)) {
+                // at most the read revision
+                lastModified = readRevision;
+            }
+            if (lastRevision == null || isRevisionNewer(lastModified, lastRevision)) {
+                lastRevision = lastModified;
+            }
+        }
+        if (lastRevision == null) {
+            // use readRevision if none found
+            lastRevision = readRevision;
+        }
         n.setLastRevision(lastRevision);
         return n;
     }
@@ -1089,7 +1119,7 @@ public class MongoMK implements MicroKer
             } finally {
                 if (!success) {
                     b.removeCommit(rev);
-                    if (b.getCommits().isEmpty()) {
+                    if (!b.hasCommits()) {
                         branches.remove(b);
                     }
                 }
@@ -1432,6 +1462,7 @@ public class MongoMK implements MicroKer
             }
             if (store.findAndUpdate(DocumentStore.Collection.NODES, op) != null) {
                 // remove from branchCommits map after successful update
+                b.applyTo(unsavedLastRevisions);
                 branches.remove(b);
             } else {
                 throw new MicroKernelException("Conflicting concurrent change. Update operation failed: " + op);
@@ -1499,18 +1530,28 @@ public class MongoMK implements MicroKer
      * @param isNew whether this is a new node
      * @param isDelete whether the node is deleted
      * @param isWritten whether the MongoDB documented was added / updated
+     * @param isBranchCommit whether this is from a branch commit
      * @param added the list of added child nodes
      * @param removed the list of removed child nodes
+     *
      */
     public void applyChanges(Revision rev, String path, 
-            boolean isNew, boolean isDelete, boolean isWritten, 
-            ArrayList<String> added, ArrayList<String> removed) {
-        if (!isWritten) {
-            Revision prev = unsavedLastRevisions.put(path, rev);
+            boolean isNew, boolean isDelete, boolean isWritten,
+            boolean isBranchCommit, ArrayList<String> added,
+            ArrayList<String> removed) {
+        UnsavedModifications unsaved = unsavedLastRevisions;
+        if (isBranchCommit) {
+            unsaved = branches.getBranch(rev).getModifications(rev);
+        }
+        // track unsaved modifications of nodes that were not
+        // written in the commit (implicitly modified parent)
+        // or any modification if this is a branch commit
+        if (!isWritten || isBranchCommit) {
+            Revision prev = unsaved.put(path, rev);
             if (prev != null) {
                 if (isRevisionNewer(prev, rev)) {
                     // revert
-                    unsavedLastRevisions.put(path, prev);
+                    unsaved.put(path, prev);
                     String msg = String.format("Attempt to update " +
                             "unsavedLastRevision for %s with %s, which is " +
                             "older than current %s.",
@@ -1521,7 +1562,7 @@ public class MongoMK implements MicroKer
         } else {
             // the document was updated:
             // we no longer need to update it in a background process
-            unsavedLastRevisions.remove(path);
+            unsaved.remove(path);
         }
         Children c = nodeChildrenCache.getIfPresent(path + "@" + rev);
         if (isNew || (!isDelete && c != null)) {
@@ -1555,7 +1596,7 @@ public class MongoMK implements MicroKer
     }
 
     public int getPendingWriteCount() {
-        return unsavedLastRevisions.size();
+        return unsavedLastRevisions.getPaths().size();
     }
 
     public boolean isCached(String path) {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Node.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Node.java?rev=1499769&r1=1499768&r2=1499769&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Node.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/Node.java Thu Jul  4 14:35:32 2013
@@ -79,7 +79,6 @@ public class Node implements CacheValue 
         op.set(UpdateOp.ID, id);
         Commit.setModified(op, rev);
         op.setMapEntry(UpdateOp.DELETED, rev.toString(), "false");
-        op.setMapEntry(UpdateOp.LAST_REV, "" + rev.getClusterId(), rev.toString());
         for (String p : properties.keySet()) {
             String key = Utils.escapePropertyName(p);
             op.setMapEntry(key, rev.toString(), properties.get(p));

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnmergedBranches.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnmergedBranches.java?rev=1499769&r1=1499768&r2=1499769&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnmergedBranches.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnmergedBranches.java Thu Jul  4 14:35:32 2013
@@ -93,7 +93,7 @@ class UnmergedBranches {
                     commits.add(base);
                     base = tmp.remove(base);
                 }
-                branches.add(new Branch(commits, base));
+                branches.add(new Branch(commits, base, comparator));
             }
         }
     }
@@ -109,7 +109,7 @@ class UnmergedBranches {
     Branch create(@Nonnull Revision base, @Nonnull Revision initial) {
         SortedSet<Revision> commits = new TreeSet<Revision>(comparator);
         commits.add(checkNotNull(initial));
-        Branch b = new Branch(commits, checkNotNull(base));
+        Branch b = new Branch(commits, checkNotNull(base), comparator);
         synchronized (branches) {
             branches.add(b);
         }

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnsavedModifications.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnsavedModifications.java?rev=1499769&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnsavedModifications.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnsavedModifications.java Thu Jul  4 14:35:32 2013
@@ -0,0 +1,73 @@
+/*
+ * 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.mongomk;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Keeps track of when nodes where last modified. To be persisted later by
+ * a background thread.
+ */
+class UnsavedModifications {
+
+    private final ConcurrentHashMap<String, Revision> map = new ConcurrentHashMap<String, Revision>();
+
+    @CheckForNull
+    public Revision put(@Nonnull String path, @Nonnull Revision revision) {
+        return map.put(checkNotNull(path), checkNotNull(revision));
+    }
+
+    @CheckForNull
+    public Revision get(String path) {
+        return map.get(path);
+    }
+
+    @CheckForNull
+    public Revision remove(String path) {
+        return map.remove(path);
+    }
+
+    @Nonnull
+    public Collection<String> getPaths() {
+        return map.keySet();
+    }
+
+    /**
+     * Applies all modifications from this instance to the <code>other</code>.
+     * A modification is only applied if there is no modification in other
+     * for a given path or if the other modification is earlier.
+     *
+     * @param other the other <code>UnsavedModifications</code>.
+     */
+    public void applyTo(UnsavedModifications other) {
+        for (Map.Entry<String, Revision> entry : map.entrySet()) {
+            Revision r = other.map.putIfAbsent(entry.getKey(), entry.getValue());
+            if (r != null) {
+                if (r.compareRevisionTime(entry.getValue()) < 0) {
+                    other.map.put(entry.getKey(), entry.getValue());
+                }
+            }
+        }
+    }
+}

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

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/mongomk/UnsavedModifications.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/ClusterTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/ClusterTest.java?rev=1499769&r1=1499768&r2=1499769&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/ClusterTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/ClusterTest.java Thu Jul  4 14:35:32 2013
@@ -168,20 +168,23 @@ public class ClusterTest {
     }    
     
     @Test
-    public void clusterBranchInVisibility() {
-        MongoMK mk1 = createMK(0);
+    public void clusterBranchInVisibility() throws InterruptedException {
+        MongoMK mk1 = createMK(1);
         mk1.commit("/", "+\"regular\": {}", null, null);
         String b1 = mk1.branch(null);
         String b2 = mk1.branch(null);
         b1 = mk1.commit("/", "+\"branchVisible\": {}", b1, null);
         b2 = mk1.commit("/", "+\"branchInvisible\": {}", b2, null);
         mk1.merge(b1, null);
-        
-        MongoMK mk2 = createMK(0);
+        // mk1.merge only becomes visible to mk2 after async delay
+        // therefore dispose mk1 now to make sure it flushes
+        // unsaved last revisions
+        mk1.dispose();
+
+        MongoMK mk2 = createMK(2);
         String nodes = mk2.getNodes("/", null, 0, 0, 100, null);
         assertEquals("{\"branchVisible\":{},\"regular\":{},\":childNodeCount\":2}", nodes);
         
-        mk1.dispose();
         mk2.dispose();
     }
     

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMKBranchTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMKBranchTest.java?rev=1499769&r1=1499768&r2=1499769&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMKBranchTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMKBranchTest.java Thu Jul  4 14:35:32 2013
@@ -20,6 +20,7 @@ import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
 import org.junit.Test;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -74,4 +75,55 @@ public class MongoMKBranchTest extends B
         JSONObject obj = (JSONObject) parser.parse(json);
         assertTrue(obj.containsKey("foo"));
     }
+
+    @Test
+    public void branchIsolation1() throws Exception {
+        String filter = "{\"properties\":[\"*\",\":hash\",\":id\"]}";
+        String baseRev = mk.commit("/", "+\"test\":{\"node\":{}}", null, null);
+
+        // branch commit under /test/node
+        String branchRev = mk.branch(baseRev);
+        String branchRev1 = mk.commit("/test/node", "+\"branch-node\":{}", branchRev, null);
+
+        // trunk commit under /test/node
+        String rev1 = mk.commit("/test/node", "+\"trunk-node\":{}", null, null);
+
+        // branch commit on /
+        String branchRev2 = mk.commit("/", "+\"other\":{}", branchRev1, null);
+
+        // get /test on branch and use returned identifier to get next level
+        String json = mk.getNodes("/test", branchRev2, 0, 0, 1000, filter);
+
+        JSONObject test = parseJSONObject(json);
+        String id = resolveValue(test, ":id").toString();
+        String revision = id.split("@")[1];
+        assertNodesExist(revision, "/test/node/branch-node");
+        assertNodesNotExist(revision, "/test/node/trunk-node");
+    }
+
+    @Test
+    public void branchIsolation2() throws Exception {
+        String filter = "{\"properties\":[\"*\",\":hash\",\":id\"]}";
+        String baseRev = mk.commit("/", "+\"test\":{\"node\":{}}", null, null);
+        String branchRev = mk.branch(baseRev);
+
+        // trunk commit under /test/node
+        String rev1 = mk.commit("/test/node", "+\"trunk-node\":{}", null, null);
+
+        // branch commit under /test/node
+        String branchRev1 = mk.commit("/test/node", "+\"branch-node\":{}", branchRev, null);
+
+        // trunk commit on /
+        String rev2 = mk.commit("/", "+\"other\":{}", null, null);
+
+        // get /test on trunk and use returned identifier to get next level
+        String json = mk.getNodes("/test", rev2, 0, 0, 1000, filter);
+
+        JSONObject test = parseJSONObject(json);
+        String id = resolveValue(test, ":id").toString();
+        String revision = id.split("@")[1];
+        assertEquals(rev1, revision);
+        assertNodesExist(revision, "/test/node/trunk-node");
+        assertNodesNotExist(revision, "/test/node/branch-node");
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMKTestBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMKTestBase.java?rev=1499769&r1=1499768&r2=1499769&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMKTestBase.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/mongomk/MongoMKTestBase.java Thu Jul  4 14:35:32 2013
@@ -163,7 +163,7 @@ public abstract class MongoMKTestBase {
         throw new AssertionError("failed to resolve JSONObject value at " + relPath + ": " + val);
     }
 
-    private static Object resolveValue(JSONObject obj, String relPath) {
+    protected static Object resolveValue(JSONObject obj, String relPath) {
         String[] names = relPath.split("/");
         Object val = obj;
         for (String name : names) {