Author: mreutegg
Date: Tue Jan 26 07:15:28 2016
New Revision: 1726741

URL: http://svn.apache.org/viewvc?rev=1726741&view=rev
Log:
OAK-3833: Allow to acquire multiple locks

Apply patch by Tomek Rekawek

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

Added: 
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=1726741&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkLock.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/BulkLock.java
 Tue Jan 26 07:15:28 2016
@@ -0,0 +1,69 @@
+/*
+ * 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 com.google.common.collect.Lists.reverse;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * This class exposes a list of locks as a single Lock instance.
+ */
+class BulkLock implements Lock {
+
+    private final List<Lock> locks;
+
+    public BulkLock(List<Lock> locks) {
+        this.locks = locks;
+    }
+
+    @Override
+    public void lock() {
+        for (Lock l : locks) {
+            l.lock();
+        }
+    }
+
+    public void unlock() {
+        for (Lock l : reverse(locks)) {
+            l.unlock();
+        }
+    }
+
+    @Override
+    public void lockInterruptibly() throws InterruptedException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean tryLock() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean tryLock(long time, TimeUnit unit) throws 
InterruptedException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Condition newCondition() {
+        throw new UnsupportedOperationException();
+    }
+}

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

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/NodeDocumentLocks.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/NodeDocumentLocks.java?rev=1726741&r1=1726740&r2=1726741&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/NodeDocumentLocks.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/NodeDocumentLocks.java
 Tue Jan 26 07:15:28 2016
@@ -16,16 +16,25 @@
  */
 package org.apache.jackrabbit.oak.plugins.document.locks;
 
+import java.util.Collection;
 import java.util.concurrent.locks.Lock;
 
 public interface NodeDocumentLocks {
 
     /**
-     * Acquires a log for the given key.
+     * Acquires a lock for the given key.
      *
      * @param key a key.
      * @return the acquired lock for the given key.
      */
     Lock acquire(String key);
 
+    /**
+     * Acquires locks for the given keys. Locks are sorted before the operation
+     * to avoid deadlocks.
+     *
+     * @param keys keys
+     * @return the object wrapping the acquired locks
+     */
+    Lock acquire(Collection<String> keys);
 }

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/StripedNodeDocumentLocks.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/StripedNodeDocumentLocks.java?rev=1726741&r1=1726740&r2=1726741&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/StripedNodeDocumentLocks.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/StripedNodeDocumentLocks.java
 Tue Jan 26 07:15:28 2016
@@ -16,21 +16,46 @@
  */
 package org.apache.jackrabbit.oak.plugins.document.locks;
 
+import static com.google.common.base.Predicates.equalTo;
+import static com.google.common.base.Predicates.not;
+import static com.google.common.collect.Iterables.filter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 import java.util.concurrent.locks.Lock;
 
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+
 import com.google.common.util.concurrent.Striped;
 
 public class StripedNodeDocumentLocks implements NodeDocumentLocks {
 
+    private static final String ROOT = Utils.getIdFromPath("/");
+
     /**
      * Locks to ensure cache consistency on reads, writes and invalidation.
      */
     private final Striped<Lock> locks = Striped.lock(4096);
-    private final Lock rootLock = Striped.lock(1).get("0:/");
+    private final Lock rootLock = Striped.lock(1).get(ROOT);
 
     @Override
     public Lock acquire(String key) {
-        Lock lock = "0:/".equals(key) ? rootLock : locks.get(key);
+        Lock lock = ROOT.equals(key) ? rootLock : locks.get(key);
+        lock.lock();
+        return lock;
+    }
+
+    @Override
+    public Lock acquire(Collection<String> keys) {
+        List<Lock> lockList = new ArrayList<Lock>();
+        if (keys.contains(ROOT)) {
+            lockList.add(rootLock);
+        }
+        for (Lock l : locks.bulkGet(filter(keys, not(equalTo(ROOT))))) {
+            lockList.add(l);
+        }
+        Lock lock = new BulkLock(lockList);
         lock.lock();
         return lock;
     }

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=1726741&r1=1726740&r2=1726741&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
 Tue Jan 26 07:15:28 2016
@@ -18,6 +18,10 @@ 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;
@@ -28,6 +32,8 @@ import javax.annotation.Nonnull;
 
 import org.apache.jackrabbit.oak.plugins.document.util.Utils;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
 import com.google.common.util.concurrent.Striped;
 
 public class TreeNodeDocumentLocks implements NodeDocumentLocks {
@@ -53,7 +59,7 @@ public class TreeNodeDocumentLocks imple
     private volatile AtomicLong lockAcquisitionCounter;
 
     /**
-     * Acquires a log for the given key. The returned tree lock will also hold
+     * Acquires a lock for the given key. The returned tree lock will also hold
      * a shared lock on the parent key.
      *
      * @param key a key.
@@ -68,6 +74,42 @@ public class TreeNodeDocumentLocks imple
         lock.lock();
         return lock;
     }
+
+    /**
+     * 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.
+     * <p>
+     * Since we only acquire a parentLock.read, there's no danger of
+     * deadlock caused by interleaving locks from two different stripes by two
+     * threads. The only place where the parentLock.write is acquired is the
+     * {@link #acquireExclusive(String)} and that method doesn't acquire locks 
in bulk.
+     */
+    @Override
+    public Lock acquire(Collection<String> keys) {
+        if (lockAcquisitionCounter != null) {
+            lockAcquisitionCounter.addAndGet(keys.size());
+        }
+
+        Iterable<String> parentKeys = Iterables.transform(keys, new 
Function<String, String>() {
+            @Override
+            public String apply(String keys) {
+                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);
+        lock.lock();
+        return lock;
+    }
 
     /**
      * Acquires an exclusive lock on the given parent key. Use this method to

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/locks/NodeDocumentLocksTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/locks/NodeDocumentLocksTest.java?rev=1726741&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/locks/NodeDocumentLocksTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/locks/NodeDocumentLocksTest.java
 Tue Jan 26 07:15:28 2016
@@ -0,0 +1,93 @@
+/*
+ * 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.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NodeDocumentLocksTest {
+
+    @Parameters(name = "{1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { { new 
StripedNodeDocumentLocks(), "StripedNodeDocumentLocks" },
+                { new TreeNodeDocumentLocks(), "TreeNodeDocumentLocks" } });
+    }
+
+    private final NodeDocumentLocks locks;
+
+    public NodeDocumentLocksTest(NodeDocumentLocks locks, String name) {
+        this.locks = locks;
+    }
+
+    @Test
+    public void testBulkAcquireNonConflicting() throws InterruptedException {
+        testBulkAcquire(false);
+    }
+
+    @Test
+    public void testBulkAcquireConflicting() throws InterruptedException {
+        testBulkAcquire(true);
+    }
+
+    private void testBulkAcquire(boolean conflicting) throws 
InterruptedException {
+        int threadCount = 10;
+        int locksPerThread = 100;
+
+        List<Thread> threads = new ArrayList<Thread>();
+        String keyName = conflicting ? "lock_%d" : "lock_%d_%d";
+        for (int i = 0; i < threadCount; i++) {
+            final List<String> keys = new ArrayList<String>(locksPerThread);
+            for (int j = 0; j < locksPerThread; j++) {
+                keys.add(String.format(keyName, j, i));
+            }
+            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 (Thread t : threads) {
+            t.start();
+        }
+        for (Thread t : threads) {
+            t.join(10000);
+            if (t.isAlive()) {
+                fail("Thread hasn't stopped in 10s");
+            }
+        }
+    }
+
+}

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


Reply via email to