From 8377b61404033c479afa38424211314e84c2105a Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 06 Aug 2026 08:03:21 +0000
Subject: [PATCH] Replace per-operation backend read lock with a scalable shared-access gate (#680)
---
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java | 60 ++++++++++++++++++++++++++++++++----------------------------
1 files changed, 32 insertions(+), 28 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java
index 40b0c0f..03cd930 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java
@@ -29,7 +29,6 @@
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.atomic.AtomicInteger;
import org.forgerock.i18n.LocalizableException;
import org.forgerock.i18n.LocalizableMessage;
@@ -93,9 +92,14 @@
/** The root container to use for this backend. */
private RootContainer rootContainer;
- // FIXME: this is broken. Replace with read-write lock.
- /** A count of the total operation threads currently in the backend. */
- private final AtomicInteger threadTotalCount = new AtomicInteger(0);
+ /**
+ * A count of the total operation threads currently in the backend. Bumped
+ * twice per operation by all worker threads, so it uses a striped counter
+ * to avoid contending on a single cache line; it is only read when waiting
+ * for the backend to become quiescent, which is why it is not a LongAdder —
+ * see {@link StripedCounter}.
+ */
+ private final StripedCounter threadTotalCount = new StripedCounter();
/** The base DNs defined for this backend instance. */
private Set<DN> baseDNs;
@@ -146,14 +150,14 @@
throw new DirectoryException(
noEntryContainerResultCode, ERR_BACKEND_ENTRY_DOESNT_EXIST.get(entryDN, getBackendID()));
}
- threadTotalCount.getAndIncrement();
+ threadTotalCount.increment();
return ec;
}
/** End a Backend API method that accesses the EntryContainer. */
private void accessEnd()
{
- threadTotalCount.getAndDecrement();
+ threadTotalCount.decrement();
}
/**
@@ -163,7 +167,7 @@
*/
private void waitUntilQuiescent()
{
- while (threadTotalCount.get() > 0)
+ while (threadTotalCount.sum() > 0)
{
// Still have threads accessing the storage so sleep a little
try
@@ -268,7 +272,7 @@
}
// Make sure the thread counts are zero for next initialization.
- threadTotalCount.set(0);
+ threadTotalCount.reset();
// Log an informational message.
logger.info(NOTE_BACKEND_OFFLINE, cfg.getBackendId());
@@ -356,7 +360,7 @@
throw de;
}
- container.sharedLock.lock();
+ container.beginSharedAccess();
try
{
return ConditionResult.valueOf(container.hasSubordinates(entryDN));
@@ -367,7 +371,7 @@
}
finally
{
- container.sharedLock.unlock();
+ container.endSharedAccess();
accessEnd();
}
}
@@ -378,7 +382,7 @@
checkNotNull(baseDN, "baseDN must not be null");
final EntryContainer ec = accessBegin(null, baseDN);
- ec.sharedLock.lock();
+ ec.beginSharedAccess();
try
{
return ec.getNumberOfEntriesInBaseDN();
@@ -390,7 +394,7 @@
}
finally
{
- ec.sharedLock.unlock();
+ ec.endSharedAccess();
accessEnd();
}
}
@@ -417,7 +421,7 @@
throw de;
}
- ec.sharedLock.lock();
+ ec.beginSharedAccess();
try
{
return ec.getNumberOfChildren(parentDN);
@@ -428,7 +432,7 @@
}
finally
{
- ec.sharedLock.unlock();
+ ec.endSharedAccess();
accessEnd();
}
}
@@ -437,7 +441,7 @@
public boolean entryExists(final DN entryDN) throws DirectoryException
{
EntryContainer ec = accessBegin(null, entryDN);
- ec.sharedLock.lock();
+ ec.beginSharedAccess();
try
{
return ec.entryExists(entryDN);
@@ -448,7 +452,7 @@
}
finally
{
- ec.sharedLock.unlock();
+ ec.endSharedAccess();
accessEnd();
}
}
@@ -457,7 +461,7 @@
public Entry getEntry(DN entryDN) throws DirectoryException
{
EntryContainer ec = accessBegin(null, entryDN);
- ec.sharedLock.lock();
+ ec.beginSharedAccess();
try
{
return ec.getEntry(entryDN);
@@ -468,7 +472,7 @@
}
finally
{
- ec.sharedLock.unlock();
+ ec.endSharedAccess();
accessEnd();
}
}
@@ -478,7 +482,7 @@
{
EntryContainer ec = accessBegin(addOperation, entry.getName());
- ec.sharedLock.lock();
+ ec.beginSharedAccess();
try
{
ec.addEntry(entry, addOperation);
@@ -489,7 +493,7 @@
}
finally
{
- ec.sharedLock.unlock();
+ ec.endSharedAccess();
accessEnd();
}
}
@@ -500,7 +504,7 @@
{
EntryContainer ec = accessBegin(deleteOperation, entryDN);
- ec.sharedLock.lock();
+ ec.beginSharedAccess();
try
{
ec.deleteEntry(entryDN, deleteOperation);
@@ -511,7 +515,7 @@
}
finally
{
- ec.sharedLock.unlock();
+ ec.endSharedAccess();
accessEnd();
}
}
@@ -522,7 +526,7 @@
{
EntryContainer ec = accessBegin(modifyOperation, newEntry.getName());
- ec.sharedLock.lock();
+ ec.beginSharedAccess();
try
{
@@ -534,7 +538,7 @@
}
finally
{
- ec.sharedLock.unlock();
+ ec.endSharedAccess();
accessEnd();
}
}
@@ -554,7 +558,7 @@
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, WARN_FUNCTION_NOT_SUPPORTED.get());
}
- currentContainer.sharedLock.lock();
+ currentContainer.beginSharedAccess();
try
{
currentContainer.renameEntry(currentDN, entry, modifyDNOperation);
@@ -565,7 +569,7 @@
}
finally
{
- currentContainer.sharedLock.unlock();
+ currentContainer.endSharedAccess();
accessEnd();
}
}
@@ -577,7 +581,7 @@
// is concerned: report it as such instead of the UNDEFINED result code used internally.
EntryContainer ec = accessBegin(searchOperation, searchOperation.getBaseDN(), ResultCode.NO_SUCH_OBJECT);
- ec.sharedLock.lock();
+ ec.beginSharedAccess();
try
{
@@ -589,7 +593,7 @@
}
finally
{
- ec.sharedLock.unlock();
+ ec.endSharedAccess();
accessEnd();
}
}
--
Gitblit v1.10.0