package org.infinispan.remoting.inboundhandler.action; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.infinispan.commands.tx.PrepareCommand; import org.infinispan.context.impl.TxInvocationContext; import org.infinispan.interceptors.locking.ClusteringDependentLogic; import org.infinispan.util.concurrent.locks.PendingLockListener; import org.infinispan.util.concurrent.locks.PendingLockManager; import org.infinispan.util.concurrent.locks.PendingLockPromise; import org.infinispan.util.concurrent.locks.RemoteLockCommand; import org.infinispan.util.concurrent.locks.TransactionalRemoteLockCommand; /** * An {@link Action} implementation that check for older topology transactions. * <p/> * This action is ready when no older topology transactions exists or is canceled when the timeout occurs. * * @author Pedro Ruivo * @since 8.0 */ public class PendingTxAction extends BaseLockingAction implements PendingLockListener { private final PendingLockManager pendingLockManager; private final CompletableFuture<Void> notifier; private volatile PendingLockPromise pendingLockPromise; public PendingTxAction(PendingLockManager pendingLockManager, ClusteringDependentLogic clusteringDependentLogic) { super(clusteringDependentLogic); this.pendingLockManager = pendingLockManager; notifier = new CompletableFuture<>(); } @Override protected ActionStatus checking(ActionState state) { PendingLockPromise promise = pendingLockPromise; if (promise != null && promise.isReady() && cas(InternalState.CHECKING, InternalState.MAKE_READY)) { if (promise.hasTimedOut()) { cas(InternalState.MAKE_READY, InternalState.CANCELED); return ActionStatus.CANCELED; } else { state.updateTimeout(promise.getRemainingTimeout()); cas(InternalState.MAKE_READY, InternalState.READY); return ActionStatus.READY; } } 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 TxInvocationContext<?> context = createContext(state); if (context == null) { //cancel everything. nobody else was able to update the state from checking, so no need to check the CAS cas(InternalState.CHECKING, InternalState.CANCELED); return ActionStatus.CANCELED; } final long timeout = state.getTimeout(); final List<Object> keysToLock = getAndUpdateFilteredKeys(state); if (keysToLock.isEmpty()) { cas(InternalState.CHECKING, InternalState.READY); return ActionStatus.READY; } RemoteLockCommand command = state.getCommand(); if (command instanceof PrepareCommand && ((PrepareCommand) command).isRetriedCommand()) { //clear the backup locks context.getCacheTransaction().cleanupBackupLocks(); keysToLock.removeAll(context.getLockedKeys()); } PendingLockPromise promise = keysToLock.size() == 1 ? pendingLockManager.checkPendingTransactionsForKey(context, keysToLock.get(0), timeout, TimeUnit.MILLISECONDS) : pendingLockManager.checkPendingTransactionsForKeys(context, keysToLock, timeout, TimeUnit.MILLISECONDS); if (promise.isReady()) { //nothing to do. nobody else was able to update the state from checking, so no need to check the CAS cas(InternalState.CHECKING, InternalState.READY); return ActionStatus.READY; } pendingLockPromise = promise; if (!promise.isReady()) { promise.addListener(this); } return check(state); } @Override public void addListener(ActionListener listener) { notifier.thenRun(listener::onComplete); } @Override public void onReady() { notifier.complete(null); } private TxInvocationContext<?> createContext(ActionState state) { RemoteLockCommand command = state.getCommand(); if (command instanceof TransactionalRemoteLockCommand) { return ((TransactionalRemoteLockCommand) command).createContext(); } return null; } }