Interface LockManager


  • public interface LockManager

    This LockManager is a manager to obtain cluster wide locks. To obtain the LockManager you can use

                LockManager lockManager = HippoServiceRegistry.getService(LockManager.class))
             
    When a 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:

                 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);
                    }
                  }
             
    or without using 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

                 session.refresh(true|false);
             
    after obtaining the 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.

    • Method Detail

      • lock

        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.

        Parameters:
        key - the key for the Lock where key is now allowed to exceed 256 chars
        Returns:
        LockResource 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.
        Throws:
        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 chars
      • unlock

        void unlock​(String key)
        Parameters:
        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.
        Throws:
        IllegalArgumentException - if the key exceeds 256 chars
      • isLocked

        boolean 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

        Parameters:
        key - the key to check whether there is a lock for
        Returns:
        true when locked
        Throws:
        IllegalArgumentException - if the key exceeds 256 chars
        LockManagerException - if some irrecoverable error occurs, for example a database request timeout
      • getLocks

        List<Lock> getLocks()
                     throws LockManagerException
        Returns:
        all the Locks that are currently active (including locks that are marked to be aborted but not yet aborted)
        Throws:
        LockManagerException - if some irrecoverable error occurs, for example a database request timeout
      • abort

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

        Parameters:
        key - the key to check whether there is a lock for
        Throws:
        IllegalArgumentException - if the key exceeds 256 chars
        LockManagerException - if some irrecoverable error occurs, for example a database request timeout