From 099f991f4a13c13f9a1bd0750e27a49ff861a73c Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 04 Sep 2025 07:55:09 +0000
Subject: [PATCH] [#545] Add GroupManager writeLock performance (#551)

---
 opendj-server-legacy/src/test/java/org/opends/server/core/GroupManagerTestCase.java |   54 +++++++++++++++++++++++++++
 opendj-server-legacy/src/main/java/org/opends/server/core/GroupManager.java         |   59 +++++++++++------------------
 2 files changed, 77 insertions(+), 36 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/GroupManager.java b/opendj-server-legacy/src/main/java/org/opends/server/core/GroupManager.java
index fc1ca49..458a6ab 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/GroupManager.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/GroupManager.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2007-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2025 3A Systems,LLC.
  */
 package org.opends.server.core;
 
@@ -726,48 +727,34 @@
       return;
     }
 
+    Group<?> group =null;
     lock.readLock().lock();
-    try
-    {
-      if (!groupInstances.containsKey(oldEntry.getName()))
-      {
-        // If the modified entry is not in any group instance, it's probably
-        // not a group, exit fast
-        return;
-      }
+    try{
+        group = groupInstances.get(oldEntry.getName());
     }
     finally
     {
-      lock.readLock().unlock();
+        lock.readLock().unlock();
     }
-
-    lock.writeLock().lock();
-    try
-    {
-      Group<?> group = groupInstances.get(oldEntry.getName());
-      if (group != null)
-      {
-        if (!oldEntry.getName().equals(newEntry.getName())
-            || !group.mayAlterMemberList()
-            || updatesObjectClass(modifications))
-        {
-          groupInstances.remove(oldEntry.getName());
-          // This updates the refreshToken
-          createAndRegisterGroup(newEntry);
+    if (group!=null) {
+        try {
+            if (!oldEntry.getName().equals(newEntry.getName())
+                    || !group.mayAlterMemberList()
+                    || updatesObjectClass(modifications)) {
+                lock.writeLock().lock();
+                try {
+                    groupInstances.remove(oldEntry.getName());
+                    // This updates the refreshToken
+                    createAndRegisterGroup(newEntry);
+                } finally {
+                    lock.writeLock().unlock();
+                }
+            } else {
+                group.updateMembers(modifications);
+            }
+        } catch (UnsupportedOperationException | DirectoryException e) {
+            logger.traceException(e);
         }
-        else
-        {
-          group.updateMembers(modifications);
-        }
-      }
-    }
-    catch (UnsupportedOperationException | DirectoryException e)
-    {
-      logger.traceException(e);
-    }
-    finally
-    {
-      lock.writeLock().unlock();
     }
   }
 
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/core/GroupManagerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/core/GroupManagerTestCase.java
index 140f825..a4449fc 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/core/GroupManagerTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/core/GroupManagerTestCase.java
@@ -13,12 +13,17 @@
  *
  * Copyright 2008-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2025 3A Systems, LLC
  */
 package org.opends.server.core;
 
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.ResultCode;
@@ -2292,6 +2297,55 @@
     TestCaseUtils.clearBackend("userRoot");
   }
 
+  @Test
+  public void test_issue_535() throws Exception {
+      TestCaseUtils.clearBackend("userRoot", "dc=example,dc=com");
+      TestCaseUtils.addEntries(
+              "dn: ou=Users,dc=example,dc=com",
+              "objectClass: organizationalUnit",
+              "objectClass: top",
+              "ou: Users",
+              "",
+              "dn: ou=Groups,dc=example,dc=com",
+              "objectClass: organizationalUnit",
+              "objectClass: top",
+              "ou: Groups",
+              "",
+              "dn: cn=Test User,ou=Users,dc=example,dc=com",
+              "objectClass: inetOrgPerson",
+              "objectClass: organizationalPerson",
+              "objectClass: person",
+              "objectClass: top",
+              "uid: testuser",
+              "cn: Test User",
+              "sn: User",
+              "userPassword: password123",
+              "",
+              "dn: cn=Level1,ou=Groups,dc=example,dc=com",
+              "objectClass: groupOfNames",
+              "objectClass: top",
+              "cn: Level1",
+              "member: cn=Test User,ou=Users,dc=example,dc=com",
+              "",
+              "dn: cn=Level2,ou=Groups,dc=example,dc=com",
+              "objectClass: groupOfNames",
+              "objectClass: top",
+              "cn: Level2",
+              "member: cn=Level1,ou=Groups,dc=example,dc=com",
+              ""
+      );
+      ExecutorService executor = Executors.newFixedThreadPool(100);
+      for (int i = 0; i < 10000; i++) {
+          executor.submit(() -> {
+              final ModifyRequest modifyRequest = newModifyRequest(DN.valueOf("cn=Level2,ou=Groups,dc=example,dc=com"));
+              modifyRequest.addModification(REPLACE, "member", "cn=Test User,ou=Users,dc=example,dc=com");
+              ModifyOperation modifyOperation = getRootConnection().processModify(modifyRequest);
+              assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+          });
+      }
+      executor.shutdown();
+      assertTrue(executor.awaitTermination(1, TimeUnit.MINUTES));
+  }
   /**
    * Adds nested group entries.
    *

--
Gitblit v1.10.0