package org.gbif.occurrence.persistence.zookeeper;
import org.gbif.api.exception.ServiceUnavailableException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Manages locks in zookeeper on dataset keys (uuids). Initial use is for the purposes of creating new occurrence keys.
* Note that this class is not thread-safe: each thread should create its own instance of this class (although they
* can (and should) all share the same CuratorFramework instance).
*/
public class ZookeeperLockManager {
private final CuratorFramework curator;
private final Map<String, InterProcessSemaphoreMutex> locks = Maps.newHashMap();
private static final String PATH_TO_LOCKS = "/datasetLocks/";
private static final long MILLISECONDS_TO_WAIT = 0;
private static final Logger LOG = LoggerFactory.getLogger(ZookeeperLockManager.class);
/**
* Create a new instance, one for every thread that wants lock access.
*
* @param curator a started curator that holds the zookeeper connection
*/
@Inject
public ZookeeperLockManager(CuratorFramework curator) {
this.curator = checkNotNull(curator, "curator can't be null");
}
/**
* Get a lock for the passed in dataset. If the lock has already been taken, or there is any error in the process
* of acquiring the lock, false will be returned.
*
* @param datasetKey the dataset on which to lock
*
* @return true if the lock was acquired
*/
public boolean getLock(String datasetKey) {
checkNotNull(datasetKey);
boolean gotLock = false;
String path = buildPath(datasetKey);
InterProcessSemaphoreMutex lock = locks.get(path);
if (lock == null) {
lock = new InterProcessSemaphoreMutex(curator, path);
locks.put(path, lock);
}
try {
// if the lock is out, fail immediately
gotLock = lock.acquire(MILLISECONDS_TO_WAIT, TimeUnit.MILLISECONDS);
} catch (Exception e) {
LOG.warn("Failure communicating with Zookeeper", e);
}
return gotLock;
}
/**
* A method that blocks until the lock for the requested dataset becomes available.
*
* @param datasetKey the dataset on which to lock
*
* @throws org.gbif.api.exception.ServiceUnavailableException
* if there is an error communicating with Zookeeper
*/
public void waitForLock(String datasetKey) {
checkNotNull(datasetKey);
String path = buildPath(datasetKey);
InterProcessSemaphoreMutex lock = locks.get(path);
if (lock == null) {
lock = new InterProcessSemaphoreMutex(curator, path);
locks.put(path, lock);
}
try {
lock.acquire();
} catch (Exception e) {
throw new ServiceUnavailableException("Failure while communicating with Zookeeper", e);
}
}
/**
* Release a held lock on a dataset. There is no danger in calling this method if the lock is not held.
*
* @param datasetKey the dataset for which the held lock should be released
*/
public void releaseLock(String datasetKey) {
String path = buildPath(datasetKey);
InterProcessSemaphoreMutex lock = locks.get(path);
try {
if (lock != null) {
lock.release();
}
} catch (Exception e) {
LOG.warn("Failure communicating with Zookeeper", e);
} finally {
// if we fail to contact zookeeper we've already lost the lock
locks.remove(path);
}
}
private static String buildPath(String datasetKey) {
return PATH_TO_LOCKS + datasetKey;
}
}