Chapter 13. Clustered Lock

A clustered lock is a lock which is distributed and shared among all nodes in the Red Hat Data Grid cluster and currently provides a way to execute code that will be synchronized between the nodes in a given cluster.

13.1. Installation

In order to start using the clustered locks, you needs to add the dependency in your Maven pom.xml file:

pom.xml

<dependency>
  <groupId>org.infinispan</groupId>
  <artifactId>infinispan-clustered-lock</artifactId>
  <version>${version.infinispan}</version>
</dependency>

Replace ${version.infinispan} with the appropriate version of Red Hat Data Grid.

13.2. ClusteredLock Configuration

Currently there is a single type of ClusteredLock supported : non reentrant, NODE ownership lock.

13.2.1. Ownership

  • NODE When a ClusteredLock is defined, this lock can be used from all the nodes in the Red Hat Data Grid cluster. When the ownership is NODE type, this means that the owner of the lock is the Red Hat Data Grid node that acquired the lock at a given time. This means that each time we get a ClusteredLock instance with the ClusteredCacheManager, this instance will be the same instance for each Red Hat Data Grid node. This lock can be used to synchronize code between Red Hat Data Grid nodes. The advantage of this lock is that any thread in the node can release the lock at a given time.
  • INSTANCE - not yet supported

When a ClusteredLock is defined, this lock can be used from all the nodes in the Red Hat Data Grid cluster. When the ownership is INSTANCE type, this means that the owner of the lock is the actual instance we acquired when ClusteredLockManager.get("lockName") is called.

This means that each time we get a ClusteredLock instance with the ClusteredCacheManager, this instance will be a new instance. This lock can be used to synchronize code between Red Hat Data Grid nodes and inside each Red Hat Data Grid node. The advantage of this lock is that only the instance that called 'lock' can release the lock.

13.2.2. Reentrancy

When a ClusteredLock is configured reentrant, the owner of the lock can reacquire the lock as many consecutive times as it wants while holding the lock.

Currently, only non reentrant locks are supported. This means that when two consecutive lock calls are sent for the same owner, the first call will acquire the lock if it’s available, and the second call will block.

13.3. ClusteredLockManager Interface

The ClusteredLockManager interface, marked as experimental, is the entry point to define, retrieve and remove a lock. It automatically listen to the creation of EmbeddedCacheManager and proceeds with the registration of an instance of it per EmbeddedCacheManager. It starts the internal caches needed to store the lock state.

Retrieving the ClusteredLockManager is as simple as invoking the EmbeddedClusteredLockManagerFactory.from(EmbeddedCacheManager) as shown in the example below:

// create or obtain your EmbeddedCacheManager
EmbeddedCacheManager manager = ...;

// retrieve the ClusteredLockManager
ClusteredLockManager clusteredLockManager = EmbeddedClusteredLockManagerFactory.from(manager);
@Experimental
public interface ClusteredLockManager {

   boolean defineLock(String name);

   boolean defineLock(String name, ClusteredLockConfiguration configuration);

   ClusteredLock get(String name);

   ClusteredLockConfiguration getConfiguration(String name);

   boolean isDefined(String name);

   CompletableFuture<Boolean> remove(String name);

   CompletableFuture<Boolean> forceRelease(String name);
}
  • defineLock : Defines a lock with the specified name and the default ClusteredLockConfiguration. It does not overwrite existing configurations.
  • defineLock(String name, ClusteredLockConfiguration configuration) : Defines a lock with the specified name and ClusteredLockConfiguration. It does not overwrite existing configurations.
  • ClusteredLock get(String name) : Get’s a ClusteredLock by it’s name. A call of defineLock must be done at least once in the cluster. See ownership level section to understand the implications of get method call.

Currently, the only ownership level supported is NODE.

  • ClusteredLockConfiguration getConfiguration(String name) :

Returns the configuration of a ClusteredLock, if such exists.

  • boolean isDefined(String name) : Checks if a lock is already defined.
  • CompletableFuture<Boolean> remove(String name) : Removes a ClusteredLock if such exists.
  • CompletableFuture<Boolean> forceRelease(String name) : Releases - or unlocks - a ClusteredLock, if such exists, no matter who is holding it at a given time. Calling this method may cause concurrency issues and has to be used in exceptional situations.

13.4. ClusteredLock Interface

ClusteredLock interface, marked as experimental, is the interface that implements the clustered locks.

@Experimental
public interface ClusteredLock {

   CompletableFuture<Void> lock();

   CompletableFuture<Boolean> tryLock();

   CompletableFuture<Boolean> tryLock(long time, TimeUnit unit);

   CompletableFuture<Void> unlock();

   CompletableFuture<Boolean> isLocked();

   CompletableFuture<Boolean> isLockedByMe();
}
  • lock : Acquires the lock. If the lock is not available then call blocks until the lock is acquired. Currently, there is no maximum time specified for a lock request to fail, so this could cause thread starvation.
  • tryLock Acquires the lock only if it is free at the time of invocation, and returns true in that case. This method does not block (or wait) for any lock acquisition.
  • tryLock(long time, TimeUnit unit) If the lock is available this method returns immediately with true. If the lock is not available then the call waits until :

    • The lock is acquired
    • The specified waiting time elapses

If the time is less than or equal to zero, the method will not wait at all.

  • unlock

Releases the lock. Only the holder of the lock may release the lock.

  • isLocked Returns true when the lock is locked and false when the lock is released.
  • isLockedByMe Returns true when the lock is owned by the caller and false when the lock is owned by someone else or it’s released.

13.4.1. Usage Examples

  EmbeddedCache cm = ...;
  ClusteredLockManager cclm = EmbeddedClusteredLockManagerFactory.from(cm);

  lock.tryLock()
    .thenCompose(result -> {
       if (result) {
        try {
            // manipulate protected state
            } finally {
               return lock.unlock();
            }
       } else {
          // Do something else
       }
    });
 }

13.5. ClusteredLockManager Configuration

You can configure ClusteredLockManager to use different strategies for locks, either declaratively or programmatically, with the following attributes:

num-owners
Defines the total number of nodes in each cluster that store the states of clustered locks. The default value is -1, which replicates the value to all nodes.
reliability

Controls how clustered locks behave when clusters split into partitions or multiple nodes leave a cluster. You can set the following values:

  • AVAILABLE: Nodes in any partition can concurrently operate on locks.
  • CONSISTENT: Only nodes that belong to the majority partition can operate on locks. This is the default value.

The following is an example declarative configuration for ClusteredLockManager:

<?xml version="1.0" encoding="UTF-8"?>
<infinispan
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:infinispan:config:${infinispan.core.schema.version} http://www.infinispan.org/schemas/infinispan-config-9.4.xsd"
        xmlns="urn:infinispan:config:9.4">
    ...
    <cache-container default-cache="default">
        <transport/>
        <local-cache name="default">
            <locking concurrency-level="100" acquire-timeout="1000"/>
        </local-cache>

        <clustered-locks xmlns="urn:infinispan:config:clustered-locks:9.4"
                         num-owners = "3"
                         reliability="AVAILABLE">
            <clustered-lock name="lock1" />
            <clustered-lock name="lock2" />
        </clustered-locks>
    </cache-container>
    ...
</infinispan>