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

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


The following commit(s) were added to refs/heads/main by this push:
     new 5140760  Additional locking to improve protection for concurrent 
modifications
5140760 is described below

commit 5140760a3853ef0ced5d2a1c51349a98a2f62905
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Sun Jan 2 13:16:16 2022 +0000

    Additional locking to improve protection for concurrent modifications
---
 .../catalina/users/DataSourceUserDatabase.java     | 481 ++++++++++++---------
 res/spotbugs/filter-false-positives.xml            |  10 +
 webapps/docs/changelog.xml                         |   4 +
 3 files changed, 298 insertions(+), 197 deletions(-)

diff --git a/java/org/apache/catalina/users/DataSourceUserDatabase.java 
b/java/org/apache/catalina/users/DataSourceUserDatabase.java
index a010498..cd3c319 100644
--- a/java/org/apache/catalina/users/DataSourceUserDatabase.java
+++ b/java/org/apache/catalina/users/DataSourceUserDatabase.java
@@ -220,14 +220,26 @@ public class DataSourceUserDatabase extends 
SparseUserDatabase {
      */
     protected boolean readonly = true;
 
-
+    // The write lock on the database is assumed to include the write locks
+    // for groups, users and roles.
     private final ReentrantReadWriteLock dbLock = new ReentrantReadWriteLock();
-    private final Lock readLock = dbLock.readLock();
-    private final Lock writeLock = dbLock.writeLock();
+    private final Lock dbReadLock = dbLock.readLock();
+    private final Lock dbWriteLock = dbLock.writeLock();
 
+    private final ReentrantReadWriteLock groupsLock = new 
ReentrantReadWriteLock();
+    private final Lock groupsReadLock = groupsLock.readLock();
+    private final Lock groupsWriteLock = groupsLock.writeLock();
+
+    private final ReentrantReadWriteLock usersLock = new 
ReentrantReadWriteLock();
+    private final Lock usersReadLock = usersLock.readLock();
+    private final Lock usersWriteLock = usersLock.writeLock();
+
+    private final ReentrantReadWriteLock rolesLock = new 
ReentrantReadWriteLock();
+    private final Lock rolesReadLock = rolesLock.readLock();
+    private final Lock rolesWriteLock = rolesLock.writeLock();
 
-    // ------------------------------------------------------------- Properties
 
+    // ------------------------------------------------------------- Properties
 
     /**
      * @return the name of the JNDI JDBC DataSource.
@@ -445,109 +457,124 @@ public class DataSourceUserDatabase extends 
SparseUserDatabase {
 
     @Override
     public Iterator<Group> getGroups() {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            HashMap<String, Group> groups = new HashMap<>();
-            groups.putAll(createdGroups);
-            groups.putAll(modifiedGroups);
-
-            Connection dbConnection = openConnection();
-            if (dbConnection != null && preparedAllGroups != null) {
-                try (PreparedStatement stmt = 
dbConnection.prepareStatement(preparedAllGroups)) {
-                    try (ResultSet rs = stmt.executeQuery()) {
-                        while (rs.next()) {
-                            String groupName = rs.getString(1);
-                            if (groupName != null) {
-                                if (!groups.containsKey(groupName) && 
!removedGroups.containsKey(groupName)) {
-                                    Group group = 
findGroupInternal(dbConnection, groupName);
-                                    if (group != null) {
-                                        groups.put(groupName, group);
+            groupsReadLock.lock();
+            try {
+                HashMap<String, Group> groups = new HashMap<>();
+                groups.putAll(createdGroups);
+                groups.putAll(modifiedGroups);
+
+                Connection dbConnection = openConnection();
+                if (dbConnection != null && preparedAllGroups != null) {
+                    try (PreparedStatement stmt = 
dbConnection.prepareStatement(preparedAllGroups)) {
+                        try (ResultSet rs = stmt.executeQuery()) {
+                            while (rs.next()) {
+                                String groupName = rs.getString(1);
+                                if (groupName != null) {
+                                    if (!groups.containsKey(groupName) && 
!removedGroups.containsKey(groupName)) {
+                                        Group group = 
findGroupInternal(dbConnection, groupName);
+                                        if (group != null) {
+                                            groups.put(groupName, group);
+                                        }
                                     }
                                 }
                             }
                         }
+                    } catch (SQLException e) {
+                        
log.error(sm.getString("dataSourceUserDatabase.exception"), e);
+                    } finally {
+                        closeConnection(dbConnection);
                     }
-                } catch (SQLException e) {
-                    
log.error(sm.getString("dataSourceUserDatabase.exception"), e);
-                } finally {
-                    closeConnection(dbConnection);
                 }
+                return groups.values().iterator();
+            } finally {
+                groupsReadLock.unlock();
             }
-            return groups.values().iterator();
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
     @Override
     public Iterator<Role> getRoles() {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            HashMap<String, Role> roles = new HashMap<>();
-            roles.putAll(createdRoles);
-            roles.putAll(modifiedRoles);
-
-            Connection dbConnection = openConnection();
-            if (dbConnection != null && preparedAllRoles != null) {
-                try (PreparedStatement stmt = 
dbConnection.prepareStatement(preparedAllRoles)) {
-                    try (ResultSet rs = stmt.executeQuery()) {
-                        while (rs.next()) {
-                            String roleName = rs.getString(1);
-                            if (roleName != null) {
-                                if (!roles.containsKey(roleName) && 
!removedRoles.containsKey(roleName)) {
-                                    Role role = findRoleInternal(dbConnection, 
roleName);
-                                    if (role != null) {
-                                        roles.put(roleName, role);
+            rolesReadLock.lock();
+            try {
+                HashMap<String, Role> roles = new HashMap<>();
+                roles.putAll(createdRoles);
+                roles.putAll(modifiedRoles);
+
+                Connection dbConnection = openConnection();
+                if (dbConnection != null && preparedAllRoles != null) {
+                    try (PreparedStatement stmt = 
dbConnection.prepareStatement(preparedAllRoles)) {
+                        try (ResultSet rs = stmt.executeQuery()) {
+                            while (rs.next()) {
+                                String roleName = rs.getString(1);
+                                if (roleName != null) {
+                                    if (!roles.containsKey(roleName) && 
!removedRoles.containsKey(roleName)) {
+                                        Role role = 
findRoleInternal(dbConnection, roleName);
+                                        if (role != null) {
+                                            roles.put(roleName, role);
+                                        }
                                     }
                                 }
                             }
                         }
+                    } catch (SQLException e) {
+                        
log.error(sm.getString("dataSourceUserDatabase.exception"), e);
+                    } finally {
+                        closeConnection(dbConnection);
                     }
-                } catch (SQLException e) {
-                    
log.error(sm.getString("dataSourceUserDatabase.exception"), e);
-                } finally {
-                    closeConnection(dbConnection);
                 }
+                return roles.values().iterator();
+            } finally {
+                rolesReadLock.unlock();
             }
-            return roles.values().iterator();
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
     @Override
     public Iterator<User> getUsers() {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            HashMap<String, User> users = new HashMap<>();
-            users.putAll(createdUsers);
-            users.putAll(modifiedUsers);
-
-            Connection dbConnection = openConnection();
-            if (dbConnection != null) {
-                try (PreparedStatement stmt = 
dbConnection.prepareStatement(preparedAllUsers)) {
-                    try (ResultSet rs = stmt.executeQuery()) {
-                        while (rs.next()) {
-                            String userName = rs.getString(1);
-                            if (userName != null) {
-                                if (!users.containsKey(userName) && 
!removedUsers.containsKey(userName)) {
-                                    User user = findUserInternal(dbConnection, 
userName);
-                                    if (user != null) {
-                                        users.put(userName, user);
+            usersReadLock.lock();
+            try {
+                HashMap<String, User> users = new HashMap<>();
+                users.putAll(createdUsers);
+                users.putAll(modifiedUsers);
+
+                Connection dbConnection = openConnection();
+                if (dbConnection != null) {
+                    try (PreparedStatement stmt = 
dbConnection.prepareStatement(preparedAllUsers)) {
+                        try (ResultSet rs = stmt.executeQuery()) {
+                            while (rs.next()) {
+                                String userName = rs.getString(1);
+                                if (userName != null) {
+                                    if (!users.containsKey(userName) && 
!removedUsers.containsKey(userName)) {
+                                        User user = 
findUserInternal(dbConnection, userName);
+                                        if (user != null) {
+                                            users.put(userName, user);
+                                        }
                                     }
                                 }
                             }
                         }
+                    } catch (SQLException e) {
+                        
log.error(sm.getString("dataSourceUserDatabase.exception"), e);
+                    } finally {
+                        closeConnection(dbConnection);
                     }
-                } catch (SQLException e) {
-                    
log.error(sm.getString("dataSourceUserDatabase.exception"), e);
-                } finally {
-                    closeConnection(dbConnection);
                 }
+                return users.values().iterator();
+            } finally {
+                usersReadLock.unlock();
             }
-            return users.values().iterator();
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
@@ -557,76 +584,96 @@ public class DataSourceUserDatabase extends 
SparseUserDatabase {
 
     @Override
     public Group createGroup(String groupname, String description) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            Group group = new GenericGroup<>(this, groupname, description, 
null);
-            createdGroups.put(groupname, group);
-            modifiedGroups.remove(groupname);
-            return group;
+            groupsWriteLock.lock();
+            try {
+                Group group = new GenericGroup<>(this, groupname, description, 
null);
+                createdGroups.put(groupname, group);
+                modifiedGroups.remove(groupname);
+                return group;
+            } finally {
+                groupsWriteLock.unlock();
+            }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
     @Override
     public Role createRole(String rolename, String description) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            Role role = new GenericRole<>(this, rolename, description);
-            createdRoles.put(rolename, role);
-            modifiedRoles.remove(rolename);
-            return role;
+            rolesWriteLock.lock();
+            try {
+                Role role = new GenericRole<>(this, rolename, description);
+                createdRoles.put(rolename, role);
+                modifiedRoles.remove(rolename);
+                return role;
+            } finally {
+                rolesWriteLock.unlock();
+            }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
     @Override
     public User createUser(String username, String password, String fullName) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            User user = new GenericUser<>(this, username, password, fullName, 
null, null);
-            createdUsers.put(username, user);
-            modifiedUsers.remove(username);
-            return user;
+            usersWriteLock.lock();
+            try {
+                User user = new GenericUser<>(this, username, password, 
fullName, null, null);
+                createdUsers.put(username, user);
+                modifiedUsers.remove(username);
+                return user;
+            } finally {
+                usersWriteLock.unlock();
+            }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
     @Override
     public Group findGroup(String groupname) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            // Check local changes first
-            Group group = createdGroups.get(groupname);
-            if (group != null) {
-                return group;
-            }
-            group = modifiedGroups.get(groupname);
-            if (group != null) {
-                return group;
-            }
-            group = removedGroups.get(groupname);
-            if (group != null) {
-                return null;
-            }
-
-            if (isGroupStoreDefined()) {
-                Connection dbConnection = openConnection();
-                if (dbConnection == null) {
+            groupsReadLock.lock();
+            try {
+                // Check local changes first
+                Group group = createdGroups.get(groupname);
+                if (group != null) {
+                    return group;
+                }
+                group = modifiedGroups.get(groupname);
+                if (group != null) {
+                    return group;
+                }
+                group = removedGroups.get(groupname);
+                if (group != null) {
                     return null;
                 }
-                try {
-                    return findGroupInternal(dbConnection, groupname);
-                } finally {
-                    closeConnection(dbConnection);
+
+                if (isGroupStoreDefined()) {
+                    Connection dbConnection = openConnection();
+                    if (dbConnection == null) {
+                        return null;
+                    }
+                    try {
+                        return findGroupInternal(dbConnection, groupname);
+                    } finally {
+                        closeConnection(dbConnection);
+                    }
+                } else {
+                    return null;
                 }
-            } else {
-                return null;
+            } finally {
+                groupsReadLock.unlock();
             }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
@@ -670,37 +717,42 @@ public class DataSourceUserDatabase extends 
SparseUserDatabase {
 
     @Override
     public Role findRole(String rolename) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            // Check local changes first
-            Role role = createdRoles.get(rolename);
-            if (role != null) {
-                return role;
-            }
-            role = modifiedRoles.get(rolename);
-            if (role != null) {
-                return role;
-            }
-            role = removedRoles.get(rolename);
-            if (role != null) {
-                return null;
-            }
-
-            if (userRoleTable != null && roleNameCol != null) {
-                Connection dbConnection = openConnection();
-                if (dbConnection == null) {
+            rolesReadLock.lock();
+            try {
+                // Check local changes first
+                Role role = createdRoles.get(rolename);
+                if (role != null) {
+                    return role;
+                }
+                role = modifiedRoles.get(rolename);
+                if (role != null) {
+                    return role;
+                }
+                role = removedRoles.get(rolename);
+                if (role != null) {
                     return null;
                 }
-                try {
-                    return findRoleInternal(dbConnection, rolename);
-                } finally {
-                    closeConnection(dbConnection);
+
+                if (userRoleTable != null && roleNameCol != null) {
+                    Connection dbConnection = openConnection();
+                    if (dbConnection == null) {
+                        return null;
+                    }
+                    try {
+                        return findRoleInternal(dbConnection, rolename);
+                    } finally {
+                        closeConnection(dbConnection);
+                    }
+                } else {
+                    return null;
                 }
-            } else {
-                return null;
+            } finally {
+                rolesReadLock.unlock();
             }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
@@ -724,33 +776,38 @@ public class DataSourceUserDatabase extends 
SparseUserDatabase {
 
     @Override
     public User findUser(String username) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            // Check local changes first
-            User user = createdUsers.get(username);
-            if (user != null) {
-                return user;
-            }
-            user = modifiedUsers.get(username);
-            if (user != null) {
-                return user;
-            }
-            user = removedUsers.get(username);
-            if (user != null) {
-                return null;
-            }
-
-            Connection dbConnection = openConnection();
-            if (dbConnection == null) {
-                return null;
-            }
+            usersReadLock.lock();
             try {
-                return findUserInternal(dbConnection, username);
+                // Check local changes first
+                User user = createdUsers.get(username);
+                if (user != null) {
+                    return user;
+                }
+                user = modifiedUsers.get(username);
+                if (user != null) {
+                    return user;
+                }
+                user = removedUsers.get(username);
+                if (user != null) {
+                    return null;
+                }
+
+                Connection dbConnection = openConnection();
+                if (dbConnection == null) {
+                    return null;
+                }
+                try {
+                    return findUserInternal(dbConnection, username);
+                } finally {
+                    closeConnection(dbConnection);
+                }
             } finally {
-                closeConnection(dbConnection);
+                usersReadLock.unlock();
             }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
@@ -822,40 +879,55 @@ public class DataSourceUserDatabase extends 
SparseUserDatabase {
 
     @Override
     public void modifiedGroup(Group group) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            String name = group.getName();
-            if (!createdGroups.containsKey(name) && 
!removedGroups.containsKey(name)) {
-                modifiedGroups.put(name, group);
+            groupsWriteLock.lock();
+            try {
+                String name = group.getName();
+                if (!createdGroups.containsKey(name) && 
!removedGroups.containsKey(name)) {
+                    modifiedGroups.put(name, group);
+                }
+            } finally {
+                groupsWriteLock.unlock();
             }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
     @Override
     public void modifiedRole(Role role) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            String name = role.getName();
-            if (!createdRoles.containsKey(name) && 
!removedRoles.containsKey(name)) {
-                modifiedRoles.put(name, role);
+            rolesWriteLock.lock();
+            try {
+                String name = role.getName();
+                if (!createdRoles.containsKey(name) && 
!removedRoles.containsKey(name)) {
+                    modifiedRoles.put(name, role);
+                }
+            } finally {
+                rolesWriteLock.unlock();
             }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
     @Override
     public void modifiedUser(User user) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            String name = user.getName();
-            if (!createdUsers.containsKey(name) && 
!removedUsers.containsKey(name)) {
-                modifiedUsers.put(name, user);
+            usersWriteLock.lock();
+            try {
+                String name = user.getName();
+                if (!createdUsers.containsKey(name) && 
!removedUsers.containsKey(name)) {
+                    modifiedUsers.put(name, user);
+                }
+            } finally {
+                usersWriteLock.unlock();
             }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
@@ -870,7 +942,7 @@ public class DataSourceUserDatabase extends 
SparseUserDatabase {
                     + "], Groups [" + Boolean.toString(isRoleStoreDefined()) + 
"]");
         }
 
-        writeLock.lock();
+        dbWriteLock.lock();
         try {
 
             StringBuilder temp = new StringBuilder("SELECT ");
@@ -970,47 +1042,62 @@ public class DataSourceUserDatabase extends 
SparseUserDatabase {
             }
 
         } finally {
-            writeLock.unlock();
+            dbWriteLock.unlock();
         }
 
     }
 
     @Override
     public void removeGroup(Group group) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            String name = group.getName();
-            createdGroups.remove(name);
-            modifiedGroups.remove(name);
-            removedGroups.put(name, group);
+            groupsWriteLock.lock();
+            try {
+                String name = group.getName();
+                createdGroups.remove(name);
+                modifiedGroups.remove(name);
+                removedGroups.put(name, group);
+            } finally {
+                groupsWriteLock.unlock();
+            }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
     @Override
     public void removeRole(Role role) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            String name = role.getName();
-            createdRoles.remove(name);
-            modifiedRoles.remove(name);
-            removedRoles.put(name, role);
+            rolesWriteLock.lock();
+            try {
+                String name = role.getName();
+                createdRoles.remove(name);
+                modifiedRoles.remove(name);
+                removedRoles.put(name, role);
+            } finally {
+                rolesWriteLock.unlock();
+            }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
     @Override
     public void removeUser(User user) {
-        readLock.lock();
+        dbReadLock.lock();
         try {
-            String name = user.getName();
-            createdUsers.remove(name);
-            modifiedUsers.remove(name);
-            removedUsers.put(name, user);
+            usersWriteLock.lock();
+            try {
+                String name = user.getName();
+                createdUsers.remove(name);
+                modifiedUsers.remove(name);
+                removedUsers.put(name, user);
+            } finally {
+                usersWriteLock.unlock();
+            }
         } finally {
-            readLock.unlock();
+            dbReadLock.unlock();
         }
     }
 
@@ -1025,7 +1112,7 @@ public class DataSourceUserDatabase extends 
SparseUserDatabase {
             return;
         }
 
-        writeLock.lock();
+        dbWriteLock.lock();
         try {
             try {
                 saveInternal(dbConnection);
@@ -1033,7 +1120,7 @@ public class DataSourceUserDatabase extends 
SparseUserDatabase {
                 closeConnection(dbConnection);
             }
         } finally {
-            writeLock.unlock();
+            dbWriteLock.unlock();
         }
     }
 
diff --git a/res/spotbugs/filter-false-positives.xml 
b/res/spotbugs/filter-false-positives.xml
index b32add4..589e42c 100644
--- a/res/spotbugs/filter-false-positives.xml
+++ b/res/spotbugs/filter-false-positives.xml
@@ -754,6 +754,16 @@
     <Bug pattern="SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING"/>
   </Match>
   <Match>
+    <!-- All modifications wrapped with appropriate locks -->
+    <Class name="org.apache.catalina.users.DataSourceUserDatabase"/>
+    <Or>
+      <Method name="modifiedGroup"/>
+      <Method name="modifiedRole"/>
+      <Method name="modifiedUser"/>
+    </Or>
+    <Bug pattern="AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION"/>
+  </Match>
+  <Match>
     <Class name="org.apache.catalina.util.LifecycleBase" />
     <Method name="getState"/>
     <Bug code="UG" />
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 64f3cd8..0be6e04 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -112,6 +112,10 @@
         ensure that the method fails if called after the web application has
         started. (markt)
       </fix>
+      <fix>
+        Add additional locking to <code>DataSourceUserDatabase</code> to 
provide
+        improved protection for concurrent modifications. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">

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

Reply via email to