Author: baedke
Date: Wed Aug 24 18:08:10 2016
New Revision: 1757557

URL: http://svn.apache.org/viewvc?rev=1757557&view=rev
Log:
OAK-4682: ConcurrentModificationException in JournalEntry.TreeNode

Merging fix into branch 1.4.

Modified:
    
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/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java

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=1757557&r1=1757556&r2=1757557&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
 Wed Aug 24 18:08:10 2016
@@ -393,7 +393,6 @@ public final class DocumentNodeStore
         if (builder.getLogging()) {
             s = new LoggingDocumentStoreWrapper(s);
         }
-        this.changes = Collection.JOURNAL.newDocument(s);
         this.executor = builder.getExecutor();
         this.clock = builder.getClock();
 
@@ -410,6 +409,7 @@ public final class DocumentNodeStore
             
clusterNodeInfo.setLeaseFailureHandler(builder.getLeaseFailureHandler());
         }
         this.store = s;
+        this.changes = newJournalEntry();
         this.clusterId = cid;
         this.branches = new UnmergedBranches();
         this.asyncDelay = builder.getAsyncDelay();
@@ -744,6 +744,16 @@ public final class DocumentNodeStore
         return diffCache.getStats();
     }
 
+    /**
+     * Returns the journal entry that will be stored in the journal with the
+     * next background updated.
+     *
+     * @return the current journal entry.
+     */
+    JournalEntry getCurrentJournalEntry() {
+        return changes;
+    }
+
     void invalidateDocChildrenCache() {
         docChildrenCache.invalidateAll();
     }
@@ -2024,7 +2034,7 @@ public final class DocumentNodeStore
             public void acquiring(Revision mostRecent) {
                 if (store.create(JOURNAL, 
singletonList(changes.asUpdateOp(mostRecent)))) {
                     // success: start with a new document
-                    changes = JOURNAL.newDocument(getDocumentStore());
+                    changes = newJournalEntry();
                 } else {
                     // fail: log and keep the changes
                     LOG.error("Failed to write to journal, accumulating 
changes for future write (~" + changes.getMemory()
@@ -2036,6 +2046,10 @@ public final class DocumentNodeStore
 
     //-----------------------------< internal 
>---------------------------------
 
+    private JournalEntry newJournalEntry() {
+        return new JournalEntry(store, true);
+    }
+
     /**
      * Performs an initial read of the _lastRevs on the root document and sets
      * the root state.

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=1757557&r1=1757556&r2=1757557&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
 Wed Aug 24 18:08:10 2016
@@ -78,8 +78,15 @@ public final class JournalEntry extends
 
     private volatile TreeNode changes = null;
 
+    private boolean concurrent;
+
     JournalEntry(DocumentStore store) {
+        this(store, false);
+    }
+
+    JournalEntry(DocumentStore store, boolean concurrent) {
         this.store = store;
+        this.concurrent = concurrent;
     }
 
     static StringSort newSorter() {
@@ -364,7 +371,7 @@ public final class JournalEntry extends
     @Nonnull
     private TreeNode getChanges() {
         if (changes == null) {
-            TreeNode node = new TreeNode();
+            TreeNode node = new TreeNode(concurrent);
             String c = (String) get(CHANGES);
             if (c != null) {
                 node.parse(new JsopTokenizer(c));
@@ -380,17 +387,22 @@ public final class JournalEntry extends
 
         private Map<String, TreeNode> children = NO_CHILDREN;
 
+        private final MapFactory mapFactory;
         private final TreeNode parent;
         private final String name;
 
         TreeNode() {
-            this(null, "");
+            this(false);
+        }
+
+        TreeNode(boolean concurrent) {
+            this(concurrent ? MapFactory.CONCURRENT : MapFactory.DEFAULT, 
null, "");
         }
 
-        TreeNode(TreeNode parent, String name) {
+        TreeNode(MapFactory mapFactory, TreeNode parent, String name) {
             checkArgument(!name.contains("/"),
                     "name must not contain '/': {}", name);
-
+            this.mapFactory = mapFactory;
             this.parent = parent;
             this.name = name;
         }
@@ -491,11 +503,11 @@ public final class JournalEntry extends
         @Nonnull
         private TreeNode getOrCreate(String name) {
             if (children == NO_CHILDREN) {
-                children = Maps.newHashMap();
+                children = mapFactory.newMap();
             }
             TreeNode c = children.get(name);
             if (c == null) {
-                c = new TreeNode(this, name);
+                c = new TreeNode(mapFactory, this, name);
                 children.put(name, c);
             }
             return c;
@@ -507,4 +519,23 @@ public final class JournalEntry extends
         void node(TreeNode node, String path) throws IOException;
     }
 
+    private interface MapFactory {
+
+        MapFactory DEFAULT = new MapFactory() {
+            @Override
+            public Map<String, TreeNode> newMap() {
+                return Maps.newHashMap();
+            }
+        };
+
+        MapFactory CONCURRENT = new MapFactory() {
+            @Override
+            public Map<String, TreeNode> newMap() {
+                return Maps.newConcurrentMap();
+            }
+        };
+
+        Map<String, TreeNode> newMap();
+    }
+
 }

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=1757557&r1=1757556&r2=1757557&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
 Wed Aug 24 18:08:10 2016
@@ -213,4 +213,32 @@ public class JournalEntryTest {
             assertTrue(loaderCalled.get());
         }
     }
+
+    // OAK-4682
+    @Test
+    public void concurrentModification() throws Exception {
+        DocumentNodeStore store = new DocumentMK.Builder().getNodeStore();
+        try {
+            final JournalEntry entry = store.getCurrentJournalEntry();
+            Thread t = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = 0; i < 100000; i++) {
+                        entry.modified("/node-" + i);
+                    }
+                }
+            });
+            t.start();
+            StringSort sort = JournalEntry.newSorter();
+            try {
+                entry.addTo(sort);
+            } finally {
+                sort.close();
+            }
+            t.join();
+        } finally {
+            store.dispose();
+        }
+    }
+
 }


Reply via email to