public class ConsistentKeyLocker extends AbstractLocker<ConsistentKeyLockStatus> implements Locker
Locker
that resolves inter-thread lock contention via
AbstractLocker
and resolves inter-process contention by reading and
writing lock data using KeyColumnValueStore
.
Locking is done in two stages: first between threads inside a shared process, and then between processes in a JanusGraph cluster.
Lock contention between transactions within a shared process is arbitrated by
the LocalLockMediator
class. This mediator uses standard
java.util.concurrent
classes to guarantee that at most one thread
holds a lock on any given KeyColumn
at any given time. The code that
uses a mediator to resolve inter-thread lock contention is common to multiple
Locker
implementations and lives in the abstract base class
AbstractLocker
.
However, the mediator has no way to perform inter-process communication. The mediator can't detect or prevent a thread in another process (potentially on different machine) acquiring the same lock. This is addressed in the next section.
After the mediator signals that the current transaction has obtained a lock
at the inter-thread/intra-process level, this implementation does the
following series of writes and reads to KeyColumnValueStore
to check
whether it is the only process that holds the lock. These Cassandra
operations go to a dedicated store holding nothing but locking data (a
"store" in this context means a Cassandra column family, an HBase table,
etc.)
KeyColumn.getKey()
followed by KeyColumn.getColumn()
.rid
(an opaque identifier which uniquely identify
this process either globally or at least within the JanusGraph cluster)lockWait
to complete
successfully, then retry the write with an updated timestamp and everything
else the same until we either exceed the configured retry count (in which
case we abort the lock attempt) or successfully complete the write in less
than lockWait
.lockWait
has passed
between the timestamp on our successful write and the current time.lockExpire
.rid
, then we hold the lock. Otherwise,
another process holds the lock and we have failed to acquire it.
As mentioned earlier, this class relies on AbstractLocker
to obtain
and release an intra-process lock before and after the sequence of steps
listed above. The mediator step is necessary for thread-safety, because
rid
is only unique at the process level. Without a mediator, distinct
threads could write lock columns with the same rid
and be unable to
tell their lock claims apart.
Modifier and Type | Class and Description |
---|---|
static class |
ConsistentKeyLocker.Builder |
Modifier and Type | Field and Description |
---|---|
static StaticBuffer |
LOCK_COL_END |
static StaticBuffer |
LOCK_COL_START |
llm, lockExpire, lockState, rid, serializer, times
Modifier and Type | Method and Description |
---|---|
protected void |
checkSingleLock(KeyColumn kc,
ConsistentKeyLockStatus ls,
StoreTransaction tx)
Try to verify that the lock identified by
lockID is already held
by tx . |
protected void |
deleteSingleLock(KeyColumn kc,
ConsistentKeyLockStatus ls,
StoreTransaction tx)
Try to unlock/release/delete the lock identified by
lockID and
both held by and verified for tx . |
protected ConsistentKeyLockStatus |
writeSingleLock(KeyColumn lockID,
StoreTransaction txh)
Try to write a lock record remotely up to the configured number of
times.
|
checkLocks, deleteLocks, writeLock
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
checkLocks, deleteLocks, writeLock
public static final StaticBuffer LOCK_COL_START
public static final StaticBuffer LOCK_COL_END
protected ConsistentKeyLockStatus writeSingleLock(KeyColumn lockID, StoreTransaction txh) throws Throwable
TemporaryLockingException
, then we'll call mutate again to add a
new column with an updated timestamp and to delete the column that tried
to write when the store threw an exception. We continue like that up to
the retry limit. If the store throws anything else, such as an unchecked
exception or a PermanentBackendException
, then we'll try to
delete whatever we added and return without further retries.writeSingleLock
in class AbstractLocker<ConsistentKeyLockStatus>
lockID
- lock to acquiretxh
- transactionTemporaryLockingException
- if the lock retry count is exceeded without successfully
writing the lock in less than the wait limitThrowable
- if the storage layer throws anything elseprotected void checkSingleLock(KeyColumn kc, ConsistentKeyLockStatus ls, StoreTransaction tx) throws BackendException, InterruptedException
AbstractLocker
lockID
is already held
by tx
. The lockStatus
argument refers to the object
returned by a previous call to
AbstractLocker.writeSingleLock(KeyColumn, StoreTransaction)
. This should be a
read-only operation: return if the lock is already held, but this method
finds that it is not held, then throw an exception instead of trying to
acquire it.
This method is only useful with nonblocking locking implementations try
to lock and then check the outcome of the attempt in two separate stages.
For implementations that build writeSingleLock(...)
on a
synchronous locking primitive, such as a blocking lock()
method
or a blocking semaphore p()
, this method is redundant with
writeSingleLock(...)
and may unconditionally return true.
checkSingleLock
in class AbstractLocker<ConsistentKeyLockStatus>
kc
- identifies the lock to checkls
- the result of a prior successful writeSingleLock(...)
call on this lockID
and tx
tx
- identifies the process claiming this lockBackendException
InterruptedException
protected void deleteSingleLock(KeyColumn kc, ConsistentKeyLockStatus ls, StoreTransaction tx)
AbstractLocker
lockID
and
both held by and verified for tx
. This method is only called with
arguments for which AbstractLocker.writeSingleLock(KeyColumn, StoreTransaction)
and AbstractLocker.checkSingleLock(KeyColumn, LockStatus, StoreTransaction)
both returned successfully (i.e. without exceptions).deleteSingleLock
in class AbstractLocker<ConsistentKeyLockStatus>
kc
- identifies the lock to releasels
- the result of a prior successful writeSingleLock(...)
followed by a successful checkSingleLock(...)
tx
- identifies the process that wrote and checked this lockCopyright © 2012–2023. All rights reserved.