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

markt pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/11.0.x by this push:
     new d50e55eb47 Add a new keyed reentrant read/write lock
d50e55eb47 is described below

commit d50e55eb47c8e25f59d10aefc6b6f912851fc732
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Wed Aug 27 10:33:04 2025 +0100

    Add a new keyed reentrant read/write lock
    
    This class will be used when Tomcat needs to manage concurrent access to
    components identified by a key (e..g sessions).
---
 build.xml                                          |   3 +
 .../concurrent/KeyedReentrantReadWriteLock.java    | 179 +++++++++++++++++++++
 .../tomcat/util/concurrent/LocalStrings.properties |  16 ++
 .../TestKeyedReentrantReadWriteLock.java           |  31 ++++
 4 files changed, 229 insertions(+)

diff --git a/build.xml b/build.xml
index cc7e6d1be1..bf4b25efca 100644
--- a/build.xml
+++ b/build.xml
@@ -448,6 +448,7 @@
     <include name="org/apache/tomcat/util/buf/**" />
     <include name="org/apache/tomcat/util/collections/**" />
     <include name="org/apache/tomcat/util/compat/**" />
+    <include name="org/apache/tomcat/util/concurrent/**" />
     <include name="org/apache/tomcat/util/file/**" />
     <include name="org/apache/tomcat/util/json/**" />
     <include name="org/apache/tomcat/util/res/**" />
@@ -543,6 +544,7 @@
     <include name="org/apache/tomcat/util/buf/**" />
     <include name="org/apache/tomcat/util/collections/**" />
     <include name="org/apache/tomcat/util/compat/**" />
+    <include name="org/apache/tomcat/util/concurrent/**" />
     <include name="org/apache/tomcat/util/descriptor/**" />
     <include name="org/apache/tomcat/util/file/**" />
     <include name="org/apache/tomcat/util/http/**" />
@@ -2476,6 +2478,7 @@
         <exclude name="org/apache/tomcat/util/buf" />
         <exclude name="org/apache/tomcat/util/collections" />
         <exclude name="org/apache/tomcat/util/compat" />
+        <exclude name="org/apache/tomcat/util/concurrent" />
         <exclude name="org/apache/tomcat/util/descriptor" />
         <exclude name="org/apache/tomcat/util/file" />
         <exclude name="org/apache/tomcat/util/http" />
diff --git 
a/java/org/apache/tomcat/util/concurrent/KeyedReentrantReadWriteLock.java 
b/java/org/apache/tomcat/util/concurrent/KeyedReentrantReadWriteLock.java
new file mode 100644
index 0000000000..d1c0541d62
--- /dev/null
+++ b/java/org/apache/tomcat/util/concurrent/KeyedReentrantReadWriteLock.java
@@ -0,0 +1,179 @@
+/*
+ * 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.tomcat.util.concurrent;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Function;
+
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Provides a reentrant read/write lock for a given key. Any locks obtained 
from an instance of this class using the
+ * same key will use the same underlying reentrant read/write lock as long as 
at least one lock for that key remains in
+ * use. Once no locks are in use for the given key, the lock is eligible for 
GC and the next lock obtained using that key
+ * will use a new underlying reentrant read/write lock.
+ * <p>
+ * The class is used when Tomcat needs to manage concurrent access to 
components identified by a key (e.g. sessions).
+ * <p>
+ * The map of keys to locks is maintained so that locks are created as 
required and removed when no longer used.
+ * <p>
+ * The locks provided by this class only implement {@code Lock#lock()} and 
{@code Lock#unlock()}. All other methods will
+ * throw {@code UnsupportedOperationException}.
+ */
+public class KeyedReentrantReadWriteLock {
+
+    private final Map<String,CountedLock> locksMap = new HashMap<>();
+
+
+    /**
+     * Obtain the reentrant read/write lock for the given key.
+     *
+     * @param key The key for which the lock should be obtained
+     *
+     * @return A reentrant read/write lock for the given key
+     */
+    public ReadWriteLock getLock(String key) {
+        return new ReadWriteLockImpl(locksMap, key);
+    }
+
+
+    /*
+     * Reentrant read/write lock implementation that is passed back to the 
caller. It provides the lock wrappers that
+     * track usage.
+     */
+    private static class ReadWriteLockImpl implements ReadWriteLock {
+
+        private final Map<String,CountedLock> locksMap;
+        private final String key;
+        private volatile Lock readLock;
+        private volatile Lock writeLock;
+
+        ReadWriteLockImpl(Map<String,CountedLock> locksMap, String key) {
+            this.locksMap = locksMap;
+            this.key = key;
+        }
+
+        @Override
+        public Lock readLock() {
+            if (readLock == null) {
+                readLock = new LockImpl(locksMap, key, 
ReentrantReadWriteLock::readLock);
+            }
+            return readLock;
+        }
+
+        @Override
+        public Lock writeLock() {
+            if (writeLock == null) {
+                writeLock = new LockImpl(locksMap, key, 
ReentrantReadWriteLock::writeLock);
+            }
+            return writeLock;
+        }
+    }
+
+
+    /*
+     * Lock wrapper implementation that provides both read locks and write 
locks from the underlying lock and tracks
+     * their usage. Most of the methods throw UnsupportedOperationException as 
Tomcat does not (currently) require
+     * implementations of those methods.
+     */
+    private static class LockImpl implements Lock {
+
+        private static final StringManager sm = 
StringManager.getManager(LockImpl.class);
+
+        private final Map<String,CountedLock> locksMap;
+        private final String key;
+        private final Function<ReentrantReadWriteLock,Lock> function;
+
+        LockImpl(Map<String,CountedLock> locksMap, String key, 
Function<ReentrantReadWriteLock,Lock> function) {
+            this.locksMap = locksMap;
+            this.key = key;
+            this.function = function;
+        }
+
+        @Override
+        public void lock() {
+            CountedLock countedLock = null;
+            synchronized (locksMap) {
+                // Lookup / create the counted lock for the given key
+                countedLock = locksMap.compute(key, (k, v) -> v == null ? new 
CountedLock() : v);
+                // Increment usage count inside the sync block to ensure other 
threads are aware key is in use.
+                countedLock.count.incrementAndGet();
+            }
+            // Lock outside of the sync block in case the call to lock() 
blocks.
+            function.apply(countedLock.reentrantLock).lock();
+        }
+
+        @Override
+        public void unlock() {
+            CountedLock countedLock = null;
+            // Unlocking so a lock should exist in the map for the given key.
+            synchronized (locksMap) {
+                countedLock = locksMap.get(key);
+            }
+            if (countedLock == null) {
+                throw new 
IllegalStateException(sm.getString("lockImpl.unlockWithoutLock"));
+            }
+            // No need to unlock inside sync block, so don't.
+            function.apply(countedLock.reentrantLock).unlock();
+            synchronized (locksMap) {
+                /*
+                 * Decrement usage count and check for zero inside the sync 
block to ensure usage tracking is consistent
+                 * across multiple threads.
+                 */
+                if (countedLock.count.decrementAndGet() == 0) {
+                    locksMap.remove(key);
+                }
+            }
+        }
+
+        @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();
+        }
+    }
+
+
+    /*
+     * Holds the underlying reentrant read/write lock and the counter that 
tracks usage.
+     */
+    private static class CountedLock {
+        AtomicInteger count = new AtomicInteger();
+        ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
+    }
+}
diff --git a/java/org/apache/tomcat/util/concurrent/LocalStrings.properties 
b/java/org/apache/tomcat/util/concurrent/LocalStrings.properties
new file mode 100644
index 0000000000..c938a3b529
--- /dev/null
+++ b/java/org/apache/tomcat/util/concurrent/LocalStrings.properties
@@ -0,0 +1,16 @@
+# 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.
+
+lockImpl.unlockWithoutLock=An attempt was made to release a lock without first 
obtaining the lock
\ No newline at end of file
diff --git 
a/test/org/apache/tomcat/util/concurrent/TestKeyedReentrantReadWriteLock.java 
b/test/org/apache/tomcat/util/concurrent/TestKeyedReentrantReadWriteLock.java
new file mode 100644
index 0000000000..f2a1d5f77b
--- /dev/null
+++ 
b/test/org/apache/tomcat/util/concurrent/TestKeyedReentrantReadWriteLock.java
@@ -0,0 +1,31 @@
+/*
+ * 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.tomcat.util.concurrent;
+
+import java.util.concurrent.locks.ReadWriteLock;
+
+import org.junit.Test;
+
+public class TestKeyedReentrantReadWriteLock {
+
+    @Test(expected = IllegalStateException.class)
+    public void testUnlockWithoutLock() {
+        KeyedReentrantReadWriteLock locks = new KeyedReentrantReadWriteLock();
+        ReadWriteLock lock = locks.getLock("any");
+        lock.readLock().unlock();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to