From a5789e69cf5ecfff3234af5b81dfd5fc3de6d1e3 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 16 Apr 2015 14:49:54 +0000
Subject: [PATCH] CR-6653 OPENDJ-1878: re-implemented LockManager to support sub-tree write locking
---
opendj-server-legacy/src/test/java/org/opends/server/core/DeleteOperationTestCase.java | 15
opendj-server-legacy/src/main/java/org/opends/server/extensions/PasswordModifyExtendedOperation.java | 34
opendj-server-legacy/src/main/java/org/opends/server/types/DirectoryEnvironmentConfig.java | 232 ------
opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java | 14
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java | 19
opendj-server-legacy/src/test/java/org/opends/server/core/CompareOperationTestCase.java | 81 --
opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java | 24
opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java | 11
opendj-server-legacy/src/test/java/org/opends/server/core/TestModifyDNOperation.java | 6
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java | 8
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java | 49
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java | 134 +--
opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskScheduler.java | 48
opendj-server-legacy/src/test/java/org/opends/server/core/ModifyOperationTestCase.java | 13
opendj-server-legacy/src/main/java/org/opends/server/extensions/FIFOEntryCache.java | 5
opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java | 7
opendj-server-legacy/src/main/java/org/opends/server/types/LockManager.java | 1018 ++++++++++++---------------
opendj-server-legacy/src/test/java/org/opends/server/core/AddOperationTestCase.java | 10
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java | 3
opendj-server-legacy/src/test/java/org/opends/server/types/LockManagerTest.java | 320 ++++++++
opendj-server-legacy/src/main/java/org/opends/server/backends/task/Task.java | 52
21 files changed, 961 insertions(+), 1,142 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java
index ce2bafd..0610720 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java
@@ -102,14 +102,9 @@
*/
public LDIFBackend()
{
- super();
-
entryMap = new LinkedHashMap<DN,Entry>();
childDNs = new HashMap<DN, Set<DN>>();
-
- boolean useFairLocking =
- DirectoryServer.getEnvironmentConfig().getLockManagerFairOrdering();
- backendLock = new ReentrantReadWriteLock(useFairLocking);
+ backendLock = new ReentrantReadWriteLock();
}
/** {@inheritDoc} */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
index f54798a..786251f 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
@@ -1887,8 +1887,7 @@
/**
* Fetch an entry by DN, trying the entry cache first, then the database.
* Retrieves the requested entry, trying the entry cache first,
- * then the database. Note that the caller must hold a read or write lock
- * on the specified DN.
+ * then the database.
*
* @param entryDN The distinguished name of the entry to retrieve.
* @return The requested entry, or <CODE>null</CODE> if the entry does not
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/task/Task.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/task/Task.java
index ae70be2..6fdd5c3 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/task/Task.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/task/Task.java
@@ -36,7 +36,6 @@
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
-import java.util.concurrent.locks.Lock;
import javax.mail.MessagingException;
@@ -47,6 +46,7 @@
import org.opends.messages.Severity;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.*;
+import org.opends.server.types.LockManager.DNLock;
import org.opends.server.util.EMailMessage;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.TimeThread;
@@ -583,13 +583,11 @@
{
// We only need to grab the entry-level lock if we don't already hold the
// broader scheduler lock.
- boolean needLock = (! taskScheduler.holdsSchedulerLock());
- Lock lock = null;
- if (needLock)
+ DNLock lock = null;
+ if (!taskScheduler.holdsSchedulerLock())
{
lock = taskScheduler.writeLockEntry(taskEntryDN);
}
-
try
{
this.taskState = taskState;
@@ -601,9 +599,9 @@
}
finally
{
- if (needLock)
+ if (lock != null)
{
- taskScheduler.unlockEntry(taskEntryDN, lock);
+ lock.unlock();
}
}
}
@@ -675,13 +673,11 @@
{
// We only need to grab the entry-level lock if we don't already hold the
// broader scheduler lock.
- boolean needLock = (! taskScheduler.holdsSchedulerLock());
- Lock lock = null;
- if (needLock)
+ DNLock lock = null;
+ if (!taskScheduler.holdsSchedulerLock())
{
lock = taskScheduler.writeLockEntry(taskEntryDN);
}
-
try
{
Entry taskEntry = getTaskEntry();
@@ -694,9 +690,9 @@
}
finally
{
- if (needLock)
+ if (lock != null)
{
- taskScheduler.unlockEntry(taskEntryDN, lock);
+ lock.unlock();
}
}
}
@@ -744,13 +740,11 @@
{
// We only need to grab the entry-level lock if we don't already hold the
// broader scheduler lock.
- boolean needLock = (! taskScheduler.holdsSchedulerLock());
- Lock lock = null;
- if (needLock)
+ DNLock lock = null;
+ if (!taskScheduler.holdsSchedulerLock())
{
lock = taskScheduler.writeLockEntry(taskEntryDN);
}
-
try
{
this.actualStartTime = actualStartTime;
@@ -764,9 +758,9 @@
}
finally
{
- if (needLock)
+ if (lock != null)
{
- taskScheduler.unlockEntry(taskEntryDN, lock);
+ lock.unlock();
}
}
}
@@ -800,13 +794,11 @@
{
// We only need to grab the entry-level lock if we don't already hold the
// broader scheduler lock.
- boolean needLock = (! taskScheduler.holdsSchedulerLock());
- Lock lock = null;
- if (needLock)
+ DNLock lock = null;
+ if (!taskScheduler.holdsSchedulerLock())
{
lock = taskScheduler.writeLockEntry(taskEntryDN);
}
-
try
{
this.completionTime = completionTime;
@@ -822,9 +814,9 @@
}
finally
{
- if (needLock)
+ if (lock != null)
{
- taskScheduler.unlockEntry(taskEntryDN, lock);
+ lock.unlock();
}
}
}
@@ -946,13 +938,11 @@
// We only need to grab the entry-level lock if we don't already hold the
// broader scheduler lock.
- boolean needLock = (! taskScheduler.holdsSchedulerLock());
- Lock lock = null;
- if (needLock)
+ DNLock lock = null;
+ if (!taskScheduler.holdsSchedulerLock())
{
lock = taskScheduler.writeLockEntry(taskEntryDN);
}
-
try
{
StringBuilder buffer = new StringBuilder();
@@ -1008,9 +998,9 @@
}
finally
{
- if (needLock)
+ if (lock != null)
{
- taskScheduler.unlockEntry(taskEntryDN, lock);
+ lock.unlock();
}
}
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java
index c454452..9838cb3 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java
@@ -35,7 +35,6 @@
import java.net.InetAddress;
import java.security.MessageDigest;
import java.util.*;
-import java.util.concurrent.locks.Lock;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -59,6 +58,7 @@
import org.opends.server.config.ConfigEntry;
import org.opends.server.core.*;
import org.opends.server.types.*;
+import org.opends.server.types.LockManager.DNLock;
import org.opends.server.util.DynamicConstants;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;
@@ -413,8 +413,7 @@
return null;
}
- Lock lock = taskScheduler.readLockEntry(entryDN);
-
+ DNLock lock = taskScheduler.readLockEntry(entryDN);
try
{
if (entryDN.equals(taskRootDN))
@@ -453,7 +452,7 @@
}
finally
{
- taskScheduler.unlockEntry(entryDN, lock);
+ lock.unlock();
}
}
@@ -584,11 +583,10 @@
ModifyOperation modifyOperation) throws DirectoryException
{
DN entryDN = newEntry.getName();
-
- Lock entryLock = null;
+ DNLock entryLock = null;
if (! taskScheduler.holdsSchedulerLock())
{
- entryLock = LockManager.lockWrite(entryDN);
+ entryLock = DirectoryServer.getLockManager().tryWriteLockEntry(entryDN);
if (entryLock == null)
{
throw new DirectoryException(ResultCode.BUSY,
@@ -707,7 +705,7 @@
{
if (entryLock != null)
{
- LockManager.unlock(entryDN, entryLock);
+ entryLock.unlock();
}
}
}
@@ -868,8 +866,7 @@
}
else if (parentDN.equals(scheduledTaskParentDN))
{
- Lock lock = taskScheduler.readLockEntry(baseDN);
-
+ DNLock lock = taskScheduler.readLockEntry(baseDN);
try
{
Entry e = taskScheduler.getScheduledTaskEntry(baseDN);
@@ -890,13 +887,12 @@
}
finally
{
- taskScheduler.unlockEntry(baseDN, lock);
+ lock.unlock();
}
}
else if (parentDN.equals(recurringTaskParentDN))
{
- Lock lock = taskScheduler.readLockEntry(baseDN);
-
+ DNLock lock = taskScheduler.readLockEntry(baseDN);
try
{
Entry e = taskScheduler.getRecurringTaskEntry(baseDN);
@@ -917,7 +913,7 @@
}
finally
{
- taskScheduler.unlockEntry(baseDN, lock);
+ lock.unlock();
}
}
else
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskScheduler.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskScheduler.java
index ce3d125..d120d6c 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskScheduler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskScheduler.java
@@ -35,7 +35,6 @@
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.forgerock.i18n.LocalizableMessage;
@@ -45,6 +44,7 @@
import org.opends.server.core.SearchOperation;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.types.*;
+import org.opends.server.types.LockManager.DNLock;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.util.LDIFException;
@@ -1553,22 +1553,20 @@
*
* @return The write lock that has been acquired for the entry.
*/
- Lock writeLockEntry(DN entryDN)
+ DNLock writeLockEntry(DN entryDN)
{
- Lock lock = LockManager.lockWrite(entryDN);
+ DNLock lock = null;
while (lock == null)
{
- lock = LockManager.lockWrite(entryDN);
+ lock = DirectoryServer.getLockManager().tryWriteLockEntry(entryDN);
}
-
return lock;
}
/**
- * Attempts to acquire a read lock on the specified entry, trying up to five
- * times before failing.
+ * Attempts to acquire a read lock on the specified entry.
*
* @param entryDN The DN of the entry for which to acquire the read lock.
*
@@ -1576,32 +1574,14 @@
*
* @throws DirectoryException If the read lock cannot be acquired.
*/
- Lock readLockEntry(DN entryDN)
- throws DirectoryException
+ DNLock readLockEntry(DN entryDN) throws DirectoryException
{
- final Lock lock = LockManager.lockRead(entryDN);
- if (lock == null)
- {
- throw new DirectoryException(ResultCode.BUSY,
- ERR_BACKEND_CANNOT_LOCK_ENTRY.get(entryDN));
- }
- else
+ final DNLock lock = DirectoryServer.getLockManager().tryReadLockEntry(entryDN);
+ if (lock != null)
{
return lock;
}
- }
-
-
-
- /**
- * Releases the lock held on the specified entry.
- *
- * @param entryDN The DN of the entry for which the lock is held.
- * @param lock The lock held on the entry.
- */
- void unlockEntry(DN entryDN, Lock lock)
- {
- LockManager.unlock(entryDN, lock);
+ throw new DirectoryException(ResultCode.BUSY, ERR_BACKEND_CANNOT_LOCK_ENTRY.get(entryDN));
}
@@ -1670,8 +1650,7 @@
for (Task t : tasks.values())
{
DN taskEntryDN = t.getTaskEntryDN();
- Lock lock = readLockEntry(taskEntryDN);
-
+ DNLock lock = readLockEntry(taskEntryDN);
try
{
Entry e = t.getTaskEntry().duplicate(true);
@@ -1682,7 +1661,7 @@
}
finally
{
- unlockEntry(taskEntryDN, lock);
+ lock.unlock();
}
}
@@ -1818,8 +1797,7 @@
for (RecurringTask rt : recurringTasks.values())
{
DN recurringTaskEntryDN = rt.getRecurringTaskEntryDN();
- Lock lock = readLockEntry(recurringTaskEntryDN);
-
+ DNLock lock = readLockEntry(recurringTaskEntryDN);
try
{
Entry e = rt.getRecurringTaskEntry().duplicate(true);
@@ -1830,7 +1808,7 @@
}
finally
{
- unlockEntry(recurringTaskEntryDN, lock);
+ lock.unlock();
}
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java b/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
index 0dbcad8..2df4fcd 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
@@ -801,6 +801,9 @@
/** The Disk Space Monitor */
private DiskSpaceMonitor diskSpaceMonitor;
+ /** The lock manager which will be used for coordinating access to LDAP entries */
+ private final LockManager lockManager = new LockManager();
+
/**
* The maximum size that internal buffers will be allowed to grow to until
* they are trimmed.
@@ -7208,7 +7211,6 @@
TimeThread.start();
getNewInstance(config);
- LockManager.reinitializeLockTable();
directoryServer.bootstrapServer();
directoryServer.initializeConfiguration();
return directoryServer;
@@ -8491,5 +8493,15 @@
return directoryServer.maxInternalBufferSize;
}
+ /**
+ * Returns the lock manager which will be used for coordinating access to LDAP entries.
+ *
+ * @return the lock manager which will be used for coordinating access to LDAP entries.
+ */
+ public static LockManager getLockManager()
+ {
+ return directoryServer.lockManager;
+ }
+
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/FIFOEntryCache.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/FIFOEntryCache.java
index a367215..93e6400 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/FIFOEntryCache.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/FIFOEntryCache.java
@@ -49,7 +49,6 @@
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
-import org.opends.server.types.LockManager;
import org.opends.server.types.SearchFilter;
import org.opends.server.util.ServerConstants;
@@ -117,7 +116,7 @@
private FIFOEntryCacheCfg registeredConfiguration;
/** The maximum length of time to try to obtain a lock before giving up. */
- private long lockTimeout = LockManager.DEFAULT_TIMEOUT;
+ private long lockTimeout = 2000;
/** Creates a new instance of this FIFO entry cache. */
public FIFOEntryCache()
@@ -850,7 +849,7 @@
* @return <CODE>true</CODE> if configuration is acceptable,
* or <CODE>false</CODE> otherwise.
*/
- public boolean processEntryCacheConfig(
+ private boolean processEntryCacheConfig(
FIFOEntryCacheCfg configuration,
boolean applyChanges,
EntryCacheCommon.ConfigErrorHandler errorHandler
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/PasswordModifyExtendedOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/PasswordModifyExtendedOperation.java
index e3331b2..78d4d22 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/PasswordModifyExtendedOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/PasswordModifyExtendedOperation.java
@@ -36,7 +36,6 @@
import java.io.IOException;
import java.util.*;
-import java.util.concurrent.locks.Lock;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
@@ -65,6 +64,7 @@
import org.opends.server.schema.AuthPasswordSyntax;
import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.types.*;
+import org.opends.server.types.LockManager.DNLock;
/**
* This class implements the password modify extended operation defined in RFC
@@ -253,10 +253,9 @@
// See if a user identity was provided. If so, then try to resolve it to
// an actual user.
- DN userDN = null;
+ DN userDN = null;
Entry userEntry = null;
- Lock userLock = null;
-
+ DNLock userLock = null;
try
{
if (userIdentity == null)
@@ -272,16 +271,7 @@
return;
}
- // Retrieve a write lock on that user's entry.
userDN = requestorEntry.getName();
- userLock = LockManager.lockWrite(userDN);
- if (userLock == null)
- {
- operation.setResultCode(ResultCode.BUSY);
- operation.appendErrorMessage(ERR_EXTOP_PASSMOD_CANNOT_LOCK_USER_ENTRY.get(userDN));
- return;
- }
-
userEntry = requestorEntry;
}
else
@@ -341,11 +331,13 @@
return;
}
}
- // the userIdentity provided does not follow Authorization Identity form. RFC3062 declaration "may or may
- // not be an LDAPDN" allows for pretty much anything in that field. we gonna try to parse it as DN first
- // then if that fails as user ID.
else
{
+ /*
+ * the userIdentity provided does not follow Authorization Identity form. RFC3062
+ * declaration "may or may not be an LDAPDN" allows for pretty much anything in that
+ * field. we gonna try to parse it as DN first then if that fails as user ID.
+ */
try
{
userDN = DN.valueOf(authzIDStr);
@@ -384,6 +376,14 @@
}
}
+ userLock = DirectoryServer.getLockManager().tryWriteLockEntry(userDN);
+ if (userLock == null)
+ {
+ operation.setResultCode(ResultCode.BUSY);
+ operation.appendErrorMessage(ERR_EXTOP_PASSMOD_CANNOT_LOCK_USER_ENTRY.get(userDN));
+ return;
+ }
+
// At this point, we should have the user entry. Get the associated password policy.
PasswordPolicyState pwPolicyState;
try
@@ -908,7 +908,7 @@
{
if (userLock != null)
{
- LockManager.unlock(userDN, userLock);
+ userLock.unlock();
}
}
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java b/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java
index ac2ebd6..e17a800 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java
@@ -30,7 +30,6 @@
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
-import java.util.concurrent.locks.Lock;
import org.forgerock.i18n.LocalizableMessage;
import org.opends.server.admin.std.server.SynchronizationProviderCfg;
@@ -43,10 +42,13 @@
import org.opends.server.core.SchemaConfigManager;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.types.*;
+import org.opends.server.types.LockManager.DNLock;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ResultCode;
+
import static org.opends.messages.TaskMessages.*;
import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.core.DirectoryServer.getSchemaDN;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -179,11 +181,10 @@
{
// Obtain a write lock on the server schema so that we can be sure nothing
// else tries to write to it at the same time.
- DN schemaDN = DirectoryServer.getSchemaDN();
- final Lock schemaLock = LockManager.lockWrite(schemaDN);
+ final DNLock schemaLock = DirectoryServer.getLockManager().tryWriteLockEntry(getSchemaDN());
if (schemaLock == null)
{
- logger.error(ERR_TASK_ADDSCHEMAFILE_CANNOT_LOCK_SCHEMA, schemaDN);
+ logger.error(ERR_TASK_ADDSCHEMAFILE_CANNOT_LOCK_SCHEMA, getSchemaDN());
return TaskState.STOPPED_BY_ERROR;
}
@@ -267,7 +268,7 @@
}
finally
{
- LockManager.unlock(schemaDN, schemaLock);
+ schemaLock.unlock();
}
}
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/DirectoryEnvironmentConfig.java b/opendj-server-legacy/src/main/java/org/opends/server/types/DirectoryEnvironmentConfig.java
index 3ad478b..676a5a7 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/types/DirectoryEnvironmentConfig.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/DirectoryEnvironmentConfig.java
@@ -1117,158 +1117,6 @@
- /**
- * Retrieves the concurrency level for the Directory Server lock
- * table.
- *
- * @return The concurrency level for the Directory Server lock
- * table.
- */
- public int getLockManagerConcurrencyLevel()
- {
- String levelStr =
- getProperty(PROPERTY_LOCK_MANAGER_CONCURRENCY_LEVEL);
- if (levelStr == null)
- {
- return LockManager.DEFAULT_CONCURRENCY_LEVEL;
- }
-
- int concurrencyLevel;
- try
- {
- concurrencyLevel = Integer.parseInt(levelStr);
- }
- catch (Exception e)
- {
- return LockManager.DEFAULT_CONCURRENCY_LEVEL;
- }
-
- if (concurrencyLevel <= 0)
- {
- return LockManager.DEFAULT_CONCURRENCY_LEVEL;
- }
- else
- {
- return concurrencyLevel;
- }
- }
-
-
-
- /**
- * Specifies the concurrency level for the Directory Server lock
- * table. This should be set to the maximum number of threads that
- * could attempt to interact with the lock table at any given time.
- *
- * @param concurrencyLevel The concurrency level for the Directory
- * Server lock manager.
- *
- * @return The previously-configured concurrency level. If there
- * was no previously-configured value, then the default
- * concurrency level will be returned.
- *
- * @throws InitializationException If the Directory Server is
- * already running or there is a
- * problem with the provided
- * concurrency level value.
- */
- public int setLockManagerConcurrencyLevel(int concurrencyLevel)
- throws InitializationException
- {
- checkServerIsRunning();
-
- if (concurrencyLevel <= 0)
- {
- throw new InitializationException(
- ERR_DIRCFG_INVALID_CONCURRENCY_LEVEL.get(
- concurrencyLevel));
- }
-
- String concurrencyStr =
- setProperty(PROPERTY_LOCK_MANAGER_CONCURRENCY_LEVEL,
- String.valueOf(concurrencyLevel));
- if (concurrencyStr == null)
- {
- return LockManager.DEFAULT_CONCURRENCY_LEVEL;
- }
- else
- {
- try
- {
- return Integer.parseInt(concurrencyStr);
- }
- catch (Exception e)
- {
- return LockManager.DEFAULT_CONCURRENCY_LEVEL;
- }
- }
- }
-
- /**
- * Retrieves whether a fair ordering should be used for the lock
- * manager.
- *
- * @return True if fair orderin should be used or false otherwise
- */
- public boolean getLockManagerFairOrdering()
- {
- String sizeStr = getProperty(PROPERTY_LOCK_MANAGER_FAIR_ORDERING);
- if (sizeStr == null)
- {
- return LockManager.DEFAULT_FAIR_ORDERING;
- }
- else
- {
- try
- {
- return Boolean.parseBoolean(sizeStr);
- }
- catch (Exception e)
- {
- return LockManager.DEFAULT_FAIR_ORDERING;
- }
- }
- }
-
- /**
- * Specifies whether a fair ordering should be used for the lock
- * manager.
- *
- * @param fairOrdering {@code true} if fair ordering should be
- * used, or {@code false} if not.
- *
- * @return The previously-configured setting for fair ordering. If
- * there was no previously-configured value, then the
- * default initial setting will be returned.
- *
- * @throws InitializationException If the Directory Server is
- * already running.
- */
- public boolean setLockManagerFairOrdering(boolean fairOrdering)
- throws InitializationException
- {
- checkServerIsRunning();
-
- String fairOrderingStr =
- setProperty(PROPERTY_LOCK_MANAGER_FAIR_ORDERING,
- String.valueOf(fairOrdering));
- if (fairOrderingStr == null)
- {
- return LockManager.DEFAULT_FAIR_ORDERING;
- }
- else
- {
- try
- {
- return Boolean.parseBoolean(fairOrderingStr);
- }
- catch (Exception e)
- {
- return LockManager.DEFAULT_FAIR_ORDERING;
- }
- }
- }
-
/** Throws an exception if server is running and it is not allowed. */
private void checkServerIsRunning() throws InitializationException
{
@@ -1278,84 +1126,4 @@
ERR_DIRCFG_SERVER_ALREADY_RUNNING.get());
}
}
-
- /**
- * Retrieves the initial table size for the server lock table. This
- * can be used to ensure that the lock table has the appropriate
- * size for the expected number of locks that will be held at any
- * given time.
- *
- * @return The initial table size for the server lock table.
- */
- public int getLockManagerTableSize()
- {
- String sizeStr = getProperty(PROPERTY_LOCK_MANAGER_TABLE_SIZE);
- if (sizeStr == null)
- {
- return LockManager.DEFAULT_INITIAL_TABLE_SIZE;
- }
- else
- {
- try
- {
- return Integer.parseInt(sizeStr);
- }
- catch (Exception e)
- {
- return LockManager.DEFAULT_INITIAL_TABLE_SIZE;
- }
- }
- }
-
-
-
- /**
- * Specifies the initial table size for the server lock table. This
- * can be used to ensure taht the lock table has the appropriate
- * size for the expected number of locks that will be held at any
- * given time.
- *
- * @param lockTableSize The initial table size for the server lock
- * table.
- *
- * @return The previously-configured initial lock table size. If
- * there was no previously-configured value, then the
- * default initial table size will be returned.
- *
- * @throws InitializationException If the Directory Server is
- * already running or there is a
- * problem with the provided
- * initial table size.
- */
- public int setLockManagerTableSize(int lockTableSize)
- throws InitializationException
- {
- checkServerIsRunning();
-
- if (lockTableSize <= 0)
- {
- throw new InitializationException(
- ERR_DIRCFG_INVALID_LOCK_TABLE_SIZE.get(
- lockTableSize));
- }
-
- String tableSizeStr =
- setProperty(PROPERTY_LOCK_MANAGER_TABLE_SIZE,
- String.valueOf(lockTableSize));
- if (tableSizeStr == null)
- {
- return LockManager.DEFAULT_INITIAL_TABLE_SIZE;
- }
- else
- {
- try
- {
- return Integer.parseInt(tableSizeStr);
- }
- catch (Exception e)
- {
- return LockManager.DEFAULT_INITIAL_TABLE_SIZE;
- }
- }
- }
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/LockManager.java b/opendj-server-legacy/src/main/java/org/opends/server/types/LockManager.java
index 11dace6..f5c29a6 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/types/LockManager.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/LockManager.java
@@ -21,629 +21,517 @@
* CDDL HEADER END
*
*
- * Copyright 2006-2008 Sun Microsystems, Inc.
- * Portions Copyright 2013-2015 ForgeRock AS.
+ * Copyright 2015 ForgeRock AS.
*/
package org.opends.server.types;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import org.opends.server.core.DirectoryServer;
-import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.util.Reject;
/**
- * This class defines a Directory Server component that can keep track
- * of all locks needed throughout the Directory Server. It is
- * intended primarily for entry locking but support for other types of
- * objects might be added in the future.
+ * A lock manager coordinates directory update operations so that the DIT structure remains in a
+ * consistent state, as well as providing repeatable read isolation. When accessing entries
+ * components need to ensure that they have the appropriate lock:
+ * <ul>
+ * <li>repeatable reads: repeatable read isolation is rarely needed in practice, since all backend
+ * reads are guaranteed to be performed with read-committed isolation, which is normally sufficient.
+ * Specifically, read-only operations such as compare and search do not require any additional
+ * locking. If repeatable read isolation is required then lock the entry using
+ * {@link #tryReadLockEntry(DN)}
+ * <li>modifying an entry: acquire an entry write-lock for the target entry using
+ * {@link #tryWriteLockEntry(DN)}. Updates are typically performed using a read-modify-write cycle,
+ * so the write lock should be acquired before performing the initial read in order to ensure
+ * consistency
+ * <li>adding an entry: client code must acquire an entry write-lock for the target entry using
+ * {@link #tryWriteLockEntry(DN)}. The parent entry will automatically be protected from deletion by
+ * an implicit subtree read lock on the parent
+ * <li>deleting an entry: client code must acquire a subtree write lock for the target entry using
+ * {@link #tryWriteLockSubtree(DN)}
+ * <li>renaming an entry: client code must acquire a subtree write lock for the old entry, and a
+ * subtree write lock for the new entry using {@link #tryWriteLockSubtree(DN)}. Care should be taken
+ * to avoid deadlocks, e.g. by locking the DN which sorts first.
+ * </ul>
+ * In addition, backend implementations may choose to use their own lock manager for enforcing
+ * atomicity and isolation. This is typically the case for backends which cannot take advantage of
+ * atomicity guarantees provided by an underlying DB (the task backend is one such example).
+ * <p>
+ * <b>Implementation Notes</b>
+ * <p>
+ * The lock table is conceptually a cache of locks keyed on DN, i.e. a {@code Map<DN, DNLock>}.
+ * Locks must be kept in the cache while they are locked, but may be removed once they are no longer
+ * locked by any threads. Locks are represented using a pair of read-write locks: the first lock is
+ * the "subtree" lock and the second is the "entry" lock.
+ * <p>
+ * In order to lock an entry for read or write a <b>subtree</b> read lock is first acquired on each
+ * of the parent entries from the root DN down to the immediate parent of the entry to be locked.
+ * Then the appropriate read or write <b>entry</b> lock is acquired for the target entry. Subtree
+ * write locking is performed by acquiring a <b>subtree</b> read lock on each of the parent entries
+ * from the root DN down to the immediate parent of the subtree to be locked. Then a <b>subtree</b>
+ * write lock is acquired for the target subtree.
+ * <p>
+ * The lock table itself is not represented using a {@code ConcurrentHashMap} because the JDK6/7
+ * APIs do not provide the ability to atomically add-and-lock or unlock-and-remove locks (this
+ * capability is provided in JDK8). Instead, we provide our own implementation comprising of a fixed
+ * number of buckets, a bucket being a {@code LinkedList} of {@code DNLock}s. In addition, it is
+ * important to be able to efficiently iterate up and down a chain of hierarchically related locks,
+ * so each lock maintains a reference to its parent lock. Modern directories tend to have a flat
+ * structure so it is also important to avoid contention on "hot" parent DNs. Typically, a lock
+ * attempt against a DN will involve a cache miss for the target DN and a cache hit for the parent,
+ * but the parent will be the same parent for all lock requests, resulting in a lot of contention on
+ * the same lock bucket. To avoid this the lock manager maintains a small-thread local cache of
+ * locks, so that parent locks can be acquired using a lock-free algorithm.
+ * <p>
+ * Since the thread local cache may reference locks which are not actively locked by anyone, a
+ * reference counting mechanism is used in order to prevent cached locks from being removed from the
+ * underlying lock table. The reference counting mechanism is also used for references between a
+ * lock and its parent lock. To summarize, locking a DN involves the following steps:
+ * <ul>
+ * <li>get the lock from the thread local cache. If the lock was not in the thread local cache then
+ * try fetching it from the lock table:
+ * <ul>
+ * <li><i>found</i> - store it in the thread local cache and bump the reference count
+ * <li><i>not found</i> - create a new lock. First fetch the parent lock using the same process,
+ * i.e. looking in the thread local cache, etc. Then create a new lock referencing the parent lock
+ * (bumps the reference count for the parent lock), and store it in the lock table and the thread
+ * local cache with a reference count of 1.
+ * </ul>
+ * <li>return the lock to the application and increment its reference count since the application
+ * now also has a reference to the lock.
+ * </ul>
+ * Locks are dereferenced when they are unlocked, when they are evicted from a thread local cache,
+ * and when a child lock's reference count reaches zero. A lock is completely removed from the lock
+ * table once its reference count reaches zero.
*/
-@org.opends.server.types.PublicAPI(
- stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
- mayInstantiate=false,
- mayExtend=false,
- mayInvoke=true)
+@org.opends.server.types.PublicAPI(stability = org.opends.server.types.StabilityLevel.UNCOMMITTED,
+ mayInstantiate = false, mayExtend = false, mayInvoke = true)
public final class LockManager
{
- private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
-
/**
- * The default setting for the use of fair ordering locks.
+ * A lock on an entry or subtree. A lock can only be unlocked once.
*/
- public static final boolean DEFAULT_FAIR_ORDERING = true;
-
- /**
- * The default initial size to use for the lock table.
- */
- public static final int DEFAULT_INITIAL_TABLE_SIZE = 64;
-
-
-
- /**
- * The default concurrency level to use for the lock table.
- */
- public static final int DEFAULT_CONCURRENCY_LEVEL = 32;
-
-
-
- /**
- * The default load factor to use for the lock table.
- */
- public static final float DEFAULT_LOAD_FACTOR = 0.75F;
-
-
-
- /**
- * The default length of time in milliseconds to wait while
- * attempting to acquire a read or write lock.
- */
- public static final long DEFAULT_TIMEOUT = 9000;
-
-
-
- /** The set of entry locks that the server knows about. */
- private static
- ConcurrentHashMap<DN,ReentrantReadWriteLock> lockTable;
-
- /** Whether fair ordering should be used on the locks. */
- private static boolean fair;
-
-
-
- /** Initialize the lock table. */
- static
+ public final class DNLock
{
- DirectoryEnvironmentConfig environmentConfig =
- DirectoryServer.getEnvironmentConfig();
- lockTable = new ConcurrentHashMap<DN,ReentrantReadWriteLock>(
- environmentConfig.getLockManagerTableSize(),
- DEFAULT_LOAD_FACTOR,
- environmentConfig.getLockManagerConcurrencyLevel());
- fair = environmentConfig.getLockManagerFairOrdering();
- }
+ private final DNLockHolder lock;
+ private final Lock subtreeLock;
+ private final Lock entryLock;
+ private boolean isLocked = true;
-
-
- /**
- * Recreates the lock table. This should be called only in the
- * case that the Directory Server is in the process of an in-core
- * restart because it will destroy the existing lock table.
- */
- public static synchronized void reinitializeLockTable()
- {
- ConcurrentHashMap<DN,ReentrantReadWriteLock> oldTable = lockTable;
-
- DirectoryEnvironmentConfig environmentConfig =
- DirectoryServer.getEnvironmentConfig();
- lockTable = new ConcurrentHashMap<DN,ReentrantReadWriteLock>(
- environmentConfig.getLockManagerTableSize(),
- DEFAULT_LOAD_FACTOR,
- environmentConfig.getLockManagerConcurrencyLevel());
-
- if (! oldTable.isEmpty())
+ private DNLock(final DNLockHolder lock, final Lock subtreeLock, final Lock entryLock)
{
- for (DN dn : oldTable.keySet())
- {
- try
- {
- ReentrantReadWriteLock lock = oldTable.get(dn);
- if (lock.isWriteLocked())
- {
- logger.trace("Found stale write lock on %s", dn);
- }
- else if (lock.getReadLockCount() > 0)
- {
- logger.trace("Found stale read lock on %s", dn);
- }
- else
- {
- logger.trace("Found stale unheld lock on %s", dn);
- }
- }
- catch (Exception e)
- {
- logger.traceException(e);
- }
- }
-
- oldTable.clear();
+ this.lock = lock;
+ this.subtreeLock = subtreeLock;
+ this.entryLock = entryLock;
}
- fair = environmentConfig.getLockManagerFairOrdering();
+ @Override
+ public String toString()
+ {
+ return lock.toString();
+ }
+
+ /**
+ * Unlocks this lock and releases any blocked threads.
+ *
+ * @throws IllegalStateException
+ * If this lock has already been unlocked.
+ */
+ public void unlock()
+ {
+ if (!isLocked)
+ {
+ throw new IllegalStateException("Already unlocked");
+ }
+ lock.releaseParentSubtreeReadLock();
+ subtreeLock.unlock();
+ entryLock.unlock();
+ dereference(lock);
+ isLocked = false;
+ }
+
+ // For unit testing.
+ int refCount()
+ {
+ return lock.refCount.get();
+ }
}
-
-
/**
- * Attempts to acquire a read lock on the specified entry. It will
- * succeed only if the write lock is not already held. If any
- * blocking is required, then this call will fail rather than block.
- *
- * @param entryDN The DN of the entry for which to obtain the read
- * lock.
- *
- * @return The read lock that was acquired, or {@code null} if it
- * was not possible to obtain a read lock for some reason.
+ * Lock implementation
*/
- private static Lock tryLockRead(DN entryDN)
+ private final class DNLockHolder
{
- ReentrantReadWriteLock entryLock =
- new ReentrantReadWriteLock(fair);
- Lock readLock = entryLock.readLock();
- readLock.lock();
+ private final AtomicInteger refCount = new AtomicInteger();
+ private final DNLockHolder parent;
+ private final DN dn;
+ private final int dnHashCode;
+ private final ReentrantReadWriteLock subtreeLock = new ReentrantReadWriteLock();
+ private final ReentrantReadWriteLock entryLock = new ReentrantReadWriteLock();
- ReentrantReadWriteLock existingLock =
- lockTable.putIfAbsent(entryDN, entryLock);
- if (existingLock == null)
+ DNLockHolder(final DNLockHolder parent, final DN dn, final int dnHashCode)
{
- return readLock;
+ this.parent = parent;
+ this.dn = dn;
+ this.dnHashCode = dnHashCode;
}
- else
+
+ @Override
+ public String toString()
{
- // There's a lock in the table, but it could potentially be
- // unheld. We'll do an unsafe check to see whether it might be
- // held and if so then fail to acquire the lock.
- if (existingLock.isWriteLocked())
- {
- readLock.unlock();
- return null;
- }
+ return "\"" + dn + "\" : " + refCount;
+ }
- // We will never remove a lock from the table without holding
- // its monitor. Since there's already a lock in the table, then
- // get its monitor and try to acquire the lock. This should
- // prevent the owner from releasing the lock and removing it
- // from the table before it can be acquired by another thread.
- synchronized (existingLock)
+ /**
+ * Unlocks the subtree read lock from the parent of this lock up to the root.
+ */
+ void releaseParentSubtreeReadLock()
+ {
+ for (DNLockHolder lock = parent; lock != null; lock = lock.parent)
{
- ReentrantReadWriteLock existingLock2 =
- lockTable.putIfAbsent(entryDN, entryLock);
- if (existingLock2 == null)
- {
- return readLock;
- }
- else if (existingLock == existingLock2)
- {
- // We were able to synchronize on the lock's monitor while
- // the lock was still in the table. Try to acquire it now
- // (which will succeed if the lock isn't held by anything)
- // and either return it or return null.
- readLock.unlock();
- readLock = existingLock.readLock();
-
- try
- {
- if (readLock.tryLock(0, TimeUnit.SECONDS))
- {
- return readLock;
- }
- else
- {
- return null;
- }
- }
- catch(InterruptedException ie)
- {
- // This should never happen. Just return null
- return null;
- }
- }
- else
- {
- // If this happens, then it means that while we were waiting
- // the existing lock was unlocked and removed from the table
- // and a new one was created and added to the table. This
- // is more trouble than it's worth, so return null.
- readLock.unlock();
- return null;
- }
+ lock.subtreeLock.readLock().unlock();
}
}
- }
-
-
- /**
- * Attempts to acquire a read lock for the specified entry.
- * Multiple threads can hold the read lock concurrently for an entry
- * as long as the write lock is held. If the write lock is held,
- * then no other read or write locks will be allowed for that entry
- * until the write lock is released. A default timeout will be used
- * for the lock.
- *
- * @param entryDN The DN of the entry for which to obtain the read
- * lock.
- *
- * @return The read lock that was acquired, or {@code null} if it
- * was not possible to obtain a read lock for some reason.
- */
- public static Lock lockRead(DN entryDN)
- {
- return lockRead(entryDN, DEFAULT_TIMEOUT);
- }
-
-
-
- /**
- * Attempts to acquire a read lock for the specified entry.
- * Multiple threads can hold the read lock concurrently for an entry
- * as long as the write lock is not held. If the write lock is
- * held, then no other read or write locks will be allowed for that
- * entry until the write lock is released.
- *
- * @param entryDN The DN of the entry for which to obtain the read
- * lock.
- * @param timeout The maximum length of time in milliseconds to
- * wait for the lock before timing out.
- *
- * @return The read lock that was acquired, or <CODE>null</CODE> if
- * it was not possible to obtain a read lock for some
- * reason.
- */
- private static Lock lockRead(DN entryDN, long timeout)
- {
- // First, try to get the lock without blocking.
- Lock readLock = tryLockRead(entryDN);
- if (readLock != null)
+ DNLock tryReadLockEntry()
{
- return readLock;
+ return tryLock(subtreeLock.readLock(), entryLock.readLock());
}
- ReentrantReadWriteLock entryLock =
- new ReentrantReadWriteLock(fair);
- readLock = entryLock.readLock();
- readLock.lock();
-
- ReentrantReadWriteLock existingLock =
- lockTable.putIfAbsent(entryDN, entryLock);
- if (existingLock == null)
+ DNLock tryWriteLockEntry()
{
- return readLock;
+ return tryLock(subtreeLock.readLock(), entryLock.writeLock());
}
- long surrenderTime = System.currentTimeMillis() + timeout;
- readLock.unlock();
- readLock = existingLock.readLock();
+ DNLock tryWriteLockSubtree()
+ {
+ return tryLock(subtreeLock.writeLock(), entryLock.writeLock());
+ }
- while (true)
+ /**
+ * Locks the subtree read lock from the root down to the parent of this lock.
+ */
+ private boolean tryAcquireParentSubtreeReadLock()
+ {
+ // First lock the parents of the parent.
+ if (parent == null)
+ {
+ return true;
+ }
+
+ if (!parent.tryAcquireParentSubtreeReadLock())
+ {
+ return false;
+ }
+
+ // Then lock the parent of this lock
+ if (tryLockWithTimeout(parent.subtreeLock.readLock()))
+ {
+ return true;
+ }
+
+ // Failed to grab the parent lock within the timeout, so roll-back the other locks.
+ releaseParentSubtreeReadLock();
+ return false;
+ }
+
+ private DNLock tryLock(final Lock subtreeLock, final Lock entryLock)
+ {
+ if (tryAcquireParentSubtreeReadLock())
+ {
+ if (tryLockWithTimeout(subtreeLock))
+ {
+ if (tryLockWithTimeout(entryLock))
+ {
+ return new DNLock(this, subtreeLock, entryLock);
+ }
+ subtreeLock.unlock();
+ }
+ releaseParentSubtreeReadLock();
+ }
+ // Failed to acquire all the necessary locks within the time out.
+ dereference(this);
+ return null;
+ }
+
+ private boolean tryLockWithTimeout(final Lock lock)
{
try
{
- // See if we can acquire the lock while it's still in the
- // table within the given timeout.
- if (readLock.tryLock(timeout, TimeUnit.MILLISECONDS))
+ return lock.tryLock(lockTimeout, lockTimeoutUnits);
+ }
+ catch (final InterruptedException e)
+ {
+ // Unable to handle interrupts here.
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+ }
+
+ private static final long DEFAULT_LOCK_TIMEOUT = 9;
+ private static final TimeUnit DEFAULT_LOCK_TIMEOUT_UNITS = TimeUnit.SECONDS;
+ private static final int MINIMUM_NUMBER_OF_BUCKETS = 64;
+ private static final int THREAD_LOCAL_CACHE_SIZE = 8;
+
+ private final int numberOfBuckets;
+ private final LinkedList<DNLockHolder>[] lockTable;
+ private final long lockTimeout;
+ private final TimeUnit lockTimeoutUnits;
+
+ // Avoid sub-classing in order to workaround class leaks in app servers.
+ private final ThreadLocal<LinkedList<DNLockHolder>> threadLocalCache = new ThreadLocal<LinkedList<DNLockHolder>>();
+
+ /**
+ * Creates a new lock manager with a lock timeout of 9 seconds and an automatically chosen number
+ * of lock table buckets based on the number of processors.
+ */
+ public LockManager()
+ {
+ this(DEFAULT_LOCK_TIMEOUT, DEFAULT_LOCK_TIMEOUT_UNITS);
+ }
+
+ /**
+ * Creates a new lock manager with the specified lock timeout and an automatically chosen number
+ * of lock table buckets based on the number of processors.
+ *
+ * @param lockTimeout
+ * The lock timeout.
+ * @param lockTimeoutUnit
+ * The lock timeout units.
+ */
+ public LockManager(final long lockTimeout, final TimeUnit lockTimeoutUnit)
+ {
+ this(lockTimeout, lockTimeoutUnit, Runtime.getRuntime().availableProcessors() * 8);
+ }
+
+ /**
+ * Creates a new lock manager with the provided configuration.
+ *
+ * @param lockTimeout
+ * The lock timeout.
+ * @param lockTimeoutUnit
+ * The lock timeout units.
+ * @param numberOfBuckets
+ * The number of buckets to use in the lock table. The minimum number of buckets is 64.
+ */
+ @SuppressWarnings("unchecked")
+ public LockManager(final long lockTimeout, final TimeUnit lockTimeoutUnit, final int numberOfBuckets)
+ {
+ Reject.ifFalse(lockTimeout >= 0, "lockTimeout must be a non-negative integer");
+ Reject.ifNull(lockTimeoutUnit, "lockTimeoutUnit must be non-null");
+ Reject.ifFalse(numberOfBuckets > 0, "numberOfBuckets must be a positive integer");
+
+ this.lockTimeout = lockTimeout;
+ this.lockTimeoutUnits = lockTimeoutUnit;
+ this.numberOfBuckets = getNumberOfBuckets(numberOfBuckets);
+ this.lockTable = new LinkedList[this.numberOfBuckets];
+ for (int i = 0; i < this.numberOfBuckets; i++)
+ {
+ this.lockTable[i] = new LinkedList<DNLockHolder>();
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ final StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < numberOfBuckets; i++)
+ {
+ final LinkedList<DNLockHolder> bucket = lockTable[i];
+ synchronized (bucket)
+ {
+ for (final DNLockHolder lock : bucket)
{
- synchronized (existingLock)
+ builder.append(lock);
+ builder.append('\n');
+ }
+ }
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Acquires the read lock for the specified entry. This method will block if the entry is already
+ * write locked or if the entry, or any of its parents, have the subtree write lock taken.
+ *
+ * @param entry
+ * The entry whose read lock is required.
+ * @return The lock, or {@code null} if the lock attempt timed out.
+ */
+ public DNLock tryReadLockEntry(final DN entry)
+ {
+ return acquireLockFromCache(entry).tryReadLockEntry();
+ }
+
+ /**
+ * Acquires the write lock for the specified entry. This method will block if the entry is already
+ * read or write locked or if the entry, or any of its parents, have the subtree write lock taken.
+ *
+ * @param entry
+ * The entry whose write lock is required.
+ * @return The lock, or {@code null} if the lock attempt timed out.
+ */
+ public DNLock tryWriteLockEntry(final DN entry)
+ {
+ return acquireLockFromCache(entry).tryWriteLockEntry();
+ }
+
+ /**
+ * Acquires the write lock for the specified subtree. This method will block if any entry or
+ * subtree within the subtree is already read or write locked or if any of the parent entries of
+ * the subtree have the subtree write lock taken.
+ *
+ * @param subtree
+ * The subtree whose write lock is required.
+ * @return The lock, or {@code null} if the lock attempt timed out.
+ */
+ public DNLock tryWriteLockSubtree(final DN subtree)
+ {
+ return acquireLockFromCache(subtree).tryWriteLockSubtree();
+ }
+
+ // For unit testing.
+ int getLockTableRefCountFor(final DN dn)
+ {
+ final int dnHashCode = dn.hashCode();
+ final LinkedList<DNLockHolder> bucket = getBucket(dnHashCode);
+ synchronized (bucket)
+ {
+ for (final DNLockHolder lock : bucket)
+ {
+ if (lock.dnHashCode == dnHashCode && lock.dn.equals(dn))
+ {
+ return lock.refCount.get();
+ }
+ }
+ return -1;
+ }
+ }
+
+ //For unit testing.
+ int getThreadLocalCacheRefCountFor(final DN dn)
+ {
+ final LinkedList<DNLockHolder> cache = threadLocalCache.get();
+ if (cache == null)
+ {
+ return -1;
+ }
+ final int dnHashCode = dn.hashCode();
+ for (final DNLockHolder lock : cache)
+ {
+ if (lock.dnHashCode == dnHashCode && lock.dn.equals(dn))
+ {
+ return lock.refCount.get();
+ }
+ }
+ return -1;
+ }
+
+ private DNLockHolder acquireLockFromCache(final DN dn)
+ {
+ LinkedList<DNLockHolder> cache = threadLocalCache.get();
+ if (cache == null)
+ {
+ cache = new LinkedList<DNLockHolder>();
+ threadLocalCache.set(cache);
+ }
+ return acquireLockFromCache0(dn, cache);
+ }
+
+ private DNLockHolder acquireLockFromCache0(final DN dn, final LinkedList<DNLockHolder> cache)
+ {
+ final int dnHashCode = dn.hashCode();
+ DNLockHolder lock = removeLock(cache, dn, dnHashCode);
+ if (lock == null)
+ {
+ lock = acquireLockFromLockTable(dn, dnHashCode, cache);
+ if (cache.size() >= THREAD_LOCAL_CACHE_SIZE)
+ {
+ // Cache too big: evict oldest entry.
+ dereference(cache.removeLast());
+ }
+ }
+ cache.addFirst(lock); // optimize for LRU
+ lock.refCount.incrementAndGet();
+ return lock;
+ }
+
+ private DNLockHolder acquireLockFromLockTable(final DN dn, final int dnHashCode, final LinkedList<DNLockHolder> cache)
+ {
+ final LinkedList<DNLockHolder> bucket = getBucket(dnHashCode);
+ synchronized (bucket)
+ {
+ DNLockHolder lock = removeLock(bucket, dn, dnHashCode);
+ if (lock == null)
+ {
+ final DN parentDN = dn.parent();
+ final DNLockHolder parentLock = parentDN != null ? acquireLockFromCache0(parentDN, cache) : null;
+ lock = new DNLockHolder(parentLock, dn, dnHashCode);
+ }
+ bucket.addFirst(lock); // optimize for LRU
+ lock.refCount.incrementAndGet();
+ return lock;
+ }
+ }
+
+ private void dereference(final DNLockHolder lock)
+ {
+ if (lock.refCount.decrementAndGet() <= 0)
+ {
+ final LinkedList<DNLockHolder> bucket = getBucket(lock.dnHashCode);
+ synchronized (bucket)
+ {
+ // Double check: another thread could have acquired the lock since we decremented it to zero.
+ if (lock.refCount.get() <= 0)
+ {
+ removeLock(bucket, lock.dn, lock.dnHashCode);
+ if (lock.parent != null)
{
- if (lockTable.get(entryDN) == existingLock)
- {
- // We acquired the lock within the timeout and it's
- // still in the lock table, so we're good to go.
- return readLock;
- }
- else
- {
- ReentrantReadWriteLock existingLock2 =
- lockTable.putIfAbsent(entryDN, existingLock);
- if (existingLock2 == null)
- {
- // The lock had already been removed from the table,
- // but nothing had replaced it before we put it back,
- // so we're good to go.
- return readLock;
- }
- else
- {
- readLock.unlock();
- existingLock = existingLock2;
- readLock = existingLock.readLock();
- }
- }
+ dereference(lock.parent);
}
}
- else
- {
- // We couldn't acquire the lock before the timeout occurred,
- // so we have to fail.
- return null;
- }
- } catch (InterruptedException ie) {}
-
-
- // There are only two reasons we should be here:
- // - If the attempt to acquire the lock was interrupted.
- // - If we acquired the lock but it had already been removed
- // from the table and another one had replaced it before we
- // could put it back.
- // Our only recourse is to try again, but we need to reduce the
- // timeout to account for the time we've already waited.
- timeout = surrenderTime - System.currentTimeMillis();
- if (timeout <= 0)
- {
- return null;
}
}
}
-
-
- /**
- * Attempts to acquire a write lock on the specified entry. It will
- * succeed only if the lock is not already held. If any blocking is
- * required, then this call will fail rather than block.
- *
- * @param entryDN The DN of the entry for which to obtain the
- * write lock.
- *
- * @return The write lock that was acquired, or <CODE>null</CODE>
- * if it was not possible to obtain a write lock for some
- * reason.
- */
- private static Lock tryLockWrite(DN entryDN)
+ private LinkedList<DNLockHolder> getBucket(final int dnHashCode)
{
- ReentrantReadWriteLock entryLock =
- new ReentrantReadWriteLock(fair);
- Lock writeLock = entryLock.writeLock();
- writeLock.lock();
+ return lockTable[dnHashCode & numberOfBuckets - 1];
+ }
- ReentrantReadWriteLock existingLock =
- lockTable.putIfAbsent(entryDN, entryLock);
- if (existingLock == null)
+ /*
+ * Ensure that the number of buckets is a power of 2 in order to make it easier to map hash codes
+ * to bucket indexes.
+ */
+ private int getNumberOfBuckets(final int buckets)
+ {
+ final int roundedNumberOfBuckets = Math.min(buckets, MINIMUM_NUMBER_OF_BUCKETS);
+ int powerOf2 = 1;
+ while (powerOf2 < roundedNumberOfBuckets)
{
- return writeLock;
+ powerOf2 <<= 1;
}
- else
- {
- // There's a lock in the table, but it could potentially be
- // unheld. We'll do an unsafe check to see whether it might be
- // held and if so then fail to acquire the lock.
- if ((existingLock.getReadLockCount() > 0) ||
- (existingLock.isWriteLocked()))
- {
- writeLock.unlock();
- return null;
- }
+ return powerOf2;
+ }
- // We will never remove a lock from the table without holding
- // its monitor. Since there's already a lock in the table, then
- // get its monitor and try to acquire the lock. This should
- // prevent the owner from releasing the lock and removing it
- // from the table before it can be acquired by another thread.
- synchronized (existingLock)
+ private DNLockHolder removeLock(final LinkedList<DNLockHolder> lockList, final DN dn, final int dnHashCode)
+ {
+ final Iterator<DNLockHolder> iterator = lockList.iterator();
+ while (iterator.hasNext())
+ {
+ final DNLockHolder lock = iterator.next();
+ if (lock.dnHashCode == dnHashCode && lock.dn.equals(dn))
{
- ReentrantReadWriteLock existingLock2 =
- lockTable.putIfAbsent(entryDN, entryLock);
- if (existingLock2 == null)
- {
- return writeLock;
- }
- else if (existingLock == existingLock2)
- {
- // We were able to synchronize on the lock's monitor while
- // the lock was still in the table. Try to acquire it now
- // (which will succeed if the lock isn't held by anything)
- // and either return it or return null.
- writeLock.unlock();
- writeLock = existingLock.writeLock();
- try
- {
- if (writeLock.tryLock(0, TimeUnit.SECONDS))
- {
- return writeLock;
- }
- else
- {
- return null;
- }
- }
- catch(InterruptedException ie)
- {
- // This should never happen. Just return null
- return null;
- }
- }
- else
- {
- // If this happens, then it means that while we were waiting
- // the existing lock was unlocked and removed from the table
- // and a new one was created and added to the table. This
- // is more trouble than it's worth, so return null.
- writeLock.unlock();
- return null;
- }
+ // Found: remove the lock because it will be moved to the front of the list.
+ iterator.remove();
+ return lock;
}
}
- }
-
-
-
- /**
- * Attempts to acquire the write lock for the specified entry. Only
- * a single thread may hold the write lock for an entry at any given
- * time, and during that time no read locks may be held for it. A
- * default timeout will be used for the lock.
- *
- * @param entryDN The DN of the entry for which to obtain the
- * write lock.
- *
- * @return The write lock that was acquired, or <CODE>null</CODE>
- * if it was not possible to obtain a write lock for some
- * reason.
- */
- public static Lock lockWrite(DN entryDN)
- {
- return lockWrite(entryDN, DEFAULT_TIMEOUT);
- }
-
-
-
- /**
- * Attempts to acquire the write lock for the specified entry. Only
- * a single thread may hold the write lock for an entry at any given
- * time, and during that time no read locks may be held for it.
- *
- * @param entryDN The DN of the entry for which to obtain the
- * write lock.
- * @param timeout The maximum length of time in milliseconds to
- * wait for the lock before timing out.
- *
- * @return The write lock that was acquired, or <CODE>null</CODE>
- * if it was not possible to obtain a read lock for some
- * reason.
- */
- private static Lock lockWrite(DN entryDN, long timeout)
- {
- // First, try to get the lock without blocking.
- Lock writeLock = tryLockWrite(entryDN);
- if (writeLock != null)
- {
- return writeLock;
- }
-
- ReentrantReadWriteLock entryLock =
- new ReentrantReadWriteLock(fair);
- writeLock = entryLock.writeLock();
- writeLock.lock();
-
- ReentrantReadWriteLock existingLock =
- lockTable.putIfAbsent(entryDN, entryLock);
- if (existingLock == null)
- {
- return writeLock;
- }
-
- long surrenderTime = System.currentTimeMillis() + timeout;
- writeLock.unlock();
- writeLock = existingLock.writeLock();
-
- while (true)
- {
- try
- {
- // See if we can acquire the lock while it's still in the
- // table within the given timeout.
- if (writeLock.tryLock(timeout, TimeUnit.MILLISECONDS))
- {
- synchronized (existingLock)
- {
- if (lockTable.get(entryDN) == existingLock)
- {
- // We acquired the lock within the timeout and it's
- // still in the lock table, so we're good to go.
- return writeLock;
- }
- else
- {
- ReentrantReadWriteLock existingLock2 =
- lockTable.putIfAbsent(entryDN, existingLock);
- if (existingLock2 == null)
- {
- // The lock had already been removed from the table,
- // but nothing had replaced it before we put it back,
- // so we're good to go.
- return writeLock;
- }
- else
- {
- writeLock.unlock();
- existingLock = existingLock2;
- writeLock = existingLock.writeLock();
- }
- }
- }
- }
- else
- {
- // We couldn't acquire the lock before the timeout occurred,
- // so we have to fail.
- return null;
- }
- } catch (InterruptedException ie) {}
-
-
- // There are only two reasons we should be here:
- // - If the attempt to acquire the lock was interrupted.
- // - If we acquired the lock but it had already been removed
- // from the table and another one had replaced it before we
- // could put it back.
- // Our only recourse is to try again, but we need to reduce the
- // timeout to account for the time we've already waited.
- timeout = surrenderTime - System.currentTimeMillis();
- if (timeout <= 0)
- {
- return null;
- }
- }
- }
-
-
-
- /**
- * Releases a read or write lock held on the specified entry.
- *
- * @param entryDN The DN of the entry for which to release the
- * lock.
- * @param lock The read or write lock held for the entry.
- */
- public static void unlock(DN entryDN, Lock lock)
- {
- // Get the corresponding read-write lock from the lock table.
- ReentrantReadWriteLock existingLock = lockTable.get(entryDN);
-
- // it should never be null, if it is is then all we can do is
- // release the lock and return.
- lock.unlock();
- if (existingLock != null
- && !existingLock.hasQueuedThreads()
- && existingLock.getReadLockCount() <= 1)
- {
- synchronized (existingLock)
- {
- if (!existingLock.isWriteLocked()
- && existingLock.getReadLockCount() == 0)
- {
- // If there's nothing waiting on the lock,
- // then we can remove it from the table when we unlock it.
- lockTable.remove(entryDN, existingLock);
- }
- }
- }
- }
-
-
-
- /**
- * Removes any reference to the specified entry from the lock table.
- * This may be helpful if there is a case where a lock has been
- * orphaned somehow and must be removed before other threads may
- * acquire it.
- *
- * @param entryDN The DN of the entry for which to remove the lock
- * from the table.
- *
- * @return The read write lock that was removed from the table, or
- * {@code null} if nothing was in the table for the
- * specified entry. If a lock object is returned, it may
- * be possible to get information about who was holding it.
- */
- public static ReentrantReadWriteLock destroyLock(DN entryDN)
- {
- return lockTable.remove(entryDN);
- }
-
-
-
- /**
- * Retrieves the number of entries currently held in the lock table.
- * Note that this may be an expensive operation.
- *
- * @return The number of entries currently held in the lock table.
- */
- public static int lockTableSize()
- {
- return lockTable.size();
+ return null;
}
}
-
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
index a8b80c5..6ffae27 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2008-2010 Sun Microsystems, Inc.
- * Portions Copyright 2011-2014 ForgeRock AS
+ * Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.server.workflowelement.localbackend;
@@ -31,7 +31,6 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.Lock;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
@@ -71,7 +70,7 @@
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
-import org.opends.server.types.LockManager;
+import org.opends.server.types.LockManager.DNLock;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.Privilege;
import org.opends.server.types.RDN;
@@ -240,28 +239,14 @@
// Check for a request to cancel this operation.
checkIfCanceled(false);
- // Grab a read lock on the parent entry, if there is one. We need to do
- // this to ensure that the parent is not deleted or renamed while this add
- // is in progress, and we could also need it to check the entry against
- // a DIT structure rule.
- Lock entryLock = null;
- Lock parentLock = null;
- DN parentDN = null;
-
+ // Grab a write lock on the target entry. We'll need to do this
+ // eventually anyway, and we want to make sure that the two locks are
+ // always released when exiting this method, no matter what. Since
+ // the entry shouldn't exist yet, locking earlier than necessary
+ // shouldn't cause a problem.
+ final DNLock entryLock = DirectoryServer.getLockManager().tryWriteLockEntry(entryDN);
try
{
- parentDN = entryDN.getParentDNInSuffix();
- parentLock = lockParent(parentDN);
-
- // Check for a request to cancel this operation.
- checkIfCanceled(false);
-
- // Grab a write lock on the target entry. We'll need to do this
- // eventually anyway, and we want to make sure that the two locks are
- // always released when exiting this method, no matter what. Since
- // the entry shouldn't exist yet, locking earlier than necessary
- // shouldn't cause a problem.
- entryLock = LockManager.lockWrite(entryDN);
if (entryLock == null)
{
setResultCode(ResultCode.BUSY);
@@ -269,6 +254,26 @@
return;
}
+ DN parentDN = entryDN.getParentDNInSuffix();
+ if (parentDN == null && !DirectoryServer.isNamingContext(entryDN))
+ {
+ if (entryDN.isRootDN())
+ {
+ // This is not fine. The root DSE cannot be added.
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_ADD_CANNOT_ADD_ROOT_DSE.get());
+ }
+ else
+ {
+ // The entry doesn't have a parent but isn't a suffix. This is not
+ // allowed.
+ throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_ADD_ENTRY_NOT_SUFFIX.get(entryDN));
+ }
+ }
+
+ // Check for a request to cancel this operation.
+ checkIfCanceled(false);
+
+
// Invoke any conflict resolution processing that might be needed by the
// synchronization provider.
for (SynchronizationProvider<?> provider : DirectoryServer
@@ -520,32 +525,31 @@
}
finally
{
- for (SynchronizationProvider<?> provider : DirectoryServer
- .getSynchronizationProviders())
- {
- try
- {
- provider.doPostOperation(this);
- }
- catch (DirectoryException de)
- {
- logger.traceException(de);
-
- logger.error(ERR_ADD_SYNCH_POSTOP_FAILED, getConnectionID(),
- getOperationID(), getExceptionMessage(de));
- setResponseData(de);
- break;
- }
- }
-
if (entryLock != null)
{
- LockManager.unlock(entryDN, entryLock);
+ entryLock.unlock();
}
+ processSynchPostOperationPlugins();
+ }
+ }
- if (parentLock != null)
+
+
+ private void processSynchPostOperationPlugins()
+ {
+ for (SynchronizationProvider<?> provider : DirectoryServer.getSynchronizationProviders())
+ {
+ try
{
- LockManager.unlock(parentDN, parentLock);
+ provider.doPostOperation(this);
+ }
+ catch (DirectoryException de)
+ {
+ logger.traceException(de);
+ logger.error(ERR_ADD_SYNCH_POSTOP_FAILED, getConnectionID(),
+ getOperationID(), getExceptionMessage(de));
+ setResponseData(de);
+ break;
}
}
}
@@ -607,48 +611,6 @@
ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(entryDN));
}
- /**
- * Acquire a read lock on the parent of the entry to add.
- *
- * @return The acquired read lock.
- *
- * @throws DirectoryException If a problem occurs while attempting to
- * acquire the lock.
- */
- private Lock lockParent(DN parentDN) throws DirectoryException
- {
- if (parentDN != null)
- {
- final Lock parentLock = LockManager.lockRead(parentDN);
- if (parentLock == null)
- {
- throw newDirectoryException(parentDN, ResultCode.BUSY,
- ERR_ADD_CANNOT_LOCK_PARENT.get(entryDN, parentDN));
- }
- return parentLock;
- }
-
- // Either this entry is a suffix or doesn't belong in the directory.
- if (DirectoryServer.isNamingContext(entryDN))
- {
- // This is fine. This entry is one of the configured suffixes.
- return null;
- }
- else if (entryDN.isRootDN())
- {
- // This is not fine. The root DSE cannot be added.
- throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
- ERR_ADD_CANNOT_ADD_ROOT_DSE.get());
- }
- else
- {
- // The entry doesn't have a parent but isn't a suffix. This is not
- // allowed.
- throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
- ERR_ADD_ENTRY_NOT_SUFFIX.get(entryDN));
- }
- }
-
/**
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
index 96ba7b2..b62a56f 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -22,13 +22,12 @@
*
*
* Copyright 2008-2009 Sun Microsystems, Inc.
- * Portions Copyright 2011-2014 ForgeRock AS
+ * Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.server.workflowelement.localbackend;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.Lock;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -53,10 +52,10 @@
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
-import org.opends.server.types.LockManager;
import org.opends.server.types.Privilege;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SynchronizationProviderResult;
+import org.opends.server.types.LockManager.DNLock;
import org.opends.server.types.operation.PostOperationDeleteOperation;
import org.opends.server.types.operation.PostResponseDeleteOperation;
import org.opends.server.types.operation.PostSynchronizationDeleteOperation;
@@ -207,12 +206,14 @@
return;
}
- // Grab a write lock on the entry.
- final Lock entryLock = LockManager.lockWrite(entryDN);
-
+ /*
+ * Grab a write lock on the entry and its subtree in order to prevent concurrent updates to
+ * subordinate entries.
+ */
+ final DNLock subtreeLock = DirectoryServer.getLockManager().tryWriteLockSubtree(entryDN);
try
{
- if (entryLock == null)
+ if (subtreeLock == null)
{
setResultCode(ResultCode.BUSY);
appendErrorMessage(ERR_DELETE_CANNOT_LOCK_ENTRY.get(entryDN));
@@ -348,9 +349,9 @@
}
finally
{
- if (entryLock != null)
+ if (subtreeLock != null)
{
- LockManager.unlock(entryDN, entryLock);
+ subtreeLock.unlock();
}
processSynchPostOperationPlugins();
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
index ad2fd98..ca9c21d 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -30,7 +30,6 @@
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.Lock;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
@@ -62,12 +61,12 @@
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
-import org.opends.server.types.LockManager;
import org.opends.server.types.Modification;
import org.opends.server.types.Privilege;
import org.opends.server.types.RDN;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SynchronizationProviderResult;
+import org.opends.server.types.LockManager.DNLock;
import org.opends.server.types.operation.PostOperationModifyDNOperation;
import org.opends.server.types.operation.PostResponseModifyDNOperation;
import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
@@ -309,17 +308,24 @@
checkIfCanceled(false);
/*
- * FIXME: we lock the target DN and the renamed target DN, but not the parent of the target DN,
- * which seems inconsistent with the add operation implementation. Specifically, this
- * implementation does not defend against concurrent deletes of the parent of the renamed entry.
+ * Acquire subtree write locks for the current and new DN. Be careful to avoid deadlocks by
+ * taking the locks in a well defined order.
*/
-
- // Acquire write locks for the current and new DN.
- final Lock currentLock = LockManager.lockWrite(entryDN);
- Lock newLock = null;
-
+ DNLock currentLock = null;
+ DNLock newLock = null;
try
{
+ if (entryDN.compareTo(newDN) < 0)
+ {
+ currentLock = DirectoryServer.getLockManager().tryWriteLockSubtree(entryDN);
+ newLock = DirectoryServer.getLockManager().tryWriteLockSubtree(newDN);
+ }
+ else
+ {
+ newLock = DirectoryServer.getLockManager().tryWriteLockSubtree(newDN);
+ currentLock = DirectoryServer.getLockManager().tryWriteLockSubtree(entryDN);
+ }
+
if (currentLock == null)
{
setResultCode(ResultCode.BUSY);
@@ -327,23 +333,10 @@
return;
}
- try
+ if (newLock == null)
{
- newLock = LockManager.lockWrite(newDN);
- if (newLock == null)
- {
- setResultCode(ResultCode.BUSY);
- appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get(entryDN, newDN));
- return;
- }
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- setResultCodeAndMessageNoInfoDisclosure(null, newDN,
- DirectoryServer.getServerErrorResultCode(),
- ERR_MODDN_EXCEPTION_LOCKING_NEW_DN.get(entryDN, newDN, getExceptionMessage(e)));
+ setResultCode(ResultCode.BUSY);
+ appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get(entryDN, newDN));
return;
}
@@ -495,11 +488,11 @@
{
if (currentLock != null)
{
- LockManager.unlock(entryDN, currentLock);
+ currentLock.unlock();
}
if (newLock != null)
{
- LockManager.unlock(newDN, newLock);
+ newLock.unlock();
}
processSynchPostOperationPlugins();
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
index 4dc1727..ba5082e 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -31,7 +31,6 @@
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.Lock;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
@@ -79,13 +78,13 @@
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
-import org.opends.server.types.LockManager;
import org.opends.server.types.Modification;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.Privilege;
import org.opends.server.types.RDN;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SynchronizationProviderResult;
+import org.opends.server.types.LockManager.DNLock;
import org.opends.server.types.operation.PostOperationModifyOperation;
import org.opends.server.types.operation.PostResponseModifyOperation;
import org.opends.server.types.operation.PostSynchronizationModifyOperation;
@@ -394,8 +393,7 @@
checkIfCanceled(false);
// Acquire a write lock on the target entry.
- final Lock entryLock = LockManager.lockWrite(entryDN);
-
+ final DNLock entryLock = DirectoryServer.getLockManager().tryWriteLockEntry(entryDN);
try
{
if (entryLock == null)
@@ -605,7 +603,7 @@
{
if (entryLock != null)
{
- LockManager.unlock(entryDN, entryLock);
+ entryLock.unlock();
}
processSynchPostOperationPlugins();
}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/core/AddOperationTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/core/AddOperationTestCase.java
index baed2a8..0624a6e 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/core/AddOperationTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/core/AddOperationTestCase.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2006-2008 Sun Microsystems, Inc.
- * Portions Copyright 2011-2014 ForgeRock AS.
+ * Portions Copyright 2011-2015 ForgeRock AS.
*/
package org.opends.server.core;
@@ -32,7 +32,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.locks.Lock;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.ByteString;
@@ -60,11 +59,11 @@
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
-import org.opends.server.types.LockManager;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.Operation;
import org.opends.server.types.RawAttribute;
import org.opends.server.types.WritabilityMode;
+import org.opends.server.types.LockManager.DNLock;
import org.opends.server.util.StaticUtils;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.DataProvider;
@@ -1582,8 +1581,7 @@
{
TestCaseUtils.initializeTestBackend(true);
- Lock entryLock = LockManager.lockRead(DN.valueOf("ou=People,o=test"));
-
+ final DNLock entryLock = DirectoryServer.getLockManager().tryReadLockEntry(DN.valueOf("ou=People,o=test"));
try
{
Entry entry = TestCaseUtils.makeEntry(
@@ -1597,7 +1595,7 @@
}
finally
{
- LockManager.unlock(DN.valueOf("ou=People,o=test"), entryLock);
+ entryLock.unlock();
}
}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/core/CompareOperationTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/core/CompareOperationTestCase.java
index fa1f59d..2126ba3 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/core/CompareOperationTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/core/CompareOperationTestCase.java
@@ -22,15 +22,13 @@
*
*
* Copyright 2006-2008 Sun Microsystems, Inc.
- * Portions Copyright 2011-2014 ForgeRock AS.
+ * Portions Copyright 2011-2015 ForgeRock AS.
*/
package org.opends.server.core;
-import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.locks.Lock;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ResultCode;
@@ -41,7 +39,6 @@
import org.opends.server.plugins.InvocationCounterPlugin;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.ldap.*;
-import org.opends.server.tools.LDAPWriter;
import org.opends.server.types.*;
import org.opends.server.util.ServerConstants;
import org.testng.annotations.BeforeClass;
@@ -639,80 +636,4 @@
examineIncompleteOperation(compareOperation);
}
- @Test(groups = "slow")
- public void testCompareWriteLock() throws Exception
- {
- // We need the operation to be run in a separate thread because we are going
- // to write lock the entry in the test case thread and check that the
- // compare operation does not proceed.
-
- // Establish a connection to the server.
- Socket s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
- try
- {
- org.opends.server.tools.LDAPReader r =
- new org.opends.server.tools.LDAPReader(s);
- LDAPWriter w = new LDAPWriter(s);
- TestCaseUtils.configureSocket(s);
-
- BindRequestProtocolOp bindRequest =
- new BindRequestProtocolOp(
- ByteString.valueOf("cn=Directory Manager"),
- 3, ByteString.valueOf("password"));
- LDAPMessage message = new LDAPMessage(1, bindRequest);
- w.writeMessage(message);
-
- message = r.readMessage();
- BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp();
- assertEquals(bindResponse.getResultCode(), LDAPResultCode.SUCCESS);
-
- // Since we are going to be watching the post-response count, we need to
- // wait for the server to become idle before kicking off the next request
- // to ensure that any remaining post-response processing from the previous
- // operation has completed.
- TestCaseUtils.quiesceServer();
-
- Lock writeLock = LockManager.lockWrite(entry.getName());
- assertNotNull(writeLock);
-
- try
- {
- InvocationCounterPlugin.resetAllCounters();
-
- long compareRequests = ldapStatistics.getCompareRequests();
- long compareResponses = ldapStatistics.getCompareResponses();
-
- CompareRequestProtocolOp compareRequest =
- new CompareRequestProtocolOp(
- ByteString.valueOf(entry.getName().toString()),
- "uid", ByteString.valueOf("rogasawara"));
- message = new LDAPMessage(2, compareRequest);
- w.writeMessage(message);
-
- message = r.readMessage();
- CompareResponseProtocolOp compareResponse =
- message.getCompareResponseProtocolOp();
-
- assertEquals(compareResponse.getResultCode(), LDAPResultCode.BUSY);
-
-// assertEquals(InvocationCounterPlugin.getPreParseCount(), 1);
-// assertEquals(InvocationCounterPlugin.getPreOperationCount(), 0);
-// assertEquals(InvocationCounterPlugin.getPostOperationCount(), 0);
-
- // The post response might not have been called yet.
- assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
-
- assertEquals(ldapStatistics.getCompareRequests(), compareRequests+1);
- assertEquals(ldapStatistics.getCompareResponses(), compareResponses+1);
- } finally
- {
- LockManager.unlock(entry.getName(), writeLock);
- }
- } finally
- {
- s.close();
- }
-
- }
-
}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/core/DeleteOperationTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/core/DeleteOperationTestCase.java
index b9a00fd..c29c007 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/core/DeleteOperationTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/core/DeleteOperationTestCase.java
@@ -22,14 +22,13 @@
*
*
* Copyright 2006-2008 Sun Microsystems, Inc.
- * Portions Copyright 2011-2014 ForgeRock AS.
+ * Portions Copyright 2011-2015 ForgeRock AS.
*/
package org.opends.server.core;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.locks.Lock;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.ByteString;
@@ -45,6 +44,7 @@
import org.opends.server.tools.LDAPDelete;
import org.opends.server.tools.LDAPWriter;
import org.opends.server.types.*;
+import org.opends.server.types.LockManager.DNLock;
import org.opends.server.util.StaticUtils;
import org.opends.server.workflowelement.localbackend.LocalBackendDeleteOperation;
import org.testng.annotations.AfterMethod;
@@ -207,8 +207,9 @@
DeleteOperation deleteOperation = processDeleteRaw("o=test");
assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
retrieveCompletedOperationElements(deleteOperation);
+ @SuppressWarnings("unchecked")
List<LocalBackendDeleteOperation> localOps =
- (List) deleteOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
+ (List<LocalBackendDeleteOperation>) deleteOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
assertNotNull(localOps);
for (LocalBackendDeleteOperation curOp : localOps)
{
@@ -239,8 +240,9 @@
DeleteOperation deleteOperation = processDeleteRaw("ou=People,o=test");
assertFalse(deleteOperation.getResultCode() == ResultCode.SUCCESS);
+ @SuppressWarnings("unchecked")
List<LocalBackendDeleteOperation> localOps =
- (List) deleteOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
+ (List<LocalBackendDeleteOperation>) deleteOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
assertNotNull(localOps);
for (LocalBackendDeleteOperation curOp : localOps)
{
@@ -694,8 +696,7 @@
{
TestCaseUtils.initializeTestBackend(true);
- Lock entryLock = LockManager.lockRead(DN.valueOf("o=test"));
-
+ final DNLock entryLock = DirectoryServer.getLockManager().tryReadLockEntry(DN.valueOf("o=test"));
try
{
DeleteOperation deleteOperation = processDeleteRaw("o=test");
@@ -703,7 +704,7 @@
}
finally
{
- LockManager.unlock(DN.valueOf("o=test"), entryLock);
+ entryLock.unlock();
}
}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/core/ModifyOperationTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/core/ModifyOperationTestCase.java
index 05df5ae..94159b5 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/core/ModifyOperationTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/core/ModifyOperationTestCase.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2006-2011 Sun Microsystems, Inc.
- * Portions Copyright 2011-2014 ForgeRock AS
+ * Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.server.core;
@@ -30,7 +30,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.concurrent.locks.Lock;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.ByteString;
@@ -63,11 +62,11 @@
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
-import org.opends.server.types.LockManager;
import org.opends.server.types.Modification;
import org.opends.server.types.Operation;
import org.opends.server.types.RawModification;
import org.opends.server.types.WritabilityMode;
+import org.opends.server.types.LockManager.DNLock;
import org.opends.server.util.Base64;
import org.opends.server.util.ServerConstants;
import org.opends.server.util.StaticUtils;
@@ -399,8 +398,9 @@
modifyOperation.getProcessingStartTime());
assertTrue(modifyOperation.getProcessingTime() >= 0);
+ @SuppressWarnings("unchecked")
List<LocalBackendModifyOperation> localOps =
- (List) modifyOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
+ (List<LocalBackendModifyOperation>) modifyOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
assertNotNull(localOps);
for (LocalBackendModifyOperation curOp : localOps)
{
@@ -2789,8 +2789,7 @@
public void testCannotLockEntry(String baseDN)
throws Exception
{
- Lock entryLock = LockManager.lockRead(DN.valueOf(baseDN));
-
+ final DNLock entryLock = DirectoryServer.getLockManager().tryReadLockEntry(DN.valueOf(baseDN));
try
{
LDAPAttribute attr = newLDAPAttribute("description", "foo");
@@ -2799,7 +2798,7 @@
}
finally
{
- LockManager.unlock(DN.valueOf(baseDN), entryLock);
+ entryLock.unlock();
}
}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/core/TestModifyDNOperation.java b/opendj-server-legacy/src/test/java/org/opends/server/core/TestModifyDNOperation.java
index c401c45..b7804ee 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/core/TestModifyDNOperation.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/core/TestModifyDNOperation.java
@@ -31,7 +31,6 @@
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
-import java.util.concurrent.locks.Lock;
import javax.naming.Context;
import javax.naming.InvalidNameException;
@@ -52,6 +51,7 @@
import org.opends.server.tools.LDAPModify;
import org.opends.server.tools.LDAPWriter;
import org.opends.server.types.*;
+import org.opends.server.types.LockManager.DNLock;
import org.opends.server.util.ServerConstants;
import org.opends.server.util.StaticUtils;
import org.testng.annotations.BeforeClass;
@@ -1042,7 +1042,7 @@
assertTrue(DirectoryServer.getWorkQueue().waitUntilIdle(10000));
- Lock writeLock = LockManager.lockWrite(entry.getName());
+ final DNLock writeLock = DirectoryServer.getLockManager().tryWriteLockEntry(entry.getName());
assertNotNull(writeLock);
try
@@ -1076,7 +1076,7 @@
// modifyDNResponses+1);
} finally
{
- LockManager.unlock(entry.getName(), writeLock);
+ writeLock.unlock();
}
} finally
{
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/types/LockManagerTest.java b/opendj-server-legacy/src/test/java/org/opends/server/types/LockManagerTest.java
new file mode 100644
index 0000000..8b2f3ea
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/types/LockManagerTest.java
@@ -0,0 +1,320 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2015 ForgeRock AS
+ */
+package org.opends.server.types;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.LinkedList;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.types.LockManager.DNLock;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@Test(timeOut = 20000, sequential = true)
+@SuppressWarnings("javadoc")
+public class LockManagerTest extends TypesTestCase
+{
+ private enum LockType
+ {
+ READ_ENTRY
+ {
+ @Override
+ DNLock lock(final LockManager lockManager, final DN dn)
+ {
+ return lockManager.tryReadLockEntry(dn);
+ }
+ },
+ WRITE_ENTRY
+ {
+ @Override
+ DNLock lock(final LockManager lockManager, final DN dn)
+ {
+ return lockManager.tryWriteLockEntry(dn);
+ }
+ },
+ WRITE_SUBTREE
+ {
+ @Override
+ DNLock lock(final LockManager lockManager, final DN dn)
+ {
+ return lockManager.tryWriteLockSubtree(dn);
+ }
+ };
+
+ abstract DNLock lock(LockManager lockManager, DN dn);
+ }
+
+ private DN dnA;
+ private DN dnAB;
+
+ private DN dnABC;
+ private DN dnABD;
+ private final ExecutorService thread1 = Executors.newSingleThreadExecutor();
+ private final ExecutorService thread2 = Executors.newSingleThreadExecutor();
+
+ @BeforeClass
+ private void setup() throws Exception
+ {
+ TestCaseUtils.startServer();
+ dnA = DN.valueOf("dc=a");
+ dnAB = DN.valueOf("dc=b,dc=a");
+ dnABC = DN.valueOf("dc=c,dc=b,dc=a");
+ dnABD = DN.valueOf("dc=d,dc=b,dc=a");
+ }
+
+ @Test
+ public void testLockTimeout() throws Exception
+ {
+ final LockManager lockManager = new LockManager(100, TimeUnit.MILLISECONDS);
+ DNLock lock1 = lockUsingThread(thread1, lockManager, LockType.WRITE_ENTRY, dnABC).get();
+ DNLock lock2 = lockUsingThread(thread2, lockManager, LockType.WRITE_ENTRY, dnABC).get();
+ assertThat(lock1).isNotNull();
+ assertThat(lock2).isNull(); // Timed out.
+ unlockUsingThread(thread1, lock1);
+ }
+
+ @DataProvider
+ private Object[][] multiThreadedLockCombinationsWhichShouldBlock()
+ {
+ // @formatter:off
+ return new Object[][] {
+ { LockType.READ_ENTRY, dnA, LockType.WRITE_ENTRY, dnA },
+ { LockType.WRITE_ENTRY, dnA, LockType.READ_ENTRY, dnA },
+ { LockType.WRITE_ENTRY, dnA, LockType.WRITE_ENTRY, dnA },
+
+ { LockType.WRITE_SUBTREE, dnA, LockType.READ_ENTRY, dnA },
+ { LockType.READ_ENTRY, dnA, LockType.WRITE_SUBTREE, dnA },
+ { LockType.WRITE_SUBTREE, dnA, LockType.WRITE_ENTRY, dnA },
+ { LockType.WRITE_ENTRY, dnA, LockType.WRITE_SUBTREE, dnA },
+ { LockType.WRITE_SUBTREE, dnA, LockType.WRITE_SUBTREE, dnA },
+
+ { LockType.WRITE_SUBTREE, dnA, LockType.READ_ENTRY, dnAB },
+ { LockType.READ_ENTRY, dnAB, LockType.WRITE_SUBTREE, dnA },
+ { LockType.WRITE_SUBTREE, dnA, LockType.WRITE_ENTRY, dnAB },
+ { LockType.WRITE_ENTRY, dnAB, LockType.WRITE_SUBTREE, dnA },
+ { LockType.WRITE_SUBTREE, dnA, LockType.WRITE_SUBTREE, dnAB },
+ { LockType.WRITE_SUBTREE, dnAB, LockType.WRITE_SUBTREE, dnA },
+ };
+ // @formatter:on
+ }
+
+ @Test(dataProvider = "multiThreadedLockCombinationsWhichShouldBlock")
+ public void testMultiThreadedLockCombinationsWhichShouldBlock(final LockType lock1Type, final DN dn1,
+ final LockType lock2Type, final DN dn2) throws Exception
+ {
+ final LockManager lockManager = new LockManager();
+ final DNLock lock1 = lockUsingThread(thread1, lockManager, lock1Type, dn1).get();
+ final Future<DNLock> lock2Future = lockUsingThread(thread2, lockManager, lock2Type, dn2);
+
+ try
+ {
+ lock2Future.get(10, TimeUnit.MILLISECONDS);
+ }
+ catch (final TimeoutException e)
+ {
+ // Ignore: we'll check the state of the future instead.
+ }
+ assertThat(lock2Future.isDone()).isFalse();
+ unlockUsingThread(thread1, lock1);
+ final DNLock lock2 = lock2Future.get();
+ unlockUsingThread(thread2, lock2);
+
+ assertThat(getThreadLocalLockRefCountFor(thread1, lockManager, dn1)).isGreaterThan(0);
+ assertThat(getThreadLocalLockRefCountFor(thread2, lockManager, dn2)).isGreaterThan(0);
+ assertThat(lockManager.getLockTableRefCountFor(dn1)).isGreaterThan(0);
+ assertThat(lockManager.getLockTableRefCountFor(dn2)).isGreaterThan(0);
+ }
+
+ @DataProvider
+ private Object[][] multiThreadedLockCombinationsWhichShouldNotBlock()
+ {
+ // @formatter:off
+ return new Object[][] {
+ { LockType.READ_ENTRY, dnA, LockType.READ_ENTRY, dnA },
+
+ { LockType.READ_ENTRY, dnA, LockType.WRITE_ENTRY, dnAB },
+ { LockType.WRITE_ENTRY, dnAB, LockType.READ_ENTRY, dnA },
+ { LockType.WRITE_ENTRY, dnA, LockType.WRITE_ENTRY, dnAB },
+ { LockType.WRITE_ENTRY, dnAB, LockType.WRITE_ENTRY, dnA },
+
+ { LockType.READ_ENTRY, dnA, LockType.WRITE_SUBTREE, dnAB },
+ { LockType.WRITE_SUBTREE, dnAB, LockType.READ_ENTRY, dnA },
+
+ { LockType.WRITE_ENTRY, dnA, LockType.WRITE_SUBTREE, dnAB },
+ { LockType.WRITE_SUBTREE, dnAB, LockType.WRITE_ENTRY, dnA },
+
+ { LockType.WRITE_SUBTREE, dnABC, LockType.WRITE_SUBTREE, dnABD },
+ };
+ // @formatter:on
+ }
+
+ @Test(dataProvider = "multiThreadedLockCombinationsWhichShouldNotBlock")
+ public void testMultiThreadedLockCombinationsWhichShouldNotBlock(final LockType lock1Type, final DN dn1,
+ final LockType lock2Type, final DN dn2) throws Exception
+ {
+ final LockManager lockManager = new LockManager();
+ final DNLock lock1 = lockUsingThread(thread1, lockManager, lock1Type, dn1).get();
+ final DNLock lock2 = lockUsingThread(thread2, lockManager, lock2Type, dn2).get();
+
+ assertThat(lock1).isNotSameAs(lock2);
+ unlockUsingThread(thread1, lock1);
+ unlockUsingThread(thread2, lock2);
+
+ assertThat(getThreadLocalLockRefCountFor(thread1, lockManager, dn1)).isGreaterThan(0);
+ assertThat(getThreadLocalLockRefCountFor(thread2, lockManager, dn2)).isGreaterThan(0);
+ assertThat(lockManager.getLockTableRefCountFor(dn1)).isGreaterThan(0);
+ assertThat(lockManager.getLockTableRefCountFor(dn2)).isGreaterThan(0);
+ }
+
+ @DataProvider
+ private LockType[][] reentrantLockCombinationsWhichShouldNotBlock()
+ {
+ // @formatter:off
+ return new LockType[][] {
+ { LockType.READ_ENTRY, LockType.READ_ENTRY },
+
+ { LockType.WRITE_ENTRY, LockType.READ_ENTRY },
+ { LockType.WRITE_ENTRY, LockType.WRITE_ENTRY },
+
+ { LockType.WRITE_SUBTREE, LockType.READ_ENTRY },
+ { LockType.WRITE_SUBTREE, LockType.WRITE_ENTRY },
+ { LockType.WRITE_SUBTREE, LockType.WRITE_SUBTREE },
+ };
+ // @formatter:on
+ }
+
+ @Test(dataProvider = "reentrantLockCombinationsWhichShouldNotBlock")
+ public void testReentrantLockCombinationsWhichShouldNotBlock(final LockType lock1Type, final LockType lock2Type)
+ {
+ final LockManager lockManager = new LockManager();
+ final DNLock lock1 = lock1Type.lock(lockManager, dnA);
+ final DNLock lock2 = lock2Type.lock(lockManager, dnA);
+
+ assertThat(lock1).isNotSameAs(lock2);
+ assertThat(lock1.refCount()).isEqualTo(3); // +1 for thread local cache
+ assertThat(lock2.refCount()).isEqualTo(3);
+
+ lock1.unlock();
+ assertThat(lock1.refCount()).isEqualTo(2);
+ assertThat(lock2.refCount()).isEqualTo(2);
+
+ lock2.unlock();
+ assertThat(lock1.refCount()).isEqualTo(1);
+ assertThat(lock2.refCount()).isEqualTo(1);
+
+ assertThat(lockManager.getThreadLocalCacheRefCountFor(dnA)).isGreaterThan(0);
+ assertThat(lockManager.getLockTableRefCountFor(dnA)).isGreaterThan(0);
+ }
+
+ @Test
+ public void testThreadLocalCacheEviction() throws Exception
+ {
+ final LockManager lockManager = new LockManager();
+
+ // Acquire 100 different locks. The first few locks should be evicted from the cache.
+ final LinkedList<DNLock> locks = new LinkedList<DNLock>();
+ for (int i = 0; i < 100; i++)
+ {
+ locks.add(lockManager.tryWriteLockEntry(dn(i)));
+ }
+
+ // The first lock should have been evicted from the cache, but still in the lock table because it is locked.
+ assertThat(locks.getFirst().refCount()).isEqualTo(1);
+ assertThat(lockManager.getThreadLocalCacheRefCountFor(dn(0))).isLessThan(0);
+ assertThat(lockManager.getLockTableRefCountFor(dn(0))).isGreaterThan(0);
+
+ // The last lock should still be in the cache and the lock table.
+ assertThat(locks.getLast().refCount()).isEqualTo(2);
+ assertThat(lockManager.getThreadLocalCacheRefCountFor(dn(99))).isGreaterThan(0);
+ assertThat(lockManager.getLockTableRefCountFor(dn(99))).isGreaterThan(0);
+
+ for (final DNLock lock : locks)
+ {
+ lock.unlock();
+ }
+
+ // The first lock should not be in the cache or the lock table.
+ assertThat(locks.getFirst().refCount()).isEqualTo(0);
+ assertThat(lockManager.getThreadLocalCacheRefCountFor(dn(0))).isLessThan(0);
+ assertThat(lockManager.getLockTableRefCountFor(dn(0))).isLessThan(0);
+
+ // The last lock should still be in the cache and the lock table.
+ assertThat(locks.getLast().refCount()).isEqualTo(1);
+ assertThat(lockManager.getThreadLocalCacheRefCountFor(dn(99))).isGreaterThan(0);
+ assertThat(lockManager.getLockTableRefCountFor(dn(99))).isGreaterThan(0);
+ }
+
+ private DN dn(final int i) throws DirectoryException
+ {
+ return DN.valueOf(String.format("uid=user.%d,ou=people,dc=example,dc=com", i));
+ }
+
+ private Future<DNLock> lockUsingThread(final ExecutorService thread, final LockManager lockManager,
+ final LockType lockType, final DN dn) throws Exception
+ {
+ return thread.submit(new Callable<DNLock>()
+ {
+ @Override
+ public DNLock call() throws Exception
+ {
+ return lockType.lock(lockManager, dn);
+ }
+ });
+ }
+
+ private int getThreadLocalLockRefCountFor(final ExecutorService thread, final LockManager lockManager,
+ final DN dn) throws Exception
+ {
+ return thread.submit(new Callable<Integer>()
+ {
+ @Override
+ public Integer call() throws Exception
+ {
+ return lockManager.getThreadLocalCacheRefCountFor(dn);
+ }
+ }).get();
+ }
+
+ private void unlockUsingThread(final ExecutorService thread, final DNLock lock) throws Exception
+ {
+ thread.submit(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ lock.unlock();
+ }
+ }).get();
+ }
+}
--
Gitblit v1.10.0