package org.rakam.util.lock;
import com.google.common.base.Throwables;
import org.rakam.analysis.JDBCPoolDataSource;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.util.BooleanMapper;
import java.sql.SQLException;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
public class PostgresqlLockService
implements LockService
{
private final DBI dbi;
private Handle currentHandle;
private Set<String> locks;
public PostgresqlLockService(JDBCPoolDataSource poolDataSource)
{
this.dbi = new DBI(() -> {
return poolDataSource.getConnection(true);
});
this.currentHandle = dbi.open();
locks = new ConcurrentSkipListSet<>();
}
@Override
public synchronized Lock tryLock(String name)
{
if (!locks.add(name)) {
return null;
}
try {
return tryLock(name, 4);
}
catch (Throwable e) {
locks.remove(name);
throw Throwables.propagate(e);
}
}
private Lock tryLock(String name, int tryCount)
{
try {
if (currentHandle.getConnection().isClosed()) {
synchronized (this) {
currentHandle = dbi.open();
}
}
Boolean first = currentHandle.createQuery("select pg_try_advisory_lock(:name)")
.bind("name", name.hashCode())
.map(BooleanMapper.FIRST)
.first();
if (!Boolean.TRUE.equals(first)) {
locks.remove(name);
return null;
}
return () -> {
locks.remove(name);
currentHandle.createQuery("select pg_advisory_unlock(:name)")
.bind("name", name.hashCode())
.map(BooleanMapper.FIRST)
.first();
};
}
catch (SQLException e) {
try {
if (currentHandle.getConnection().isClosed()) {
synchronized (this) {
currentHandle = dbi.open();
}
}
}
catch (SQLException e1) {
synchronized (this) {
currentHandle = dbi.open();
}
}
if (tryCount == 0) {
throw Throwables.propagate(e);
}
return tryLock(name, tryCount - 1);
}
}
}