com.thinkaurelius.titan.diskstorage.locking.consistentkey

## Class ConsistentKeyLocker

- java.lang.Object
- - com.thinkaurelius.titan.diskstorage.locking.AbstractLocker<ConsistentKeyLockStatus>
  - - com.thinkaurelius.titan.diskstorage.locking.consistentkey.ConsistentKeyLocker

- All Implemented Interfaces:
    Locker

      public class ConsistentKeyLocker
      extends AbstractLocker<ConsistentKeyLockStatus>
      implements Locker

  A global `Locker` that resolves inter-thread lock contention via `AbstractLocker` and resolves inter-process contention by reading and writing lock data using `KeyColumnValueStore`.

  ## Protocol and internals

  Locking is done in two stages: first between threads inside a shared process, and then between processes in a Titan cluster.

  ### Inter-thread lock contention

  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.

  ### Inter-process lock contention

  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.)

  #### Locking I/O sequence

  1. Write a single column to the store with the following data
     key
       `KeyColumn.getKey()` followed by `KeyColumn.getColumn()`.
     column
       the approximate current timestamp in nanoseconds followed by this process's `rid` (an opaque identifier which uniquely identifie this process either globally or at least within the Titan cluster)
     value
       the single byte 0; this is unused but reserved for future use

  2. If the write failed or took longer than `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`.

  3. Wait, if necessary, until the time interval `lockWait` has passed between the timestamp on our successful write and the current time.

  4. Read all columns for the key we wrote in the first step.

  5. Discard any columns with timestamps older than `lockExpire`.

  6. If our column is either the first column read or is preceeded only by columns containing our own `rid`, then we hold the lock. Otherwise, another process holds the lock and we have failed to acquire it.

  7. To release the lock, we delete from the store the column that we wrote earlier in this sequence

  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.

- - ### Nested Class Summary

    Nested Classes
    Modifier and Type | Class and Description
    --- | ---
    `static class` | `ConsistentKeyLocker.Builder`

  - ### Field Summary

    Fields
    Modifier and Type | Field and Description
    --- | ---
    `static StaticBuffer` | `LOCK_COL_END`
    `static StaticBuffer` | `LOCK_COL_START`

    - ### Fields inherited from class com.thinkaurelius.titan.diskstorage.locking.AbstractLocker

      `llm, lockExpire, lockState, rid, serializer, times, timeUnit`

  - ### Method Summary

    Methods
    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.

    - ### Methods inherited from class com.thinkaurelius.titan.diskstorage.locking.AbstractLocker

      `checkLocks, deleteLocks, writeLock`

    - ### Methods inherited from class java.lang.Object

      `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

    - ### Methods inherited from interface com.thinkaurelius.titan.diskstorage.locking.Locker

      `checkLocks, deleteLocks, writeLock`

- - ### Field Detail

    - #### LOCK_COL_START

          public static final StaticBuffer LOCK_COL_START

    - #### LOCK_COL_END

          public static final StaticBuffer LOCK_COL_END

  - ### Method Detail

    - #### writeSingleLock

          protected ConsistentKeyLockStatus writeSingleLock(KeyColumn lockID,
                                                StoreTransaction txh)
                                                     throws Throwable

      Try to write a lock record remotely up to the configured number of times. If the store produces `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.
      Specified by:
        `writeSingleLock` in class `AbstractLocker<ConsistentKeyLockStatus>`
      Parameters:
        `lockID` - lock to acquire
        `txh` - transaction
      Returns:
        the timestamp, in nanoseconds since UNIX Epoch, on the lock column that we successfully wrote to the store
      Throws:
        `TemporaryLockingException` - if the lock retry count is exceeded without successfully writing the lock in less than the wait limit
        `Throwable` - if the storage layer throws anything else

    - #### checkSingleLock

          protected void checkSingleLock(KeyColumn kc,
                             ConsistentKeyLockStatus ls,
                             StoreTransaction tx)
                                  throws BackendException,
                                         InterruptedException

      Description copied from class: `AbstractLocker`
      Try to verify that the lock identified by `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.
      Specified by:
        `checkSingleLock` in class `AbstractLocker<ConsistentKeyLockStatus>`
      Parameters:
        `kc` - identifies the lock to check
        `ls` - the result of a prior successful `writeSingleLock(...)` call on this `lockID` and `tx`
        `tx` - identifies the process claiming this lock
      Throws:
        `BackendException`
        `InterruptedException`

    - #### deleteSingleLock

          protected void deleteSingleLock(KeyColumn kc,
                              ConsistentKeyLockStatus ls,
                              StoreTransaction tx)

      Description copied from class: `AbstractLocker`
      Try to unlock/release/delete the lock identified by `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).
      Specified by:
        `deleteSingleLock` in class `AbstractLocker<ConsistentKeyLockStatus>`
      Parameters:
        `kc` - identifies the lock to release
        `ls` - the result of a prior successful `writeSingleLock(...)` followed by a successful `checkSingleLock(...)`
        `tx` - identifies the process that wrote and checked this lock

Copyright © 2012–2014. All rights reserved.