From 68a3491d7d8a3c0a8c84b57e625de59039d68bca Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Fri, 13 Apr 2007 19:47:30 +0000
Subject: [PATCH] Re-implement the server lock manager using a simpler approach that eliminates a potential for deadlocks and appears to provide better performance.
---
opends/src/server/org/opends/server/types/LockManager.java | 899 ++++++++++++++++++++-----------------------------------
opends/src/server/org/opends/server/util/ServerConstants.java | 22 +
2 files changed, 350 insertions(+), 571 deletions(-)
diff --git a/opends/src/server/org/opends/server/types/LockManager.java b/opends/src/server/org/opends/server/types/LockManager.java
index d583735..0bb541d 100644
--- a/opends/src/server/org/opends/server/types/LockManager.java
+++ b/opends/src/server/org/opends/server/types/LockManager.java
@@ -31,15 +31,10 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
-import static
- org.opends.server.loggers.debug.DebugLogger.debugEnabled;
-import static org.opends.server.loggers.debug.DebugLogger.debugError;
-import static
- org.opends.server.loggers.debug.DebugLogger.debugWarning;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -52,29 +47,24 @@
*/
public class LockManager
{
+ /**
+ * The default initial size to use for the lock table.
+ */
+ public static final int DEFAULT_INITIAL_TABLE_SIZE = 64;
/**
- * The number of buckets into which the set of global DN locks will
- * be broken.
+ * The default concurrency level to use for the lock table.
*/
- public static final int NUM_GLOBAL_DN_LOCKS =
- (10 * Runtime.getRuntime().availableProcessors());
+ public static final int DEFAULT_CONCURRENCY_LEVEL = 32;
/**
- * The initial capacity to use for the DN lock hashtable.
+ * The default load factor to use for the lock table.
*/
- public static final int DN_TABLE_INITIAL_SIZE = 50;
-
-
-
- /**
- * The load factor to use for the DN lock hashtable.
- */
- public static final float DN_TABLE_LOAD_FACTOR = 0.75F;
+ public static final float DEFAULT_LOAD_FACTOR = 0.75F;
@@ -86,152 +76,133 @@
- // The set of global DN locks that we need to ensure thread safety
- // for all of the other operations.
- private static ReentrantLock[] globalDNLocks;
-
// The set of entry locks that the server knows about.
- private static ConcurrentHashMap<DN,ReentrantReadWriteLock>
- entryLocks;
+ private static
+ ConcurrentHashMap<DN,ReentrantReadWriteLock> lockTable;
// Initialize all of the lock variables.
static
{
- // Create the set of global DN locks.
- globalDNLocks = new ReentrantLock[NUM_GLOBAL_DN_LOCKS];
- for (int i=0; i < NUM_GLOBAL_DN_LOCKS; i++)
+ // Determine the concurrency level to use. We'll let it be
+ // specified by a system property, but if it isn't specified then
+ // we'll set the default value based on the number of CPUs in the
+ // system.
+ int concurrencyLevel = -1;
+ String propertyStr =
+ System.getProperty(PROPERTY_LOCK_MANAGER_CONCURRENCY_LEVEL);
+ if (propertyStr != null)
{
- globalDNLocks[i] = new ReentrantLock();
+ try
+ {
+ concurrencyLevel = Integer.parseInt(propertyStr);
+ } catch (Exception e) {}
+ }
+
+ if (concurrencyLevel <= 0)
+ {
+ concurrencyLevel =
+ Math.max(DEFAULT_CONCURRENCY_LEVEL,
+ (2*Runtime.getRuntime().availableProcessors()));
+ }
+
+
+ // Set the lock table size either to a user-defined value from a
+ // property or a hard-coded default.
+ int lockTableSize = DEFAULT_INITIAL_TABLE_SIZE;
+ propertyStr =
+ System.getProperty(PROPERTY_LOCK_MANAGER_TABLE_SIZE);
+ if (propertyStr != null)
+ {
+ try
+ {
+ lockTableSize = Integer.parseInt(propertyStr);
+ } catch (Exception e) {}
}
// Create an empty table for holding the entry locks.
- entryLocks = new ConcurrentHashMap<DN,ReentrantReadWriteLock>(
- DN_TABLE_INITIAL_SIZE, DN_TABLE_LOAD_FACTOR,
- NUM_GLOBAL_DN_LOCKS);
+ lockTable = new ConcurrentHashMap<DN,ReentrantReadWriteLock>(
+ lockTableSize, DEFAULT_LOAD_FACTOR, concurrencyLevel);
}
/**
* Attempts to acquire a read 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.
+ * 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</CODE> if
- * it was not possible to obtain a read lock for some
- * reason.
+ * @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 tryLockRead(DN entryDN)
{
- int hashCode = (entryDN.hashCode() & 0x7FFFFFFF);
+ ReentrantReadWriteLock entryLock = new ReentrantReadWriteLock();
+ Lock readLock = entryLock.readLock();
+ readLock.lock();
-
- // Get the hash code for the provided entry DN and determine which
- // global lock to acquire. This will ensure that no two threads
- // will be allowed to lock or unlock the same entry at any given
- // time, but should allow other entries with different hash codes
- // to be processed.
- ReentrantLock globalLock;
- try
+ ReentrantReadWriteLock existingLock =
+ lockTable.putIfAbsent(entryDN, entryLock);
+ if (existingLock == null)
{
- globalLock = globalDNLocks[hashCode % NUM_GLOBAL_DN_LOCKS];
- if (! globalLock.tryLock())
+ return readLock;
+ }
+ 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.isWriteLocked())
{
+ readLock.unlock();
return null;
}
- }
- catch (Exception e)
- {
- if (debugEnabled())
+
+ // 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)
{
- debugCaught(DebugLogLevel.ERROR, e);
-
- // This is not fine. Some unexpected error occurred.
- debugError(
- "Unexpected exception while trying to obtain the " +
- "global lock for entry %s: %s",
- entryDN, stackTraceToSingleLineString(e));
- }
- return null;
- }
-
-
-
- // At this point we have the global lock for this bucket. We must
- // use a try/catch/finally block to ensure that the global lock is
- // always released no matter what.
- try
- {
- // Now check to see if the entry is already in the lock table.
- ReentrantReadWriteLock entryLock = entryLocks.get(entryDN);
- if (entryLock == null)
- {
- // No lock exists for the entry. Create one and put it in the
- // table.
- entryLock = new ReentrantReadWriteLock();
- if (entryLock.readLock().tryLock())
+ ReentrantReadWriteLock existingLock2 =
+ lockTable.putIfAbsent(entryDN, entryLock);
+ if (existingLock2 == null)
{
- entryLocks.put(entryDN, entryLock);
- return entryLock.readLock();
+ 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();
+ if (readLock.tryLock())
+ {
+ return readLock;
+ }
+ else
+ {
+ return null;
+ }
}
else
{
- // This should never happen since we just created the lock.
- if (debugEnabled())
- {
- debugError(
- "Unable to acquire read lock on newly-created lock " +
- "for entry %s", entryDN);
- }
+ // 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;
}
}
- else
- {
- // There is already a lock for the entry. Try to get its read
- // lock.
- if (entryLock.readLock().tryLock())
- {
- // We got the read lock. We don't need to do anything else.
- return entryLock.readLock();
- }
- else
- {
- // We couldn't get the read lock. Write a debug message.
- if (debugEnabled())
- {
- debugWarning(
- "Unable to acquire a read lock for entry %s that " +
- "was already present in the lock table.", entryDN);
- }
- return null;
- }
- }
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, e);
-
- // This is not fine. Some unexpected error occurred.
- debugError(
- "Unexpected exception while trying to obtain a read " +
- "lock for entry %s: %s",
- entryDN, stackTraceToSingleLineString(e));
- }
- return null;
- }
- finally
- {
- // This will always be called even after a return.
- globalLock.unlock();
}
}
@@ -248,9 +219,8 @@
* @param entryDN The DN of the entry for which to obtain the read
* lock.
*
- * @return The read lock that was acquired, or <CODE>null</CODE> if
- * it was not possible to obtain a read lock for some
- * reason.
+ * @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)
{
@@ -262,9 +232,9 @@
/**
* 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.
+ * 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.
@@ -277,140 +247,86 @@
*/
public static Lock lockRead(DN entryDN, long timeout)
{
- int hashCode = (entryDN.hashCode() & 0x7FFFFFFF);
-
-
- // Get the hash code for the provided entry DN and determine which
- // global lock to acquire. This will ensure that no two threads
- // will be allowed to lock or unlock the same entry at any given
- // time, but should allow other entries with different hash codes
- // to be processed.
- ReentrantLock globalLock;
- try
+ // First, try to get the lock without blocking.
+ Lock readLock = tryLockRead(entryDN);
+ if (readLock != null)
{
- globalLock = globalDNLocks[hashCode % NUM_GLOBAL_DN_LOCKS];
- if (! globalLock.tryLock(timeout, TimeUnit.MILLISECONDS))
+ return readLock;
+ }
+
+ ReentrantReadWriteLock entryLock = new ReentrantReadWriteLock();
+ readLock = entryLock.readLock();
+ readLock.lock();
+
+ ReentrantReadWriteLock existingLock =
+ lockTable.putIfAbsent(entryDN, entryLock);
+ if (existingLock == null)
+ {
+ return readLock;
+ }
+
+ long surrenderTime = System.currentTimeMillis() + timeout;
+ readLock.unlock();
+ readLock = existingLock.readLock();
+
+ while (true)
+ {
+ 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))
+ {
+ 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 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();
+ }
+ }
+ }
+ }
+ 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;
}
}
- catch (InterruptedException ie)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, ie);
- }
-
- // This is fine. The thread trying to acquire the lock was
- // interrupted.
- return null;
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, e);
- }
-
- // This is not fine. Some unexpected error occurred.
- if (debugEnabled())
- {
- debugError(
- "Unexpected exception while trying to obtain the " +
- "global lock for entry %s: %s",
- entryDN, stackTraceToSingleLineString(e));
- }
- return null;
- }
-
-
-
- // At this point we have the global lock for this bucket. We must
- // use a try/catch/finally block to ensure that the global lock is
- // always released no matter what.
- try
- {
- // Now check to see if the entry is already in the lock table.
- ReentrantReadWriteLock entryLock = entryLocks.get(entryDN);
- if (entryLock == null)
- {
- // No lock exists for the entry. Create one and put it in the
- // table.
- entryLock = new ReentrantReadWriteLock();
- if (entryLock.readLock().tryLock(timeout,
- TimeUnit.MILLISECONDS))
- {
- entryLocks.put(entryDN, entryLock);
- return entryLock.readLock();
- }
- else
- {
- // This should never happen since we just created the lock.
- if (debugEnabled())
- {
- debugError(
- "Unable to acquire read lock on newly-created lock " +
- "for entry %s", entryDN);
- }
- return null;
- }
- }
- else
- {
- // There is already a lock for the entry. Try to get its read
- // lock.
- if (entryLock.readLock().tryLock(timeout,
- TimeUnit.MILLISECONDS))
- {
- // We got the read lock. We don't need to do anything else.
- return entryLock.readLock();
- }
- else
- {
- // We couldn't get the read lock. Write a debug message.
- if (debugEnabled())
- {
- debugWarning(
- "Unable to acquire a read lock for entry %s that " +
- "was already present in the lock table.", entryDN);
- }
- return null;
- }
- }
- }
- catch (InterruptedException ie)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, ie);
- }
-
- // This is fine. The thread trying to acquire the lock was
- // interrupted.
- return null;
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, e);
- }
-
- // This is not fine. Some unexpected error occurred.
- if (debugEnabled())
- {
- debugError(
- "Unexpected exception while trying to obtain a read " +
- "lock for entry %s: %s",
- entryDN, stackTraceToSingleLineString(e));
- }
- return null;
- }
- finally
- {
- // This will always be called even after a return.
- globalLock.unlock();
- }
}
@@ -429,111 +345,68 @@
*/
public static Lock tryLockWrite(DN entryDN)
{
- int hashCode = (entryDN.hashCode() & 0x7FFFFFFF);
+ ReentrantReadWriteLock entryLock = new ReentrantReadWriteLock();
+ Lock writeLock = entryLock.writeLock();
+ writeLock.lock();
-
- // Get the hash code for the provided entry DN and determine which
- // global lock to acquire. This will ensure that no two threads
- // will be allowed to lock or unlock the same entry at any given
- // time, but should allow other entries with different hash codes
- // to be processed.
- ReentrantLock globalLock;
- try
+ ReentrantReadWriteLock existingLock =
+ lockTable.putIfAbsent(entryDN, entryLock);
+ if (existingLock == null)
{
- globalLock = globalDNLocks[hashCode % NUM_GLOBAL_DN_LOCKS];
- if (! globalLock.tryLock())
+ return writeLock;
+ }
+ 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;
}
- }
- catch (Exception e)
- {
- if (debugEnabled())
+
+ // 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)
{
- debugCaught(DebugLogLevel.ERROR, e);
-
- // This is not fine. Some unexpected error occurred.
- debugError(
- "Unexpected exception while trying to obtain the " +
- "global lock for entry %s: %s",
- entryDN, stackTraceToSingleLineString(e));
- }
- return null;
- }
-
-
-
- // At this point we have the global lock for this bucket. We must
- // use a try/catch/finally block to ensure that the global lock is
- // always released no matter what.
- try
- {
- // Now check to see if the entry is already in the lock table.
- ReentrantReadWriteLock entryLock = entryLocks.get(entryDN);
- if (entryLock == null)
- {
- // No lock exists for the entry. Create one and put it in the
- // table.
- entryLock = new ReentrantReadWriteLock();
- if (entryLock.writeLock().tryLock())
+ ReentrantReadWriteLock existingLock2 =
+ lockTable.putIfAbsent(entryDN, entryLock);
+ if (existingLock2 == null)
{
- entryLocks.put(entryDN, entryLock);
- return entryLock.writeLock();
+ 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();
+ if (writeLock.tryLock())
+ {
+ return writeLock;
+ }
+ else
+ {
+ return null;
+ }
}
else
{
- // This should never happen since we just created the lock.
- if (debugEnabled())
- {
- debugError(
- "Unable to acquire write lock on newly-created " +
- "lock for entry %s", entryDN);
- }
+ // 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;
}
}
- else
- {
- // There is already a lock for the entry. Try to get its
- // write lock.
- if (entryLock.writeLock().tryLock())
- {
- // We got the write lock. We don't need to do anything
- // else.
- return entryLock.writeLock();
- }
- else
- {
- // We couldn't get the write lock. Write a debug message.
- if (debugEnabled())
- {
- debugWarning(
- "Unable to acquire the write lock for entry %s " +
- "that was already present in the lock table.",
- entryDN);
- }
- return null;
- }
- }
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, e);
-
- // This is not fine. Some unexpected error occurred.
- debugError(
- "Unexpected exception while trying to obtain the write " +
- "lock for entry %s: %s",
- entryDN, stackTraceToSingleLineString(e));
- }
- return null;
- }
- finally
- {
- // This will always be called even after a return.
- globalLock.unlock();
}
}
@@ -575,139 +448,86 @@
*/
public static Lock lockWrite(DN entryDN, long timeout)
{
- int hashCode = (entryDN.hashCode() & 0x7FFFFFFF);
-
-
- // Get the hash code for the provided entry DN and determine which
- // global lock to acquire. This will ensure that no two threads
- // will be allowed to lock or unlock the same entry at any given
- // time, but should allow other entries with different hash codes
- // to be processed.
- ReentrantLock globalLock;
- try
+ // First, try to get the lock without blocking.
+ Lock writeLock = tryLockWrite(entryDN);
+ if (writeLock != null)
{
- globalLock = globalDNLocks[hashCode % NUM_GLOBAL_DN_LOCKS];
- if (! globalLock.tryLock(timeout, TimeUnit.MILLISECONDS))
+ return writeLock;
+ }
+
+ ReentrantReadWriteLock entryLock = new ReentrantReadWriteLock();
+ 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;
}
}
- catch (InterruptedException ie)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, ie);
- }
-
- // This is fine. The thread trying to acquire the lock was
- // interrupted.
- return null;
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, e);
- }
-
- // This is not fine. Some unexpected error occurred.
- if (debugEnabled())
- {
- debugError(
- "Unexpected exception while trying to obtain the " +
- "global lock for entry %s: %s",
- entryDN, stackTraceToSingleLineString(e));
- }
- return null;
- }
-
-
-
- // At this point we have the global lock for this bucket. We must
- // use a try/catch/finally block to ensure that the global lock is
- // always released no matter what.
- try
- {
- // Now check to see if the entry is already in the lock table.
- ReentrantReadWriteLock entryLock = entryLocks.get(entryDN);
- if (entryLock == null)
- {
- // No lock exists for the entry. Create one and put it in the
- // table.
- entryLock = new ReentrantReadWriteLock();
- if (entryLock.writeLock().tryLock(timeout,
- TimeUnit.MILLISECONDS))
- {
- entryLocks.put(entryDN, entryLock);
- return entryLock.writeLock();
- }
- else
- {
- // This should never happen since we just created the lock.
- if (debugEnabled())
- {
- debugError(
- "Unable to acquire write lock on newly-created " +
- "lock for entry %s", entryDN.toString());
- }
- return null;
- }
- }
- else
- {
- // There is already a lock for the entry. Try to get its
- // write lock.
- if (entryLock.writeLock().tryLock(timeout,
- TimeUnit.MILLISECONDS))
- {
- // We got the write lock. We don't need to do anything
- // else.
- return entryLock.writeLock();
- }
- else
- {
- // We couldn't get the write lock. Write a debug message.
- if (debugEnabled())
- {
- debugWarning(
- "Unable to acquire the write lock for entry %s " +
- "that was already present in the lock table.",
- entryDN);
- }
- return null;
- }
- }
- }
- catch (InterruptedException ie)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, ie);
- }
-
- // This is fine. The thread trying to acquire the lock was
- // interrupted.
- return null;
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, e);
-
- // This is not fine. Some unexpected error occurred.
- debugError(
- "Unexpected exception while trying to obtain the write " +
- "lock for entry %s: %s",
- entryDN, stackTraceToSingleLineString(e));
- }
- return null;
- }
- finally
- {
- // This will always be called even after a return.
- globalLock.unlock();
- }
}
@@ -721,100 +541,37 @@
*/
public static void unlock(DN entryDN, Lock lock)
{
- // Unlock the entry without grabbing any additional locks.
- try
+ // Get the corresponding read-write lock from the lock table.
+ ReentrantReadWriteLock existingLock = lockTable.get(entryDN);
+ if (existingLock == null)
+ {
+ // This shouldn't happen, but if it does then all we can do is
+ // release the lock and return.
+ lock.unlock();
+ return;
+ }
+
+ // See if there's anything waiting on the lock. If so, then we
+ // can't remove it from the table when we unlock it.
+ if (existingLock.hasQueuedThreads() ||
+ (existingLock.getReadLockCount() > 1))
{
lock.unlock();
- }
- catch (Exception e)
- {
- // This should never happen. However, if it does, then just
- // capture the exception and continue because it may still be
- // necessary to remove the lock for the entry from the table.
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, e);
- }
- }
-
-
- int hashCode = (entryDN.hashCode() & 0x7FFFFFFF);
-
-
- // Now grab the global lock for the entry and check to see if we
- // can remove it from the table.
- ReentrantLock globalLock;
- try
- {
- globalLock = globalDNLocks[hashCode % NUM_GLOBAL_DN_LOCKS];
-
- // This will block until it acquires the lock or until it is
- // interrupted.
- globalLock.lockInterruptibly();
- }
- catch (InterruptedException ie)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, ie);
- }
-
- // The lock trying to acquire the lock was interrupted. In this
- // case, we'll just return. The worst that could happen here is
- // that a lock that isn't held by anything is still in the table
- // which will just consume a little memory.
return;
}
- catch (Exception e)
+ else
{
- if (debugEnabled())
+ lock.unlock();
+ synchronized (existingLock)
{
- debugCaught(DebugLogLevel.ERROR, e);
+ if ((! existingLock.isWriteLocked()) &&
+ (existingLock.getReadLockCount() == 0))
+ {
+ lockTable.remove(entryDN, existingLock);
+ }
}
-
- // This is not fine. Some unexpected error occurred. But
- // again, the worst that could happen is that we may not clean
- // up an unheld lock, which isn't really that big a deal unless
- // it happens too often.
- debugError(
- "Unexpected exception while trying to obtain the global " +
- "lock for entry %s: %s",
- entryDN, stackTraceToSingleLineString(e));
return;
}
-
-
- // At this point we have the global lock for this bucket. We must
- // use a try/catch/finally block to ensure that the global lock is
- // always released no matter what.
- try
- {
- ReentrantReadWriteLock entryLock = entryLocks.get(entryDN);
- if ((entryLock != null) &&
- (entryLock.getReadLockCount() == 0) &&
- (! entryLock.isWriteLocked()))
- {
- // This lock isn't held so we can remove it from the table.
- entryLocks.remove(entryDN);
- }
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- debugCaught(DebugLogLevel.ERROR, e);
- }
-
- // This should never happen.
- debugError(
- "Unexpected exception while trying to determine whether " +
- "the lock for entry %s can be removed: %s",
- entryDN, stackTraceToSingleLineString(e));
- }
- finally
- {
- globalLock.unlock();
- }
}
@@ -829,13 +586,13 @@
* from the table.
*
* @return The read write lock that was removed from the table, or
- * <CODE>null</CODE> if nothing was in the table for the
+ * {@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 entryLocks.remove(entryDN);
+ return lockTable.remove(entryDN);
}
@@ -848,7 +605,7 @@
*/
public static int lockTableSize()
{
- return entryLocks.size();
+ return lockTable.size();
}
}
diff --git a/opends/src/server/org/opends/server/util/ServerConstants.java b/opends/src/server/org/opends/server/util/ServerConstants.java
index 8484a2f..bda8605 100644
--- a/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -2299,6 +2299,28 @@
/**
+ * The name of the system property that can be used to specify the concurrency
+ * level for the 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.
+ */
+ public static final String PROPERTY_LOCK_MANAGER_CONCURRENCY_LEVEL =
+ "org.opends.server.LockManagerConcurrencyLevel";
+
+
+
+ /**
+ * The name of the system property that can be used to specify 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.
+ */
+ public static final String PROPERTY_LOCK_MANAGER_TABLE_SIZE =
+ "org.opends.server.LockManagerTableSize";
+
+
+
+ /**
* The name of the system property that can be used to determine whether the
* Directory Server is starting up for the purpose of running the unit tests.
*/
--
Gitblit v1.10.0