Class LockManager
- java.lang.Object
-
- org.opends.server.types.LockManager
-
@PublicAPI(stability=UNCOMMITTED, mayInstantiate=false, mayExtend=false, mayInvoke=true) public final class LockManager extends Object
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:- 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
tryReadLockEntry(Dn)
- modifying an entry: acquire an entry write-lock for the target entry using
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 - adding an entry: client code must acquire an entry write-lock for the target entry using
tryWriteLockEntry(Dn)
. The parent entry will automatically be protected from deletion by an implicit subtree read lock on the parent - deleting an entry: client code must acquire a subtree write lock for the target entry using
tryWriteLockSubtree(Dn)
- 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
tryWriteLockSubtree(Dn)
. Care should be taken to avoid deadlocks, e.g. by locking the DN which sorts first.
Implementation Notes
The lock table is conceptually a cache of locks keyed on DN, i.e. a
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.In order to lock an entry for read or write a subtree read lock is first acquired on each of the parent entries from the empty DN down to the immediate parent of the entry to be locked. Then the appropriate read or write entry lock is acquired for the target entry. Subtree write locking is performed by acquiring a subtree read lock on each of the parent entries from the empty DN down to the immediate parent of the subtree to be locked. Then a subtree write lock is acquired for the target subtree.
The lock table itself is not represented using a
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 aLinkedList
ofDNLock
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.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:
- 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:
- found - store it in the thread local cache and bump the reference count
- not found - 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.
- return the lock to the application and increment its reference count since the application now also has a reference to the lock.
- 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
-
-
Nested Class Summary
Nested Classes Modifier and Type Class Description class
LockManager.DNLock
A lock on an entry or subtree.
-
Constructor Summary
Constructors Constructor Description LockManager()
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.LockManager(long lockTimeout, TimeUnit lockTimeoutUnit)
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.
-
Method Summary
All Methods Instance Methods Concrete Methods Modifier and Type Method Description void
clear()
Clears the locks table.String
toString()
LockManager.DNLock
tryReadLockEntry(Dn entryDn)
Acquires the read lock for the specified entry DN.LockManager.DNLock
tryWriteLockEntry(Dn entryDn)
Acquires the write lock for the specified entry DN.LockManager.DNLock
tryWriteLockSubtree(Dn subtreeDn)
Acquires the write lock for the specified subtree DN.
-
-
-
Constructor Detail
-
LockManager
public LockManager()
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.
-
LockManager
public LockManager(long lockTimeout, TimeUnit lockTimeoutUnit)
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.- Parameters:
lockTimeout
- The lock timeout.lockTimeoutUnit
- The lock timeout units.
-
-
Method Detail
-
tryReadLockEntry
public LockManager.DNLock tryReadLockEntry(Dn entryDn)
Acquires the read lock for the specified entry DN. 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.- Parameters:
entryDn
- The entry DN whose read lock is required.- Returns:
- The lock, or
null
if the lock attempt timed out.
-
tryWriteLockEntry
public LockManager.DNLock tryWriteLockEntry(Dn entryDn)
Acquires the write lock for the specified entry DN. 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.- Parameters:
entryDn
- The entry DN whose write lock is required.- Returns:
- The lock, or
null
if the lock attempt timed out.
-
tryWriteLockSubtree
public LockManager.DNLock tryWriteLockSubtree(Dn subtreeDn)
Acquires the write lock for the specified subtree DN. 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.- Parameters:
subtreeDn
- The subtree DN whose write lock is required.- Returns:
- The lock, or
null
if the lock attempt timed out.
-
clear
public void clear()
Clears the locks table.
-
-