All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kiwiproject.curator.CuratorLockHelper Maven / Gradle / Ivy

There is a newer version: 2.1.6
Show newest version
package org.kiwiproject.curator;

import static java.util.Objects.isNull;
import static org.kiwiproject.base.KiwiStrings.f;

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.kiwiproject.curator.exception.LockAcquisitionException;
import org.kiwiproject.curator.exception.LockAcquisitionFailureException;
import org.kiwiproject.curator.exception.LockAcquisitionTimeoutException;

import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Supplier;

/**
 * Helper class for creating and managing Curator locks, and converting timeouts and exceptions thrown by Curator
 * into {@link LockAcquisitionException}s.
 */
@Slf4j
public class CuratorLockHelper {

    /**
     * Creates a Curator lock instance for the given path.
     * 

* Use this when you need a re-entrant mutex that can only be released by the acquiring thread. * * @param client Curator client * @param lockPath the ZooKeeper base lock path * @return a Curator lock instance ({@link InterProcessMutex}) * @see InterProcessMutex */ public InterProcessMutex createInterProcessMutex(CuratorFramework client, String lockPath) { return new InterProcessMutex(client, lockPath); } /** * Creates a Curator lock instance for the given path. *

* Use this when you need a non re-entrant mutex which can be released by any thread, not just * the acquiring thread. * * @param client Curator client * @param lockPath the ZooKeeper base lock path * @return a Curator lock instance ({@link InterProcessSemaphoreMutex}) * @see InterProcessSemaphoreMutex */ public InterProcessSemaphoreMutex createInterProcessSemaphoreMutex(CuratorFramework client, String lockPath) { return new InterProcessSemaphoreMutex(client, lockPath); } /** * Tries to acquire the specified {@code lock}, waiting up to the specified timeout period. If Curator throws any * exception, or if the timeout expires, the appropriate exception is thrown. * * @param lock the lock to acquire * @param timeout the timeout duration * @throws LockAcquisitionFailureException if the lock throws any exception during acquisition * @throws LockAcquisitionTimeoutException if the lock acquisition times out */ public void acquire(InterProcessLock lock, Duration timeout) { var nanos = timeout.toNanos(); acquire(lock, nanos, TimeUnit.NANOSECONDS); } /** * Tries to acquire the specified {@code lock}, waiting up to the specified timeout period. If Curator throws any * exception, or if the timeout expires, the appropriate exception is thrown. * * @param lock the lock to acquire * @param time the timeout quantity * @param unit the timeout unit * @throws LockAcquisitionFailureException if the lock throws any exception during acquisition * @throws LockAcquisitionTimeoutException if the lock acquisition times out */ public void acquire(InterProcessLock lock, long time, TimeUnit unit) { boolean acquired; try { acquired = lock.acquire(time, unit); } catch (Exception e) { throw new LockAcquisitionFailureException("Failed to acquire lock", e); } if (!acquired) { var msg = f("Failed to acquire lock; timed out after {} {}", time, unit); LOG.warn(msg); throw new LockAcquisitionTimeoutException(msg); } } /** * Release the given lock, ignoring if {@code null} or if any exception occurs releasing the lock. * * @param lock to release */ public void releaseQuietly(InterProcessLock lock) { if (isNull(lock)) { return; } try { lock.release(); } catch (Exception e) { // ignore LOG.warn("Unable to release lock {}", lock, e); } } /** * Release the given lock quietly (ignoring exceptions) if the current process holds it. * * @param lock to release, if held */ public void releaseLockQuietlyIfHeld(InterProcessLock lock) { if (isNull(lock)) { return; } if (lock.isAcquiredInThisProcess()) { LOG.trace("Releasing lock [{}]", lock); releaseQuietly(lock); } else { LOG.trace("This process does not own lock [{}]. Nothing to do.", lock); } } /** * Tries to acquire the specified {@code lock}, waiting up to the specified timeout period. * Once the lock is acquired, executes the specified {@code action}, then releases the lock. *

* If the action throws an exception, the lock is released, and the action's exception * is propagated to the caller. *

* If Curator throws any exception, or if the timeout expires, the appropriate exception is thrown. * * @param lock the distributed lock to acquire * @param timeout the timeout duration * @param action the action to execute while holding the lock * @throws LockAcquisitionFailureException if the lock throws any exception during acquisition * @throws LockAcquisitionTimeoutException if the lock acquisition times out */ public void useLock(InterProcessLock lock, Duration timeout, Runnable action) { var nanos = timeout.toNanos(); useLock(lock, nanos, TimeUnit.NANOSECONDS, action); } /** * Tries to acquire the specified {@code lock}, waiting up to the specified timeout period. * Once the lock is acquired, executes the specified {@code action}, then releases the lock. *

* If the action throws an exception, the lock is released, and the action's exception * is propagated to the caller. *

* If Curator throws any exception, or if the timeout expires, the appropriate exception is thrown. * * @param lock the distributed lock to acquire * @param time the timeout quantity * @param unit the timeout unit * @param action the action to execute while holding the lock * @throws LockAcquisitionFailureException if the lock throws any exception during acquisition * @throws LockAcquisitionTimeoutException if the lock acquisition times out */ public void useLock(InterProcessLock lock, long time, TimeUnit unit, Runnable action) { try { acquire(lock, time, unit); action.run(); } finally { releaseQuietly(lock); } } /** * Enumeration representing the type of error related to using a lock or returning a value * while holding a lock. */ public enum ErrorType { /** * Represents an error related to lock acquisition. */ LOCK_ACQUISITION, /** * Represents an error related to a executing function or operation, e.g. * executing a {@link Runnable} or getting a value from a {@link Supplier}. */ OPERATION } /** * Tries to acquire the specified {@code lock}, waiting up to the specified timeout period. * Once the lock is acquired, executes the specified {@code action}, then releases the lock. *

* If the lock cannot be obtained for any reason, or the action throws an exception, the lock is released, and * the {@code errorHandler} is called. The {@link ErrorType} can be used to determine the cause, to permit different * handling for lock acquisition failures and action execution failures. * * @param lock the distributed lock to acquire * @param timeout the timeout duration * @param action the action to execute while holding the lock * @param errorHandler the action to take if the lock cannot be obtained, or if the action throws any exception */ public void useLock(InterProcessLock lock, Duration timeout, Runnable action, BiConsumer errorHandler) { var nanos = timeout.toNanos(); useLock(lock, nanos, TimeUnit.NANOSECONDS, action, errorHandler); } /** * Tries to acquire the specified {@code lock}, waiting up to the specified timeout period. * Once the lock is acquired, executes the specified {@code action}, then releases the lock. *

* If the lock cannot be obtained for any reason, or the action throws an exception, the lock is released, and * the {@code errorHandler} is called. The {@link ErrorType} can be used to determine the cause, to permit different * handling for lock acquisition failures and action execution failures. * * @param lock the distributed lock to acquire * @param time the timeout quantity * @param unit the timeout unit * @param action the action to execute while holding the lock * @param errorHandler the action to take if the lock cannot be obtained, or if the action throws any exception */ public void useLock(InterProcessLock lock, long time, TimeUnit unit, Runnable action, BiConsumer errorHandler) { try { useLock(lock, time, unit, action); } catch(LockAcquisitionException e) { errorHandler.accept(ErrorType.LOCK_ACQUISITION, e); } catch (RuntimeException e) { errorHandler.accept(ErrorType.OPERATION, e); } } /** * Tries to acquire the specified {@code lock}, waiting up to the specified timeout period. * Once the lock is acquired, calls the {@code supplier} and returns the result of the its computation, * then releases the lock. *

* If the supplier throws an exception, the lock is released, and the supplier's exception * is propagated to the caller. *

* If Curator throws any exception, or if the timeout expires, the appropriate exception is thrown. * * @param the type of the result produced by the supplier * @param lock the distributed lock to acquire * @param timeout the timeout duration * @param supplier The supplier providing the computation to be executed while holding the lock * @return The result of the computation provided by the supplier * @throws LockAcquisitionFailureException if the lock throws any exception during acquisition * @throws LockAcquisitionTimeoutException if the lock acquisition times out */ public R withLock(InterProcessLock lock, Duration timeout, Supplier supplier) { var nanos = timeout.toNanos(); return withLock(lock, nanos, TimeUnit.NANOSECONDS, supplier); } /** * Tries to acquire the specified {@code lock}, waiting up to the specified timeout period. * Once the lock is acquired, calls the {@code supplier} and returns the result of the its computation, * then releases the lock. *

* If the supplier throws an exception, the lock is released, and the supplier's exception * is propagated to the caller. *

* If Curator throws any exception, or if the timeout expires, the appropriate exception is thrown. * * @param the type of the result produced by the supplier * @param lock the distributed lock to acquire * @param time the timeout quantity * @param unit the timeout unit * @param supplier The supplier providing the computation to be executed while holding the lock * @return The result of the computation provided by the supplier * @throws LockAcquisitionFailureException if the lock throws any exception during acquisition * @throws LockAcquisitionTimeoutException if the lock acquisition times out */ public R withLock(InterProcessLock lock, long time, TimeUnit unit, Supplier supplier) { try { acquire(lock, time, unit); return supplier.get(); } finally { releaseQuietly(lock); } } /** * Tries to acquire the specified {@code lock}, waiting up to the specified timeout period. * Once the lock is acquired, calls the {@code supplier} and returns the result of the its computation, * then releases the lock. *

* If the lock cannot be obtained for any reason, or the action throws an exception, the lock is released, and * the {@code errorHandler} is called. The {@link ErrorType} can be used to determine the cause, to permit different * handling for lock acquisition failures and action execution failures. Note that because this method * requires a return value, the error handler must provide one, though it could be null. * * @param the type of the result produced by the supplier * @param lock the distributed lock to acquire * @param timeout the timeout duration * @param supplier the supplier providing the computation to be executed while holding the lock * @param errorHandler the action to take if the lock cannot be obtained, or if the supplier throws any exception * @return The result of the computation provided by the supplier */ public R withLock(InterProcessLock lock, Duration timeout, Supplier supplier, BiFunction errorHandler) { var nanos = timeout.toNanos(); return withLock(lock, nanos, TimeUnit.NANOSECONDS, supplier, errorHandler); } /** * Tries to acquire the specified {@code lock}, waiting up to the specified timeout period. * Once the lock is acquired, calls the {@code supplier} and returns the result of the its computation, * then releases the lock. *

* If the lock cannot be obtained for any reason, or the action throws an exception, the lock is released, and * the {@code errorHandler} is called. The {@link ErrorType} can be used to determine the cause, to permit different * handling for lock acquisition failures and action execution failures. Note that because this method * requires a return value, the error handler must provide one, though it could be null. * * @param the type of the result produced by the supplier * @param lock the distributed lock to acquire * @param time the timeout quantity * @param unit the timeout unit * @param supplier the supplier providing the computation to be executed while holding the lock * @param errorHandler the action to take if the lock cannot be obtained, or if the supplier throws any exception * @return The result of the computation provided by the supplier */ public R withLock(InterProcessLock lock, long time, TimeUnit unit, Supplier supplier, BiFunction errorHandler) { try { return withLock(lock, time, unit, supplier) ; } catch(LockAcquisitionException e) { return errorHandler.apply(ErrorType.LOCK_ACQUISITION, e); } catch (RuntimeException e) { return errorHandler.apply(ErrorType.OPERATION, e); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy