package org.infinispan.transaction.totalorder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import org.infinispan.commons.util.CollectionFactory;
import org.infinispan.factories.KnownComponentNames;
import org.infinispan.factories.annotations.ComponentName;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.transaction.impl.TotalOrderRemoteTransactionState;
import org.infinispan.util.concurrent.BlockingTaskAwareExecutorService;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* This class behaves as a synchronization point between incoming transactions (totally ordered) and between incoming
* transactions and state transfer.
* <p/>
* Main functions:
* <ul>
* <li>
* ensure an order between prepares before sending them to the thread pool, i.e. non-conflicting
* prepares can be processed concurrently;
* </li>
* <li>
* ensure that the state transfer waits for the previous delivered prepares;
* </li>
* <li>
* ensure that the prepare waits for state transfer in progress.
* </li>
* </ul>
*
* @author Pedro Ruivo
* @since 5.3
*/
public class TotalOrderManager {
private static final Log log = LogFactory.getLog(TotalOrderManager.class);
private static final boolean trace = log.isTraceEnabled();
/**
* this map is used to keep track of concurrent transactions.
*/
private final ConcurrentMap<Object, TotalOrderLatch> keysLocked;
private final AtomicReference<TotalOrderLatch> stateTransferInProgress;
private BlockingTaskAwareExecutorService totalOrderExecutor;
public TotalOrderManager() {
keysLocked = CollectionFactory.makeConcurrentMap();
stateTransferInProgress = new AtomicReference<>(null);
}
@Inject
public void inject(@ComponentName(KnownComponentNames.REMOTE_COMMAND_EXECUTOR) BlockingTaskAwareExecutorService totalOrderExecutor) {
this.totalOrderExecutor = totalOrderExecutor;
}
/**
* It ensures the validation order for the transaction corresponding to the prepare command. This allow the prepare
* command to be moved to a thread pool.
*
* @param state the total order prepare state
*/
public final void ensureOrder(TotalOrderRemoteTransactionState state, Collection<?> keysModified) throws InterruptedException {
//the retries due to state transfer re-uses the same state. we need that the keys previous locked to be release
//in order to insert it again in the keys locked.
//NOTE: this method does not need to be synchronized because it is invoked by a one thread at the time, namely
//the thread that is delivering the messages in total order.
state.awaitUntilReset();
TotalOrderLatch transactionSynchronizedBlock = new TotalOrderLatchImpl(state.getGlobalTransaction().globalId());
state.setTransactionSynchronizedBlock(transactionSynchronizedBlock);
//this will collect all the count down latch corresponding to the previous transactions in the queue
for (Object key : keysModified) {
TotalOrderLatch prevTx = keysLocked.put(key, transactionSynchronizedBlock);
if (prevTx != null) {
state.addSynchronizedBlock(prevTx);
}
state.addLockedKey(key);
}
TotalOrderLatch stateTransfer = stateTransferInProgress.get();
if (stateTransfer != null) {
state.addSynchronizedBlock(stateTransfer);
}
if (trace) {
log.tracef("Transaction [%s] will wait for %s and locked %s", state.getGlobalTransaction().globalId(),
state.getConflictingTransactionBlocks(), state.getLockedKeys() == null ? "[ClearCommand]" :
state.getLockedKeys());
}
}
/**
* Release the locked key possibly unblock waiting prepares.
*
* @param state the state
*/
public final void release(TotalOrderRemoteTransactionState state) {
TotalOrderLatch synchronizedBlock = state.getTransactionSynchronizedBlock();
if (synchronizedBlock == null) {
//already released!
return;
}
Collection<Object> lockedKeys = state.getLockedKeys();
synchronizedBlock.unBlock();
for (Object key : lockedKeys) {
keysLocked.remove(key, synchronizedBlock);
}
if (trace) {
log.tracef("Release %s and locked keys %s. Checking pending tasks!", synchronizedBlock, lockedKeys);
}
state.reset();
}
/**
* It notifies that a state transfer is about to start.
*
* @param topologyId the new topology ID
* @return the current pending prepares
*/
public final Collection<TotalOrderLatch> notifyStateTransferStart(int topologyId, boolean isRebalance) {
if (stateTransferInProgress.get() != null) {
return Collections.emptyList();
}
List<TotalOrderLatch> preparingTransactions = new ArrayList<>(keysLocked.size());
preparingTransactions.addAll(keysLocked.values());
if (isRebalance) {
stateTransferInProgress.set(new TotalOrderLatchImpl("StateTransfer-" + topologyId));
}
if (trace) {
log.tracef("State Transfer start. It will wait for %s", preparingTransactions);
}
return preparingTransactions;
}
/**
* It notifies the end of the state transfer possibly unblock waiting prepares.
*/
public final void notifyStateTransferEnd() {
TotalOrderLatch block = stateTransferInProgress.getAndSet(null);
if (block != null) {
block.unBlock();
}
if (trace) {
log.tracef("State Transfer finish. It will release %s", block);
}
totalOrderExecutor.checkForReadyTasks();
}
public final boolean hasAnyLockAcquired() {
return !keysLocked.isEmpty();
}
}