Author: mreutegg
Date: Mon Feb  1 12:47:32 2016
New Revision: 1727929

URL: http://svn.apache.org/viewvc?rev=1727929&view=rev
Log:
OAK-3949: Deadlock with bulk acquire of TreeLocks

Applied Tomek Rekawek's patch.

Added:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkReadWriteLock.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/locks/TreeNodeDocumentsLocksTest.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkLock.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/TreeNodeDocumentLocks.java

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkLock.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkLock.java?rev=1727929&r1=1727928&r2=1727929&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkLock.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkLock.java
 Mon Feb  1 12:47:32 2016
@@ -23,6 +23,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 
+import com.google.common.collect.Lists;
+
 /**
  * This class exposes a list of locks as a single Lock instance.
  */
@@ -30,8 +32,8 @@ class BulkLock implements Lock {
 
     private final List<Lock> locks;
 
-    public BulkLock(List<Lock> locks) {
-        this.locks = locks;
+    public BulkLock(Iterable<Lock> locks) {
+        this.locks = Lists.newArrayList(locks);
     }
 
     @Override

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkReadWriteLock.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkReadWriteLock.java?rev=1727929&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkReadWriteLock.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkReadWriteLock.java
 Mon Feb  1 12:47:32 2016
@@ -0,0 +1,56 @@
+/*
+ * 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.locks;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+
+/**
+ * This class exposes a list of ReadWriteLocks as a single Lock instance.
+ */
+class BulkReadWriteLock implements ReadWriteLock {
+
+    private Iterable<ReadWriteLock> locks;
+
+    public BulkReadWriteLock(Iterable<ReadWriteLock> locks) {
+        this.locks = locks;
+    }
+
+    @Override
+    public Lock readLock() {
+        return new BulkLock(Iterables.transform(locks, new 
Function<ReadWriteLock, Lock>() {
+            @Override
+            public Lock apply(ReadWriteLock input) {
+                return input.readLock();
+            }
+        }));
+    }
+
+    @Override
+    public Lock writeLock() {
+        return new BulkLock(Iterables.transform(locks, new 
Function<ReadWriteLock, Lock>() {
+            @Override
+            public Lock apply(ReadWriteLock input) {
+                return input.writeLock();
+            }
+        }));
+    }
+
+}

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

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/TreeNodeDocumentLocks.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/TreeNodeDocumentLocks.java?rev=1727929&r1=1727928&r2=1727929&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/TreeNodeDocumentLocks.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/TreeNodeDocumentLocks.java
 Mon Feb  1 12:47:32 2016
@@ -18,10 +18,7 @@ package org.apache.jackrabbit.oak.plugin
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.Condition;
@@ -78,9 +75,8 @@ public class TreeNodeDocumentLocks imple
     /**
      * This implementation creates two sequences of locks (for the keys and for
      * the their parents) using {@link #locks} and {@link #parentLocks}. Then
-     * both sequences are zipped into pairs (parentLock, lock) and passed to 
the
-     * {@link TreeLock#shared(ReadWriteLock, Lock)}. After that all tree locks
-     * are acquired.
+     * all parent locks are acquired first and in a second step the locks for
+     * the actual keys.
      * <p>
      * Since we only acquire a parentLock.read, there's no danger of
      * deadlock caused by interleaving locks from two different stripes by two
@@ -99,14 +95,11 @@ public class TreeNodeDocumentLocks imple
                 return getParentId(keys);
             }
         });
-        Iterator<Lock> lockIt = locks.bulkGet(keys).iterator();
-        Iterator<ReadWriteLock> parentLockIt = 
parentLocks.bulkGet(parentKeys).iterator();
 
-        List<Lock> acquired = new ArrayList<Lock>(keys.size());
-        while (lockIt.hasNext()) {
-            acquired.add(TreeLock.shared(parentLockIt.next(), lockIt.next()));
-        }
-        Lock lock = new BulkLock(acquired);
+        ReadWriteLock bulkParentLock = new 
BulkReadWriteLock(parentLocks.bulkGet(parentKeys));
+        Lock bulkChildrenLock = new BulkLock(locks.bulkGet(keys));
+
+        Lock lock = TreeLock.shared(bulkParentLock, bulkChildrenLock);
         lock.lock();
         return lock;
     }

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/locks/TreeNodeDocumentsLocksTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/locks/TreeNodeDocumentsLocksTest.java?rev=1727929&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/locks/TreeNodeDocumentsLocksTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/locks/TreeNodeDocumentsLocksTest.java
 Mon Feb  1 12:47:32 2016
@@ -0,0 +1,95 @@
+/*
+ * 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.locks;
+
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+
+import org.junit.Test;
+
+public class TreeNodeDocumentsLocksTest {
+
+    /**
+     * Test for the OAK-3949. It uses multiple threads to acquire a shared lock
+     * collection. There's also another set of threads that acquires parent
+     * locks exclusively. Such combination can't lead to a deadlock.
+     */
+    @Test
+    public void testBulkAcquire() throws InterruptedException {
+        int threadCount = 10;
+        int locksCount = 100;
+
+        final TreeNodeDocumentLocks locks = new TreeNodeDocumentLocks();
+        List<Thread> threads = new ArrayList<Thread>();
+        for (int i = 0; i < threadCount; i++) {
+            final List<String> keys = new ArrayList<String>(locksCount);
+            for (int j = 0; j < locksCount; j++) {
+                keys.add(String.format("2:/parent_%d/lock_%d", j, j));
+            }
+            threads.add(new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    Lock lock = locks.acquire(keys);
+                    try {
+                        Thread.sleep(100);
+                    } catch (InterruptedException e) {
+                    } finally {
+                        lock.unlock();
+                    }
+                }
+            }));
+        }
+
+        for (int j = 0; j < threadCount; j++) {
+            final int from = j * 10;
+            final int to = (j + 1) * 10;
+            threads.add(new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        for (int i = from; i < to; i++) {
+                            Lock parentLock = 
locks.acquireExclusive(String.format("1:/parent_%d", i));
+                            Thread.sleep(100);
+                            Lock childLock = 
locks.acquire(String.format("2:/parent_%d/lock_%d", i, i));
+                            Thread.sleep(100);
+                            childLock.unlock();
+                            Thread.sleep(100);
+                            parentLock.unlock();
+                        }
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }));
+        }
+
+        Collections.shuffle(threads);
+        for (Thread t : threads) {
+            t.start();
+        }
+
+        for (Thread t : threads) {
+            t.join(10000);
+            if (t.isAlive()) {
+                fail("Thread hasn't stopped in 10s");
+            }
+        }
+    }
+}
\ No newline at end of file

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


Reply via email to