public interface LockManager
This LockManager is a manager to obtain cluster wide locks. To obtain the LockManager
you can use
When a
LockManager lockManager = HippoServiceRegistry.getService(LockManager.class))
Lock
is obtained, that lock is tied to the Thread
that obtained the
Lock
and can only be unlocked by the same Thread
. The number of invocations on lock(String)
must be balanced with unlock(String)
since calling Lock
multiple times increases the hold count: Only
when the hold count is 0, the lock is really freed.
An example usage is as follows:
or without using
public void run() {
try (LockResource ignore = lockManager.lock(key)){
// session.refresh(true|false) if JCR nodes are involved
// Do work
} catch (AlreadyLockedException e) {
log.info("'{}' is already locked", key, e);
} catch (LockException e) {
log.error("Exception while trying to obtain lock, e);
}
}
AutoCloseable
concept of LockResource
:
public void run() {
boolean locked = false;
try {
lockManager.lock(key);
locked = true;
// session.refresh(true|false) if JCR nodes are involved
// Do work
} catch (AlreadyLockedException e) {
log.info("'{}' is already locked", key, e);
} catch (LockException e) {
log.error("Exception while trying to obtain lock, e);
}finally {
if (locked) {
lockManager.unlock(key);
}
}
}
Note that when key
is already locked by another Thread
or other cluster node,
the invocation of #lock(key)
directly results in an AlreadyLockedException
:
This is thus different than ReentrantLock.lock()
behavior (which blocks until the lock is acquired).
If you need similar behavior to ReentrantLock.lock()
but then cluster wide, you can use
LockManagerUtils.waitForLock(LockManager, String, long)
and if you need the cluster wide equivalent
of ReentrantLock.tryLock(long, TimeUnit)
you can use
LockManagerUtils.waitForLock(LockManager, String, long, long)
.
Usage in combination with JCR:
When you use this LockManager
to obtain a cluster wide lock after which the code is doing JCR node manipulation,
eg updating the last modification timestamp on a JCR node, then make sure to always invoke
after obtaining the
session.refresh(true|false);
LockResource
. The reason for this is that in the cluster wide 'synchronized' part of
the code, you want to make sure that all JCR nodes the code is going to touch are in sync with the latest cluster
state and that the code is not chatting with local stale JCR nodes. Thus make sure to always invoke
session.refresh(true|false);
when dealing with JCR nodes in a cluster wide synchronized code block.
Modifier and Type | Field and Description |
---|---|
static int |
LOCK_KEY_MAX_LENGTH
Maximum number of chars for a
lock(String) key. |
Modifier and Type | Method and Description |
---|---|
void |
abort(String key)
|
List<Lock> |
getLocks() |
boolean |
isLocked(String key)
Returns
true if there is a lock for key . |
LockResource |
lock(String key)
Tries to create a
Lock for key . |
void |
unlock(String key) |
static final int LOCK_KEY_MAX_LENGTH
lock(String)
key.LockResource lock(String key) throws LockException
Tries to create a Lock
for key
. The key
is not allowed to exceed 256 chars. If there
is already a Lock
for key
then in case the current Thread
has the lock, void is
returned, otherwise a LockException
is thrown.
Invoking this method multiple times with the same key
and the same Thread
results in the hold count
being incremented. To unlock the lock, unlock(String)
or LockResource.close()
must be invoked
an equal amount of times as lock(String)
was invoked and the unlock must be invoked with the
same Thread
as the one that obtained the Lock
. Note that the LockResource.close()
may
be invoked by a different Thread
!
A lock is released when a successful unlock(String)
or LockResource.close()
is invoked as
many times as lock(String)
. Alternatively, when the LockManager
implementation detects that
the Thread that held the lock is not live any more, the LockManager
implementation can also release the lock.
In a clustered setup, a lock will be released (in the database) when it has not been refreshed for more than
60 seconds.
This is a safeguard in case of a clustered setup where a cluster node has an ungraceful shutdown (crash) : In that
case some database lock might still be present for the crashed node.
A graceful shutdown should release all locks, implying that every Thread that holds a lock calls unlock(java.lang.String)
A persisted Lock
can be marked to be aborted: In this case, the Thread
that holds the lock
gets interrupted (Thread.interrupt()
). Threads that hold a lock should invoke unlock(String)
when interrupted (in general by just stopping their work and make sure the finally in the try block is hit
which in general should contain the unlock(String)
logic.
key
- the key for the Lock
where key
is now allowed to exceed 256 charsLockResource
such that this lock(String)
method can be used in a try-with-resources statement
where the LockResource.close()
results in the lock being freed.LockException
- in case there is already a Lock
for key
(throwing a AlreadyLockedException
)
or the lock could not be created due to some other exception (resulting in a LockManagerException
)IllegalArgumentException
- if the key
exceeds 256 charsvoid unlock(String key)
key
- the key to unlock where key
is at most 256 chars. If the Thread
that invokes
unlock(key)
does not hold the Lock
, nothing happens (apart from
the LockManager
implementation most likely logging a warning or error, because it is an
implementation issue if unlock(key)
is invoked by a thread that does not hold the lock.IllegalArgumentException
- if the key
exceeds 256 charsboolean isLocked(String key) throws LockManagerException
Returns true
if there is a lock for key
. Note that this method returns true
or false
regardless whether the Thread
that invokes isLocked(String)
contains the lock or whether another
Thread
contains the lock
key
- the key
to check whether there is a lock fortrue
when lockedIllegalArgumentException
- if the key
exceeds 256 charsLockManagerException
- if some irrecoverable error occurs, for example a database request timeoutList<Lock> getLocks() throws LockManagerException
Lock
s that are currently active (including locks that are marked to be aborted but not
yet aborted)LockManagerException
- if some irrecoverable error occurs, for example a database request timeoutvoid abort(String key) throws LockManagerException
Indicates the LockManager
that the Thread
containing the Lock
for key
should
be interrupted.
This method can be invoked by another thread than the one that holds the Lock
. In clustered setups
it can be requested by other cluster nodes that do not contain a Thread
that holds the Lock
.
When the LockManager
finds a lock marked to be aborted contained in its own JVM,
it must interrupt the Thread
that holds the Lock
. As a result, the process should stop
and the Thread
to abort should invoke unlock(String)
If there is no Lock
for key
, nothing happens and void is returned.
key
- the key
to check whether there is a lock forIllegalArgumentException
- if the key
exceeds 256 charsLockManagerException
- if some irrecoverable error occurs, for example a database request timeoutCopyright © 2012–2019 Hippo B.V. (http://www.onehippo.com). All rights reserved.