package org.infinispan.remoting.inboundhandler.action;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.interceptors.locking.ClusteringDependentLogic;
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.concurrent.locks.RemoteLockCommand;
import org.infinispan.util.concurrent.locks.TransactionalRemoteLockCommand;
/**
* An {@link Action} implementation that acquire the locks.
* <p/>
* It returns {@link ActionStatus#READY} when the locks are available to acquired or the acquisition failed (timeout or
* deadlock).
*
* @author Pedro Ruivo
* @since 8.0
*/
public class LockAction extends BaseLockingAction implements LockListener {
private final LockManager lockManager;
private final CompletableFuture<Void> notifier;
private volatile LockPromise lockPromise;
public LockAction(LockManager lockManager, ClusteringDependentLogic clusteringDependentLogic) {
super(clusteringDependentLogic);
this.lockManager = lockManager;
notifier = new CompletableFuture<>();
}
@Override
protected ActionStatus checking(ActionState ignored) {
LockPromise promise = lockPromise;
if (promise != null && promise.isAvailable()) {
cas(InternalState.CHECKING, InternalState.READY);
return ActionStatus.READY;
} else {
return ActionStatus.NOT_READY;
}
}
@Override
protected ActionStatus init(ActionState state) {
if (!cas(InternalState.INIT, InternalState.CHECKING)) {
return ActionStatus.NOT_READY; //another thread is making progress
}
final Object lockOwner = getLockOwner(state);
final long timeout = state.getTimeout();
List<Object> keysToLock = getAndUpdateFilteredKeys(state);
if (keysToLock.isEmpty()) {
return cas(InternalState.CHECKING, InternalState.READY) ? ActionStatus.READY : ActionStatus.NOT_READY;
}
TxInvocationContext context = createContext(state);
if (context != null) {
keysToLock.forEach(context::addLockedKey);
}
LockPromise promise = keysToLock.size() == 1 ?
lockManager.lock(keysToLock.get(0), lockOwner, timeout, TimeUnit.MILLISECONDS) :
lockManager.lockAll(keysToLock, lockOwner, timeout, TimeUnit.MILLISECONDS);
lockPromise = promise;
if (!promise.isAvailable()) {
promise.addListener(this);
}
return check(state);
}
private Object getLockOwner(ActionState state) {
RemoteLockCommand command = state.getCommand();
return command instanceof TransactionalRemoteLockCommand ?
((TransactionalRemoteLockCommand) command).createContext().getLockOwner() :
command.getKeyLockOwner();
}
@Override
public void addListener(ActionListener listener) {
notifier.thenRun(listener::onComplete);
}
@Override
public void onException(ActionState state) {
final Object lockOwner = getLockOwner(state);
List<Object> keysToLock = getAndUpdateFilteredKeys(state);
lockManager.unlockAll(keysToLock, lockOwner);
}
@Override
public void onEvent(LockState state) {
notifier.complete(null);
}
private TxInvocationContext<?> createContext(ActionState state) {
RemoteLockCommand command = state.getCommand();
if (command instanceof TransactionalRemoteLockCommand) {
return ((TransactionalRemoteLockCommand) command).createContext();
}
return null;
}
}