package org.infinispan.util.concurrent.locks.impl;
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
import static org.infinispan.commons.util.Util.toStr;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.infinispan.commons.util.Util;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.context.InvocationContext;
import org.infinispan.factories.KnownComponentNames;
import org.infinispan.factories.annotations.ComponentName;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.jmx.annotations.DataType;
import org.infinispan.jmx.annotations.MBean;
import org.infinispan.jmx.annotations.ManagedAttribute;
import org.infinispan.util.concurrent.TimeoutException;
import org.infinispan.util.concurrent.locks.DeadlockDetectedException;
import org.infinispan.util.concurrent.locks.ExtendedLockPromise;
import org.infinispan.util.concurrent.locks.KeyAwareLockListener;
import org.infinispan.util.concurrent.locks.KeyAwareLockPromise;
import org.infinispan.util.concurrent.locks.LockListener;
import org.infinispan.util.concurrent.locks.LockManager;
import org.infinispan.util.concurrent.locks.LockPromise;
import org.infinispan.util.concurrent.locks.LockState;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* The default {@link LockManager} implementation for transactional and non-transactional caches.
*
* @author Pedro Ruivo
* @since 8.0
*/
@MBean(objectName = "LockManager", description = "Manager that handles MVCC locks for entries")
public class DefaultLockManager implements LockManager {
private static final Log log = LogFactory.getLog(DefaultLockManager.class);
private static final boolean trace = log.isTraceEnabled();
private static final AtomicReferenceFieldUpdater<CompositeLockPromise, LockState> UPDATER =
newUpdater(CompositeLockPromise.class, LockState.class, "lockState");
protected LockContainer lockContainer;
protected Configuration configuration;
protected ScheduledExecutorService scheduler;
@Inject
public void inject(LockContainer container, Configuration configuration,
@ComponentName(KnownComponentNames.TIMEOUT_SCHEDULE_EXECUTOR) ScheduledExecutorService executorService) {
this.lockContainer = container;
this.configuration = configuration;
this.scheduler = executorService;
}
@Override
public KeyAwareLockPromise lock(Object key, Object lockOwner, long time, TimeUnit unit) {
Objects.requireNonNull(key, "Key must be non null");
Objects.requireNonNull(lockOwner, "Lock owner must be non null");
Objects.requireNonNull(unit, "Time unit must be non null");
if (trace) {
log.tracef("Lock key=%s for owner=%s. timeout=%s (%s)", toStr(key), lockOwner, time, unit);
}
ExtendedLockPromise promise = lockContainer.acquire(key, lockOwner, time, unit);
return new KeyAwareExtendedLockPromise(promise, key, unit.toMillis(time)).scheduleLockTimeoutTask(scheduler);
}
@Override
public KeyAwareLockPromise lockAll(Collection<?> keys, Object lockOwner, long time, TimeUnit unit) {
Objects.requireNonNull(keys, "Keys must be non null");
Objects.requireNonNull(lockOwner, "Lock owner must be non null");
Objects.requireNonNull(unit, "Time unit must be non null");
if (keys.isEmpty()) {
if (trace) {
log.tracef("Lock all: no keys found for owner=%s", lockOwner);
}
return KeyAwareLockPromise.NO_OP;
} else if (keys.size() == 1) {
//although will have the cost of creating an iterator, at least, we don't need to enter the synchronized section.
return lock(keys.iterator().next(), lockOwner, time, unit);
}
final Set<Object> uniqueKeys = filterDistinctKeys(keys);
if (uniqueKeys.size() == 1) {
//although will have the cost of creating an iterator, at least, we don't need to enter the synchronized section.
return lock(uniqueKeys.iterator().next(), lockOwner, time, unit);
}
if (trace) {
log.tracef("Lock all keys=%s for owner=%s. timeout=%s (%s)", toStr(uniqueKeys), lockOwner, time,
unit);
}
final CompositeLockPromise compositeLockPromise = new CompositeLockPromise(uniqueKeys.size());
//needed to avoid internal deadlock when 2 or more lock owner invokes this method with the same keys.
//ordering will not solve the problem since acquire() is non-blocking and each lock owner can iterate faster/slower than the other.
synchronized (this) {
for (Object key : uniqueKeys) {
compositeLockPromise.addLock(new KeyAwareExtendedLockPromise(lockContainer.acquire(key, lockOwner, time, unit), key, unit.toMillis(time)));
}
}
compositeLockPromise.markListAsFinal();
return compositeLockPromise.scheduleLockTimeoutTask(scheduler, time, unit);
}
private Set<Object> filterDistinctKeys(Collection<?> collection) {
if (collection instanceof Set) {
//noinspection unchecked
return (Set<Object>) collection;
} else {
return new HashSet<>(collection);
}
}
@Override
public void unlock(Object key, Object lockOwner) {
if (trace) {
log.tracef("Release lock for key=%s. owner=%s", key, lockOwner);
}
lockContainer.release(key, lockOwner);
}
@Override
public void unlockAll(Collection<?> keys, Object lockOwner) {
if (trace) {
log.tracef("Release locks for keys=%s. owner=%s", toStr(keys), lockOwner);
}
if (keys.isEmpty()) {
return;
}
for (Object key : keys) {
lockContainer.release(key, lockOwner);
}
}
@Override
public void unlockAll(InvocationContext context) {
unlockAll(context.getLockedKeys(), context.getLockOwner());
context.clearLockedKeys();
}
@Override
public boolean ownsLock(Object key, Object lockOwner) {
Object currentOwner = getOwner(key);
return currentOwner != null && currentOwner.equals(lockOwner);
}
@Override
public boolean isLocked(Object key) {
return getOwner(key) != null;
}
@Override
public Object getOwner(Object key) {
InfinispanLock lock = lockContainer.getLock(key);
return lock == null ? null : lock.getLockOwner();
}
@Override
public String printLockInfo() {
return lockContainer.toString();
}
@Override
@ManagedAttribute(description = "The number of exclusive locks that are held.", displayName = "Number of locks held")
public int getNumberOfLocksHeld() {
return lockContainer.getNumLocksHeld();
}
@ManagedAttribute(description = "The concurrency level that the MVCC Lock Manager has been configured with.", displayName = "Concurrency level", dataType = DataType.TRAIT)
public int getConcurrencyLevel() {
return configuration.locking().concurrencyLevel();
}
@ManagedAttribute(description = "The number of exclusive locks that are available.", displayName = "Number of locks available")
public int getNumberOfLocksAvailable() {
return lockContainer.size() - lockContainer.getNumLocksHeld();
}
@Override
public InfinispanLock getLock(Object key) {
return lockContainer.getLock(key);
}
private static class KeyAwareExtendedLockPromise implements KeyAwareLockPromise, ExtendedLockPromise, Callable<Void> {
private final ExtendedLockPromise lockPromise;
private final Object key;
private final long timeoutMillis;
private KeyAwareExtendedLockPromise(ExtendedLockPromise lockPromise, Object key, long timeoutMillis) {
this.lockPromise = lockPromise;
this.key = key;
this.timeoutMillis = timeoutMillis;
}
@Override
public void cancel(LockState cause) {
lockPromise.cancel(cause);
}
@Override
public Object getRequestor() {
return lockPromise.getRequestor();
}
@Override
public Object getOwner() {
return lockPromise.getOwner();
}
@Override
public boolean isAvailable() {
return lockPromise.isAvailable();
}
@Override
public void lock() throws InterruptedException, TimeoutException {
try {
lockPromise.lock();
} catch (TimeoutException e) {
throw log.unableToAcquireLock(Util.prettyPrintTime(timeoutMillis), toStr(key),
lockPromise.getRequestor(), lockPromise.getOwner());
}
}
@Override
public void addListener(LockListener listener) {
lockPromise.addListener(listener);
}
@Override
public void addListener(KeyAwareLockListener listener) {
lockPromise.addListener(state -> listener.onEvent(key, state));
}
@Override
public Void call() throws Exception{
lockPromise.cancel(LockState.TIMED_OUT);
return null;
}
KeyAwareExtendedLockPromise scheduleLockTimeoutTask(ScheduledExecutorService executorService) {
if (executorService != null && timeoutMillis > 0 && !isAvailable()) {
ScheduledFuture<?> future = executorService.schedule(this, timeoutMillis, TimeUnit.MILLISECONDS);
lockPromise.addListener((state -> future.cancel(false)));
}
return this;
}
}
private static class CompositeLockPromise implements KeyAwareLockPromise, LockListener, Callable<Void> {
private final List<KeyAwareExtendedLockPromise> lockPromiseList;
private final CompletableFuture<LockState> notifier;
volatile LockState lockState = LockState.ACQUIRED;
private final AtomicInteger countersLeft = new AtomicInteger();
private CompositeLockPromise(int size) {
lockPromiseList = new ArrayList<>(size);
notifier = new CompletableFuture<>();
}
void addLock(KeyAwareExtendedLockPromise lockPromise) {
lockPromiseList.add(lockPromise);
}
void markListAsFinal() {
countersLeft.set(lockPromiseList.size());
for (LockPromise lockPromise : lockPromiseList) {
lockPromise.addListener(this);
}
}
@Override
public boolean isAvailable() {
return notifier.isDone();
}
@Override
public void lock() throws InterruptedException, TimeoutException {
InterruptedException interruptedException = null;
TimeoutException timeoutException = null;
DeadlockDetectedException deadlockException = null;
RuntimeException runtimeException = null;
for (ExtendedLockPromise lockPromise : lockPromiseList) {
try {
//we still need to invoke lock in all the locks.
lockPromise.lock();
} catch (InterruptedException e) {
interruptedException = e;
} catch (TimeoutException e) {
timeoutException = e;
} catch (DeadlockDetectedException e) {
deadlockException = e;
} catch (RuntimeException e) {
runtimeException = e;
}
}
if (interruptedException != null) {
throw interruptedException;
} else if (timeoutException != null) {
throw timeoutException;
} else if (deadlockException != null) {
throw deadlockException;
} else if (runtimeException != null) {
throw runtimeException;
}
}
@Override
public void addListener(LockListener listener) {
notifier.thenAccept(listener::onEvent);
}
@Override
public void onEvent(LockState state) {
if (notifier.isDone()) {
//already finished
return;
}
//each lock will invoke this
if (state != LockState.ACQUIRED) {
cancelAll(state);
return;
}
if (countersLeft.decrementAndGet() == 0) {
notifier.complete(lockState);
}
}
private void cancelAll(LockState state) {
if (UPDATER.compareAndSet(this, LockState.ACQUIRED, state)) {
//complete the future before cancel other locks. the remaining locks will be invoke onEvent()
notifier.complete(state);
lockPromiseList.forEach(promise -> promise.cancel(state));
}
}
@Override
public void addListener(KeyAwareLockListener listener) {
for (KeyAwareExtendedLockPromise lockPromise : lockPromiseList) {
lockPromise.addListener(listener);
}
}
@Override
public Void call() throws Exception {
lockPromiseList.forEach(promise -> promise.cancel(LockState.TIMED_OUT));
return null;
}
CompositeLockPromise scheduleLockTimeoutTask(ScheduledExecutorService executorService, long time, TimeUnit unit) {
if (executorService != null && time > 0 && !isAvailable()) {
ScheduledFuture<?> future = executorService.schedule(this, time, unit);
addListener((state -> future.cancel(false)));
}
return this;
}
}
}