com.hazelcast.cp.lock

## Interface FencedLock

- All Superinterfaces:
    DistributedObject, Lock

      public interface FencedLock
      extends Lock, DistributedObject

  A linearizable & distributed & reentrant implementation of `Lock`.

  `FencedLock` is accessed via `CPSubsystem.getLock(String)`.

  `FencedLock` is CP with respect to the CAP principle. It works on top of the Raft consensus algorithm. It offers linearizability during crash-stop failures and network partitions. If a network partition occurs, it remains available on at most one side of the partition.

  `FencedLock` works on top of CP sessions. Please see `CPSession` for more information about CP sessions.

  By default, `FencedLock` is reentrant. Once a caller acquires the lock, it can acquire the lock reentrantly as many times as it wants in a linearizable manner. You can configure the reentrancy behaviour via `FencedLockConfig`. For instance, reentrancy can be disabled and `FencedLock` can work as a non-reentrant mutex. One can also set a custom reentrancy limit. When the reentrancy limit is reached, `FencedLock` does not block a lock call. Instead, it fails with `LockAcquireLimitReachedException` or a specified return value. Please check the locking methods to see details about the behaviour.

  Distributed locks are unfortunately NOT EQUIVALENT to single-node mutexes because of the complexities in distributed systems, such as uncertain communication patterns, and independent and partial failures. In an asynchronous network, no lock service can guarantee mutual exclusion, because there is no way to distinguish between a slow and a crashed process. Consider the following scenario, where a Hazelcast client acquires a `FencedLock`, then hits a long GC pause. Since it will not be able to commit session heartbeats while paused, its CP session will be eventually closed. After this moment, another Hazelcast client can acquire this lock. If the first client wakes up again, it may not immediately notice that it has lost ownership of the lock. In this case, multiple clients think they hold the lock. If they attempt to perform an operation on a shared resource, they can break the system. To prevent such situations, you can choose to use an infinite session timeout, but this time probably you are going to deal with liveliness issues. For the scenario above, even if the first client actually crashes, requests sent by 2 clients can be re-ordered in the network and hit the external resource in reverse order.

  There is a simple solution for this problem. Lock holders are ordered by a monotonic fencing token, which increments each time the lock is assigned to a new owner. This fencing token can be passed to external services or resources to ensure sequential execution of side effects performed by lock holders.

  The following figure illustrates the idea. Client-1 acquires the lock first and receives 1 as its fencing token. Then, it passes this token to the external service, which is our shared resource in this scenario. Just after that, Client-1 hits a long GC pause and eventually loses ownership of the lock because it misses to commit CP session heartbeats. Then, Client-2 chimes in and acquires the lock. Similar to Client-1, Client-2 passes its fencing token to the external service. After that, once Client-1 comes back alive, its write request will be rejected by the external service, and only Client-2 will be able to safely talk it.

                                                             CLIENT-1's session is expired.
                                                                          |
       |------------------|               LOCK is acquired by CLIENT-1.   |     LOCK is acquired by CLIENT-2.
       |       LOCK       | . . . . . . . - - - - - - - - - - - - - - - - | . . + + + + + + + + + + + + + + + + + + + + + + + + + + +
       |------------------|             /\ \ fence = 1                    |   /| \ fence = 2
                                       /    \                                /    \
       |------------------|           /      \       |                      /      \         | CLIENT-1 wakes up.
       |     CLIENT-1     | . . . . ./. . . . \/. . .|_ _ _ _ _ _ _ _ _ _  /_ _ _ _ \ _ _ _ _|. . . . . . . . . . . . . . . . . . . .
       |------------------|    lock()            \    CLIENT-1 is paused. /          \    write(A) \
                                     set_fence(1) \                      /            \             \
       |------------------|                        \                    /              \             \
       |     CLIENT-2     | . . . . . . . . . . . . \ . . . . . . . . ./. . . . . . . . \/. . . . . . \ . . . . . . . . . . . . . . .
       |------------------|                          \           lock()                    \           \      write(B) \
                                                      \                        set_fence(2) \           \               \
       |------------------|                            \   |                                 \   |       \               \
       | EXTERNAL SERVICE | . . . . . . . . . . . . . . \/ |- - - - - - - - - - - - - - - - - \/ |+ + + + \/  + + + + + + \/  + + + +
       |------------------|                                |                                     | write(A) fails.    write(B) ok.
                                                           | SERVICE belongs to CLIENT-1.        | SERVICE belongs to CLIENT-2.

  You can read more about the fencing token idea in Martin Kleppmann's "How to do distributed locking" blog post and Google's Chubby paper. `FencedLock` integrates this idea with the `Lock` abstraction.

  All of the API methods in the new `FencedLock` abstraction offer exactly-once execution semantics. For instance, even if a `lock()` call is internally retried because of a crashed CP member, the lock is acquired only once. The same rule also applies to the other methods in the API.

  See Also:
    `FencedLockConfig`, `CPSessionManagementService`, `CPSession`, `LockOwnershipLostException`, `LockAcquireLimitReachedException`

- - ### Field Summary

    Fields
    Modifier and Type | Field and Description
    --- | ---
    `static long` | `INVALID_FENCE` Representation of a failed lock attempt where the caller thread has not acquired the lock

  - ### Method Summary

    All Methods Instance Methods Abstract Methods
    Modifier and Type | Method and Description
    --- | ---
    `long` | `getFence()` Returns the fencing token if the lock is held by the current thread.
    `CPGroupId` | `getGroupId()` Returns id of the CP group that runs this `FencedLock` instance
    `int` | `getLockCount()` Returns the reentrant lock count if the lock is held by any thread in the cluster.
    `boolean` | `isLocked()` Returns whether this lock is locked or not.
    `boolean` | `isLockedByCurrentThread()` Returns whether the lock is held by the current thread or not.
    `void` | `lock()` Acquires the lock.
    `long` | `lockAndGetFence()` Acquires the lock and returns the fencing token assigned to the current thread for this lock acquire.
    `void` | `lockInterruptibly()` Acquires the lock unless the current thread is interrupted.
    `Condition` | `newCondition()` NOT IMPLEMENTED.
    `boolean` | `tryLock()` Acquires the lock if it is available or already held by the current thread at the time of invocation & the acquire limit is not exceeded, and immediately returns with the value `true`.
    `boolean` | `tryLock(long time, TimeUnit unit)` Acquires the lock if it is free within the given waiting time, or already held by the current thread.
    `long` | `tryLockAndGetFence()` Acquires the lock only if it is free or already held by the current thread at the time of invocation & the acquire limit is not exceeded, and returns the fencing token assigned to the current thread for this lock acquire.
    `long` | `tryLockAndGetFence(long time, TimeUnit unit)` Acquires the lock if it is free within the given waiting time, or already held by the current thread at the time of invocation & the acquire limit is not exceeded, and returns the fencing token assigned to the current thread for this lock acquire.
    `void` | `unlock()` Releases the lock if the lock is currently held by the current thread.

    - ### Methods inherited from interface com.hazelcast.core.DistributedObject

      `destroy, getName, getPartitionKey, getServiceName`

- - ### Field Detail

    - #### INVALID_FENCE

          static final long INVALID_FENCE

      Representation of a failed lock attempt where the caller thread has not acquired the lock
      See Also:
        Constant Field Values

  - ### Method Detail

    - #### lock

          void lock()

      Acquires the lock.

      When the caller already holds the lock and the current lock() call is reentrant, the call can fail with `LockAcquireLimitReachedException` if the lock acquire limit is already reached. Please see `FencedLockConfig` for more information.

      If the lock is not available then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock has been acquired.

      Consider the following scenario:

               FencedLock lock = ...;
               lock.lock();
               // JVM of the caller thread hits a long pause
               // and its CP session is closed on the CP group.
               lock.lock();

      In this scenario, a thread acquires the lock, then its JVM instance encounters a long pause, which is longer than `CPSubsystemConfig.getSessionTimeToLiveSeconds()`. In this case, its CP session will be closed on the corresponding CP group because it could not commit session heartbeats in the meantime. After the JVM instance wakes up again, the same thread attempts to acquire the lock reentrantly. In this case, the second lock() call fails by throwing `LockOwnershipLostException` which extends `IllegalMonitorStateException`. If the caller wants to deal with its session loss by taking some custom actions, it can handle the thrown `LockOwnershipLostException` instance. Otherwise, it can treat it as a regular `IllegalMonitorStateException`.
      Specified by:
        `lock` in interface `Lock`
      Throws:
        `LockOwnershipLostException` - if the underlying CP session is closed while locking reentrantly
        `LockAcquireLimitReachedException` - if the lock call is reentrant and the configured lock acquire limit is already reached.

    - #### lockInterruptibly

          void lockInterruptibly()
                          throws InterruptedException

      Acquires the lock unless the current thread is interrupted.

      When the caller already holds the lock and the current lock() call is reentrant, the call can fail with `LockAcquireLimitReachedException` if the lock acquire limit is already reached. Please see `FencedLockConfig` for more information.

      If the lock is not available then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock has been acquired. Interruption may not be possible after the lock request arrives to the CP group, if the proxy does not attempt to retry its lock request because of a failure in the system.

      Please note that even if `InterruptedException` is thrown, the lock may be acquired on the CP group.

      When `InterruptedException` is thrown, the current thread's interrupted status is cleared.

      Consider the following scenario:

               FencedLock lock = ...;
               lock.lockInterruptibly();
               // JVM of the caller thread hits a long pause
               // and its CP session is closed on the CP group.
               lock.lockInterruptibly();

      In this scenario, a thread acquires the lock, then its JVM instance encounters a long pause, which is longer than `CPSubsystemConfig.getSessionTimeToLiveSeconds()`. In this case, its CP session will be closed on the corresponding CP group because it could not commit session heartbeats in the meantime. After the JVM instance wakes up again, the same thread attempts to acquire the lock reentrantly. In this case, the second lock() call fails by throwing `LockOwnershipLostException` which extends `IllegalMonitorStateException`. If the caller wants to deal with its session loss by taking some custom actions, it can handle the thrown `LockOwnershipLostException` instance. Otherwise, it can treat it as a regular `IllegalMonitorStateException`.
      Specified by:
        `lockInterruptibly` in interface `Lock`
      Throws:
        `InterruptedException` - if the current thread is interrupted while acquiring the lock.
        `LockOwnershipLostException` - if the underlying CP session is closed while locking reentrantly
        `LockAcquireLimitReachedException` - if the lock call is reentrant and the configured lock acquire limit is already reached.

    - #### lockAndGetFence

          long lockAndGetFence()

      Acquires the lock and returns the fencing token assigned to the current thread for this lock acquire. If the lock is acquired reentrantly, the same fencing token is returned, or the lock() call can fail with `LockAcquireLimitReachedException` if the lock acquire limit is already reached. Please see `FencedLockConfig` for more information.

      If the lock is not available then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock has been acquired.

      This is a convenience method for the following pattern:

               FencedLock lock = ...;
               lock.lock();
               return lock.getFence();

      Consider the following scenario where the lock is free initially:

               FencedLock lock = ...; // the lock is free
               lock.lockAndGetFence();
               // JVM of the caller thread hits a long pause
               // and its CP session is closed on the CP group.
               lock.lockAndGetFence();

      In this scenario, a thread acquires the lock, then its JVM instance encounters a long pause, which is longer than `CPSubsystemConfig.getSessionTimeToLiveSeconds()`. In this case, its CP session will be closed on the corresponding CP group because it could not commit session heartbeats in the meantime. After the JVM instance wakes up again, the same thread attempts to acquire the lock reentrantly. In this case, the second lock() call fails by throwing `LockOwnershipLostException` which extends `IllegalMonitorStateException`. If the caller wants to deal with its session loss by taking some custom actions, it can handle the thrown `LockOwnershipLostException` instance. Otherwise, it can treat it as a regular `IllegalMonitorStateException`.

      Fencing tokens are monotonic numbers that are incremented each time the lock switches from the free state to the acquired state. They are simply used for ordering lock holders. A lock holder can pass its fencing to the shared resource to fence off previous lock holders. When this resource receives an operation, it can validate the fencing token in the operation.

      Consider the following scenario where the lock is free initially:

               FencedLock lock = ...; // the lock is free
               long fence1 = lock.lockAndGetFence(); // (1)
               long fence2 = lock.lockAndGetFence(); // (2)
               assert fence1 == fence2;
               lock.unlock();
               lock.unlock();
               long fence3 = lock.lockAndGetFence(); // (3)
               assert fence3 > fence1;

      In this scenario, the lock is acquired by a thread in the cluster. Then, the same thread reentrantly acquires the lock again. The fencing token returned from the second acquire is equal to the one returned from the first acquire, because of reentrancy. After the second acquire, the lock is released 2 times, hence becomes free. There is a third lock acquire here, which returns a new fencing token. Because this last lock acquire is not reentrant, its fencing token is guaranteed to be larger than the previous tokens, independent of the thread that has acquired the lock.
      Throws:
        `LockOwnershipLostException` - if the underlying CP session is closed while locking reentrantly
        `LockAcquireLimitReachedException` - if the lock call is reentrant and the configured lock acquire limit is already reached.

    - #### tryLock

          boolean tryLock()

      Acquires the lock if it is available or already held by the current thread at the time of invocation & the acquire limit is not exceeded, and immediately returns with the value `true`. If the lock is not available, then this method immediately returns with the value `false`. When the call is reentrant, it can return `false` if the lock acquire limit is exceeded. Please see `FencedLockConfig` for more information.

      A typical usage idiom for this method would be:

               FencedLock lock = ...;
               if (lock.tryLock()) {
                   try {
                       // manipulate protected state
                   } finally {
                       lock.unlock();
                   }
               } else {
                   // perform alternative actions
               }

      This usage ensures that the lock is unlocked if it was acquired, and doesn't try to unlock if the lock was not acquired.
      Specified by:
        `tryLock` in interface `Lock`
      Returns:
        `true` if the lock was acquired and `false` otherwise
      Throws:
        `LockOwnershipLostException` - if the underlying CP session is closed while locking reentrantly

    - #### tryLockAndGetFence

          long tryLockAndGetFence()

      Acquires the lock only if it is free or already held by the current thread at the time of invocation & the acquire limit is not exceeded, and returns the fencing token assigned to the current thread for this lock acquire. If the lock is acquired reentrantly, the same fencing token is returned. If the lock is already held by another caller or the lock acquire limit is exceeded, then this method immediately returns `INVALID_FENCE` that represents a failed lock attempt. Please see `FencedLockConfig` for more information.

      This is a convenience method for the following pattern:

               FencedLock lock = ...;
               if (lock.tryLock()) {
                   return lock.getFence();
               } else {
                   return FencedLock.INVALID_FENCE;
               }

      Consider the following scenario where the lock is free initially:

               FencedLock lock = ...; // the lock is free
               lock.tryLockAndGetFence();
               // JVM of the caller thread hits a long pause
               // and its CP session is closed on the CP group.
               lock.tryLockAndGetFence();

      In this scenario, a thread acquires the lock, then its JVM instance encounters a long pause, which is longer than `CPSubsystemConfig.getSessionTimeToLiveSeconds()`. In this case, its CP session will be closed on the corresponding CP group because it could not commit session heartbeats in the meantime. After the JVM instance wakes up again, the same thread attempts to acquire the lock reentrantly. In this case, the second lock() call fails by throwing `LockOwnershipLostException` which extends `IllegalMonitorStateException`. If the caller wants to deal with its session loss by taking some custom actions, it can handle the thrown `LockOwnershipLostException` instance. Otherwise, it can treat it as a regular `IllegalMonitorStateException`.

      Fencing tokens are monotonic numbers that are incremented each time the lock switches from the free state to the acquired state. They are simply used for ordering lock holders. A lock holder can pass its fencing to the shared resource to fence off previous lock holders. When this resource receives an operation, it can validate the fencing token in the operation.

      Consider the following scenario where the lock is free initially:

               FencedLock lock = ...; // the lock is free
               long fence1 = lock.tryLockAndGetFence(); // (1)
               long fence2 = lock.tryLockAndGetFence(); // (2)
               assert fence1 == fence2;
               lock.unlock();
               lock.unlock();
               long fence3 = lock.tryLockAndGetFence(); // (3)
               assert fence3 > fence1;

      In this scenario, the lock is acquired by a thread in the cluster. Then, the same thread reentrantly acquires the lock again. The fencing token returned from the second acquire is equal to the one returned from the first acquire, because of reentrancy. After the second acquire, the lock is released 2 times, hence becomes free. There is a third lock acquire here, which returns a new fencing token. Because this last lock acquire is not reentrant, its fencing token is guaranteed to be larger than the previous tokens, independent of the thread that has acquired the lock.
      Returns:
        the fencing token if the lock was acquired and `INVALID_FENCE` otherwise
      Throws:
        `LockOwnershipLostException` - if the underlying CP session is closed while locking reentrantly

    - #### tryLock

          boolean tryLock(long time,
                          TimeUnit unit)

      Acquires the lock if it is free within the given waiting time, or already held by the current thread.

      If the lock is available, this method returns immediately with the value `true`. When the call is reentrant, it immediately returns `true` if the lock acquire limit is not exceeded. Otherwise, it returns `false` on the reentrant lock attempt if the acquire limit is exceeded. Please see `FencedLockConfig` for more information.

      If the lock is not available then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock is acquired by the current thread or the specified waiting time elapses.

      If the lock is acquired, then the value `true` is returned.

      If the specified waiting time elapses, then the value `false` is returned. If the time is less than or equal to zero, the method does not wait at all.

      Specified by:
        `tryLock` in interface `Lock`
      Parameters:
        `time` - the maximum time to wait for the lock
        `unit` - the time unit of the `time` argument
      Returns:
        `true` if the lock was acquired and `false` if the waiting time elapsed before the lock was acquired
      Throws:
        `LockOwnershipLostException` - if the underlying CP session is closed while locking reentrantly

    - #### tryLockAndGetFence

          long tryLockAndGetFence(long time,
                                  TimeUnit unit)

      Acquires the lock if it is free within the given waiting time, or already held by the current thread at the time of invocation & the acquire limit is not exceeded, and returns the fencing token assigned to the current thread for this lock acquire. If the lock is acquired reentrantly, the same fencing token is returned. If the lock acquire limit is exceeded, then this method immediately returns `INVALID_FENCE` that represents a failed lock attempt. Please see `FencedLockConfig` for more information.

      If the lock is not available then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock is acquired by the current thread or the specified waiting time elapses.

      If the specified waiting time elapses, then `INVALID_FENCE` is returned. If the time is less than or equal to zero, the method does not wait at all.

      This is a convenience method for the following pattern:

               FencedLock lock = ...;
               if (lock.tryLock(time, unit)) {
                   return lock.getFence();
               } else {
                   return FencedLock.INVALID_FENCE;
               }

      Consider the following scenario where the lock is free initially:

                FencedLock lock = ...; // the lock is free
                lock.tryLockAndGetFence(time, unit);
                // JVM of the caller thread hits a long pause and its CP session
                is closed on the CP group.
                lock.tryLockAndGetFence(time, unit);

      In this scenario, a thread acquires the lock, then its JVM instance encounters a long pause, which is longer than `CPSubsystemConfig.getSessionTimeToLiveSeconds()`. In this case, its CP session will be closed on the corresponding CP group because it could not commit session heartbeats in the meantime. After the JVM instance wakes up again, the same thread attempts to acquire the lock reentrantly. In this case, the second lock() call fails by throwing `LockOwnershipLostException` which extends `IllegalMonitorStateException`. If the caller wants to deal with its session loss by taking some custom actions, it can handle the thrown `LockOwnershipLostException` instance. Otherwise, it can treat it as a regular `IllegalMonitorStateException`.

      Fencing tokens are monotonic numbers that are incremented each time the lock switches from the free state to the acquired state. They are simply used for ordering lock holders. A lock holder can pass its fencing to the shared resource to fence off previous lock holders. When this resource receives an operation, it can validate the fencing token in the operation.

      Consider the following scenario where the lock is free initially:

               FencedLock lock = ...; // the lock is free
               long fence1 = lock.tryLockAndGetFence(time, unit); // (1)
               long fence2 = lock.tryLockAndGetFence(time, unit); // (2)
               assert fence1 == fence2;
               lock.unlock();
               lock.unlock();
               long fence3 = lock.tryLockAndGetFence(time, unit); // (3)
               assert fence3 > fence1;

      In this scenario, the lock is acquired by a thread in the cluster. Then, the same thread reentrantly acquires the lock again. The fencing token returned from the second acquire is equal to the one returned from the first acquire, because of reentrancy. After the second acquire, the lock is released 2 times, hence becomes free. There is a third lock acquire here, which returns a new fencing token. Because this last lock acquire is not reentrant, its fencing token is guaranteed to be larger than the previous tokens, independent of the thread that has acquired the lock.
      Parameters:
        `time` - the maximum time to wait for the lock
        `unit` - the time unit of the `time` argument
      Returns:
        the fencing token if the lock was acquired and `INVALID_FENCE` otherwise
      Throws:
        `LockOwnershipLostException` - if the underlying CP session is closed while locking reentrantly

    - #### unlock

          void unlock()

      Releases the lock if the lock is currently held by the current thread.
      Specified by:
        `unlock` in interface `Lock`
      Throws:
        `IllegalMonitorStateException` - if the lock is not held by the current thread
        `LockOwnershipLostException` - if the underlying CP session is closed before the current thread releases the lock

    - #### getFence

          long getFence()

      Returns the fencing token if the lock is held by the current thread.

      Fencing tokens are monotonic numbers that are incremented each time the lock switches from the free state to the acquired state. They are simply used for ordering lock holders. A lock holder can pass its fencing to the shared resource to fence off previous lock holders. When this resource receives an operation, it can validate the fencing token in the operation.

      Returns:
        the fencing token if the lock is held by the current thread
      Throws:
        `IllegalMonitorStateException` - if the lock is not held by the current thread
        `LockOwnershipLostException` - if the underlying CP session is closed while the current thread is holding the lock

    - #### isLocked

          boolean isLocked()

      Returns whether this lock is locked or not.
      Returns:
        `true` if this lock is locked by any thread in the cluster, `false` otherwise.
      Throws:
        `LockOwnershipLostException` - if the underlying CP session is closed while the current thread is holding the lock

    - #### isLockedByCurrentThread

          boolean isLockedByCurrentThread()

      Returns whether the lock is held by the current thread or not.
      Returns:
        `true` if the lock is held by the current thread or not, `false` otherwise.
      Throws:
        `LockOwnershipLostException` - if the underlying CP session is closed while the current thread is holding the lock

    - #### getLockCount

          int getLockCount()

      Returns the reentrant lock count if the lock is held by any thread in the cluster.
      Returns:
        the reentrant lock count if the lock is held by any thread in the cluster
      Throws:
        `LockOwnershipLostException` - if the underlying CP session is closed while the current thread is holding the lock

    - #### getGroupId

          CPGroupId getGroupId()

      Returns id of the CP group that runs this `FencedLock` instance
      Returns:
        id of the CP group that runs this `FencedLock` instance

    - #### newCondition

          Condition newCondition()

      NOT IMPLEMENTED. Fails by throwing `UnsupportedOperationException`.

      May the force be the one who dares to implement a linearizable distributed `Condition` :)

      Specified by:
        `newCondition` in interface `Lock`
      Throws:
        `UnsupportedOperationException`

Copyright © 2020. All rights reserved.