This is an automated email from the ASF dual-hosted git repository.

daim pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 0612aaebc5 OAK-12065 : added class to split locks similar to guava's 
Striped (#2692)
0612aaebc5 is described below

commit 0612aaebc5915ce2e00572ae31543c8d79583230
Author: Rishabh Kumar <[email protected]>
AuthorDate: Wed Jan 21 13:59:50 2026 +0530

    OAK-12065 : added class to split locks similar to guava's Striped (#2692)
    
    * OAK-12065 : added unit class to strip locks similar to guava's Striped
    
    * OAK-12065 : incorporated review comments
---
 .../commons/internal/concurrent/StripedLocks.java  |  56 +++++++++
 .../commons/internal/concurrent/package-info.java  |   2 +-
 .../internal/concurrent/StripedLocksTest.java      | 129 +++++++++++++++++++++
 3 files changed, 186 insertions(+), 1 deletion(-)

diff --git 
a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/StripedLocks.java
 
b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/StripedLocks.java
new file mode 100644
index 0000000000..73fd907deb
--- /dev/null
+++ 
b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/StripedLocks.java
@@ -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.commons.internal.concurrent;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A lightweight striped-lock implementation
+ */
+public class StripedLocks {
+
+    private final Lock[] stripes;
+    private final int len;
+
+    public StripedLocks(final int stripesCount) {
+        if (stripesCount <= 0) {
+            throw new IllegalArgumentException("stripesCount must be > 0");
+        }
+        this.stripes = new Lock[stripesCount];
+        for (int i = 0; i < stripesCount; i++) {
+            this.stripes[i] = new ReentrantLock();
+        }
+        this.len = stripesCount;
+    }
+
+    public Lock get(Object key) {
+        int h = (key == null) ? 0 : key.hashCode();
+        // Spread bits to reduce collisions, using
+        // 
https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key/12996028#12996028
+        h *= 0x45d9f3b;
+        // 
http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
+        int index = (int) (((h & 0xffffffffL) * len) >>> 32);
+        return stripes[index];
+    }
+
+    public int stripesCount() {
+        return len;
+    }
+}
diff --git 
a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/package-info.java
 
b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/package-info.java
index 26de19423a..75cc10fd69 100644
--- 
a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/package-info.java
+++ 
b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/internal/concurrent/package-info.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("1.2.0")
+@Version("1.3.0")
 @Internal(since = "1.0.0")
 package org.apache.jackrabbit.oak.commons.internal.concurrent;
 
diff --git 
a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/internal/concurrent/StripedLocksTest.java
 
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/internal/concurrent/StripedLocksTest.java
new file mode 100644
index 0000000000..7a90629e9a
--- /dev/null
+++ 
b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/internal/concurrent/StripedLocksTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.commons.internal.concurrent;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * Unit cases for StripedLocks
+ */
+public class StripedLocksTest {
+
+    @Test
+    public void testConstructorThrowsOnZero() {
+        Assert.assertThrows(IllegalArgumentException.class, () -> new 
StripedLocks(0));
+    }
+
+    @Test
+    public void testConstructorThrowsOnNegative() {
+        Assert.assertThrows(IllegalArgumentException.class, () -> new 
StripedLocks(-5));
+    }
+
+    @Test
+    public void testStripesCount() {
+        StripedLocks locks = new StripedLocks(8);
+        Assert.assertEquals(8, locks.stripesCount());
+    }
+
+    @Test
+    public void testGetReturnsNonNullLock() {
+        StripedLocks locks = new StripedLocks(4);
+        Assert.assertNotNull(locks.get("key1"));
+        Assert.assertNotNull(locks.get(null));
+    }
+
+    @Test
+    public void testSameKeyReturnsSameLock() {
+        StripedLocks locks = new StripedLocks(16);
+        Object key = "test-key";
+        Lock lock1 = locks.get(key);
+        Lock lock2 = locks.get(key);
+        Assert.assertSame(lock1, lock2);
+    }
+
+    @Test
+    public void testDifferentKeysMayReturnDifferentLocks() {
+        StripedLocks locks = new StripedLocks(64);
+        Lock lock1 = locks.get("a");
+        Lock lock2 = locks.get("b");
+
+        // If they collide, test is still valid but weaker; we just ensure we 
can lock/unlock both safely.
+        lock1.lock();
+        lock2.lock();
+        lock2.unlock();
+        lock1.unlock();
+
+        Assert.assertNotNull(lock1);
+        Assert.assertNotNull(lock2);
+    }
+
+    @Test
+    public void sameKeyIsSerialized() throws InterruptedException {
+        StripedLocks striped = new StripedLocks(8);
+        String key = "shared-key";
+
+        CountDownLatch bothStarted = new CountDownLatch(2);
+        AtomicInteger order = new AtomicInteger(0);
+        AtomicInteger firstCompleted = new AtomicInteger(0);
+        AtomicInteger secondCompleted = new AtomicInteger(0);
+
+        Runnable task = () -> {
+            Lock lock = striped.get(key);
+            bothStarted.countDown();
+            try {
+                bothStarted.await(); // ensure both threads attempt to run 
concurrently
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+
+            lock.lock();
+            try {
+                int currentOrder = order.incrementAndGet();
+                // Only one thread can see currentOrder == 1 while holding the 
lock
+                if (currentOrder == 1) {
+                    firstCompleted.incrementAndGet();
+                    try {
+                        Thread.sleep(5);
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                    }
+                } else {
+                    secondCompleted.incrementAndGet();
+                }
+            } finally {
+                lock.unlock();
+            }
+        };
+
+        Thread t1 = new Thread(task);
+        Thread t2 = new Thread(task);
+        t1.start();
+        t2.start();
+        t1.join();
+        t2.join();
+
+        Assert.assertEquals(2, order.get());
+        Assert.assertEquals(2, firstCompleted.get() + secondCompleted.get());
+    }
+}

Reply via email to