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

org.esbtools.eventhandler.lightblue.locking.LightblueAutoPingLockStrategy Maven / Gradle / Ivy

There is a newer version: 0.1.15
Show newest version
/*
 *  Copyright 2016 esbtools Contributors and/or its affiliates.
 *
 *  This file is part of esbtools.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see .
 */

package org.esbtools.eventhandler.lightblue.locking;

import com.redhat.lightblue.client.LightblueException;
import com.redhat.lightblue.client.Locking;
import com.redhat.lightblue.client.response.lock.InvalidLockException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Uses lightblue's locking APIs with TTL, automatically pinging the lock in the background until it
 * is released.
 *
 * 

Lightblue locks without TTLs are unsafe. If a client crashes, the lock will not be released. * However, locks with TTLs may expire prematurely. By pinging the lock in a separate thread * periodically, we can substantially lesson the likelihood of unintentionally losing the lock. * *

Consumers are expected to check the lock at critical points to ensure it did not expire by * calling {@link LockedResource#ensureAcquiredOrThrow(String)}. */ public class LightblueAutoPingLockStrategy implements LockStrategy { private final Locking locking; private final Duration autoPingInterval; private final Duration timeToLive; /** * Same as {@link #LightblueAutoPingLockStrategy(Locking, Duration, Duration)} * except the {@code timeToLive} duration defaults to 5 times the {@code autoPingInterval}. */ public LightblueAutoPingLockStrategy(Locking locking, Duration autoPingInterval) { this(locking, autoPingInterval, autoPingInterval.multipliedBy(5)); } /** * Validates {@code timeToLive} is greater than the {@code autoPingInterval}. * @param locking The locking client to use which holds knowledge of the locking domain to use. * The default clientId is ignored. * @param autoPingInterval Amount of time in between automatic pings of acquired locks. * @param timeToLive Time until locks automatically expire. Should be [much] larger than the * {@code autoPingInterval} to ensure locks do not accidentally expire. */ public LightblueAutoPingLockStrategy(Locking locking, Duration autoPingInterval, Duration timeToLive) { this.locking = locking; this.autoPingInterval = autoPingInterval; this.timeToLive = timeToLive; if (timeToLive.compareTo(autoPingInterval) <= 0) { throw new IllegalArgumentException("Time to live should be greater than auto ping " + "interval, otherwise the lock will likely be lost."); } } @Override public LockedResource tryAcquire(String resourceId, T resource) throws LockNotAvailableException { try { // TODO: May want to include hostname and/or thread information in clientId String callerId = UUID.randomUUID().toString(); return new AutoPingingLock<>(locking, callerId, resourceId, resource, autoPingInterval, timeToLive); } catch (LightblueException e) { throw new LockNotAvailableException(resourceId, resource, e); } } static final class AutoPingingLock implements LockedResource { private final String callerId; private final T resource; private final String resourceId; private final Locking locking; private final ScheduledExecutorService autoPingScheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledFuture autoPinger; private final AtomicBoolean isClosed = new AtomicBoolean(false); private static final Logger logger = LoggerFactory.getLogger(AutoPingingLock.class); AutoPingingLock(Locking locking, String callerId, String resourceId, T resource, Duration autoPingInterval, Duration ttl) throws LightblueException, LockNotAvailableException { this.callerId = callerId; this.resource = resource; this.locking = locking; this.resourceId = resourceId; if (!locking.acquire(callerId, resourceId, ttl.toMillis())) { throw new LockNotAvailableException(resourceId, resource); } this.autoPinger = autoPingScheduler.scheduleWithFixedDelay( new PingTask(this), /* initial delay*/ autoPingInterval.toMillis(), /* delay */ autoPingInterval.toMillis(), TimeUnit.MILLISECONDS); } @Override public void ensureAcquiredOrThrow(String lostLockMessage) throws LostLockException { try { if (!locking.ping(callerId, resourceId)) { stopPinging(); throw new LostLockException(this, lostLockMessage); } } catch (LightblueException e) { try { close(); } catch (IOException suppressed) { logger.warn("Caught IOException trying to release lock after failed ping. " + "Ignoring.", suppressed); } throw new LostLockException(this, "Failed to ping lock, assuming lost. This can " + "happen if lock is already closed or failed to communicate with " + "lightblue. " + lostLockMessage, e); } } @Override public T getResource() { return resource; } @Override public void close() throws IOException { if (isClosed.get()) { return; } try { autoPinger.cancel(true); autoPingScheduler.shutdownNow(); locking.release(callerId, resourceId); isClosed.set(true); } catch (LightblueException e) { throw new IOException("Unable to release lock. callerId: " + callerId + ", resourceId: " + resourceId, e); } } @Override public String toString() { return "AutoPingingLock{" + "resourceId='" + resourceId + '\'' + ", callerId='" + callerId + '\'' + ", lockingDomain='" + locking.getDomain() + '\'' + '}'; } /** * Does NOT call out to lightblue to release the lock. If lightblue may still have the lock * and you are not okay with it expiring on its own, use {@link #close()}. */ private void stopPinging() { autoPinger.cancel(true); autoPingScheduler.shutdownNow(); isClosed.set(true); } static class PingTask implements Runnable { final AutoPingingLock lock; PingTask(AutoPingingLock lock) { this.lock = lock; } @Override public void run() { try { if (!lock.locking.ping(lock.callerId, lock.resourceId)) { lock.stopPinging(); throw new RuntimeException("Lost lock. Will stop pinging. Lock was: " + lock); } logger.debug("Periodic lock ping successful. callerId={} resourceId={}", lock.callerId, lock.resourceId); } catch (InvalidLockException e) { logger.error("Tried to ping an invalid lock. Will stop pinging. Lock was: " + lock, e); lock.stopPinging(); throw new RuntimeException(e); } catch (LightblueException e) { logger.error("Periodic lock ping failed for callerId <{}> and " + "resourceId <{}>. Will keep trying.", lock.callerId, lock.resourceId, e); } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy