package org.infinispan.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.infinispan.Cache;
import org.infinispan.commands.VisitableCommand;
import org.infinispan.commands.tx.CommitCommand;
import org.infinispan.commands.tx.PrepareCommand;
import org.infinispan.commands.tx.RollbackCommand;
import org.infinispan.commands.write.ClearCommand;
import org.infinispan.context.InvocationContext;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.interceptors.AsyncInterceptorChain;
import org.infinispan.interceptors.BaseCustomAsyncInterceptor;
import org.infinispan.transaction.impl.TransactionTable;
import org.infinispan.transaction.xa.GlobalTransaction;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* @author Pedro Ruivo
* @since 5.3
*/
public class TransactionTrackInterceptor extends BaseCustomAsyncInterceptor {
private static final Log log = LogFactory.getLog(TransactionTrackInterceptor.class);
private static final GlobalTransaction CLEAR_TRANSACTION = new ClearGlobalTransaction();
private final Set<GlobalTransaction> localTransactionsSeen;
private final Set<GlobalTransaction> remoteTransactionsSeen;
//ordered local transaction list constructed from the operations.
private final ArrayList<GlobalTransaction> localTransactionsOperation;
private TransactionTrackInterceptor() {
localTransactionsSeen = new HashSet<>();
remoteTransactionsSeen = new HashSet<>();
localTransactionsOperation = new ArrayList<>(8);
}
public static TransactionTrackInterceptor injectInCache(Cache<?, ?> cache) {
AsyncInterceptorChain chain = cache.getAdvancedCache().getAsyncInterceptorChain();
if (chain.containsInterceptorType(TransactionTrackInterceptor.class)) {
return chain.findInterceptorWithClass(TransactionTrackInterceptor.class);
}
TransactionTrackInterceptor interceptor = new TransactionTrackInterceptor();
cache.getAdvancedCache().getComponentRegistry().wireDependencies(interceptor);
chain.addInterceptor(interceptor, 0);
return interceptor;
}
public synchronized final GlobalTransaction getLastExecutedTransaction() {
int size = localTransactionsOperation.size();
if (size == 0) {
return null;
}
return localTransactionsOperation.get(size - 1);
}
public synchronized final List<GlobalTransaction> getExecutedTransactions() {
return Collections.unmodifiableList(localTransactionsOperation);
}
@Override
public Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable {
return invokeNextAndFinally(ctx, command, (rCtx, rCommand, rv, t) -> {
if (rCtx.isOriginLocal()) {
addLocalTransaction(CLEAR_TRANSACTION);
//in total order, the transactions are self delivered. So, we simulate the self-deliver of the clear command.
seen(CLEAR_TRANSACTION, false);
}
seen(CLEAR_TRANSACTION, rCtx.isOriginLocal());
});
}
@Override
public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable {
return invokeNextAndFinally(ctx, command, (rCtx, rCommand, rv, t) -> {
seen(command.getGlobalTransaction(), rCtx.isOriginLocal());
});
}
@Override
public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command) throws Throwable {
return invokeNextAndFinally(ctx, command, (rCtx, rCommand, rv, t) -> {
seen(command.getGlobalTransaction(), rCtx.isOriginLocal());
});
}
@Override
public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable {
return invokeNextAndFinally(ctx, command, (rCtx, rCommand, rv, t) -> {
seen(command.getGlobalTransaction(), rCtx.isOriginLocal());
});
}
public boolean awaitForLocalCompletion(GlobalTransaction globalTransaction, long timeout, TimeUnit unit) throws InterruptedException {
long endTimeout = unit.toMillis(timeout) + System.currentTimeMillis();
while (System.currentTimeMillis() < endTimeout && !completedLocalTransactions(globalTransaction)) {
sleep();
}
boolean completed = completedLocalTransactions(globalTransaction);
if (log.isDebugEnabled()) {
log.debugf("[local] is %d completed? %s", (Object)globalTransaction.getId(), completed);
}
return completed;
}
public boolean awaitForRemoteCompletion(GlobalTransaction globalTransaction, long timeout, TimeUnit unit) throws InterruptedException {
long endTimeout = unit.toMillis(timeout) + System.currentTimeMillis();
while (System.currentTimeMillis() < endTimeout && !completedRemoteTransactions(globalTransaction)) {
sleep();
}
boolean completed = completedRemoteTransactions(globalTransaction);
if (log.isDebugEnabled()) {
log.debugf("[remote] is %d completed? %s", (Object)globalTransaction.getId(), completed);
}
return completed;
}
public boolean awaitForLocalCompletion(int expectedTransactions, long timeout, TimeUnit unit) throws InterruptedException {
long endTimeout = unit.toMillis(timeout) + System.currentTimeMillis();
while (System.currentTimeMillis() < endTimeout && completedLocalTransactions() < expectedTransactions) {
sleep();
}
if (log.isDebugEnabled()) {
log.debugf("[local] check for completion. seen=%s, expected=%s", localTransactionsSeen.size(), expectedTransactions);
}
return completedLocalTransactions() >= expectedTransactions;
}
public boolean awaitForRemoteCompletion(int expectedTransactions, long timeout, TimeUnit unit) throws InterruptedException {
long endTimeout = unit.toMillis(timeout) + System.currentTimeMillis();
while (System.currentTimeMillis() < endTimeout && completedRemoteTransactions() < expectedTransactions) {
sleep();
}
if (log.isDebugEnabled()) {
log.debugf("[remote] check for completion. seen=%s, expected=%s", remoteTransactionsSeen.size(), expectedTransactions);
}
return completedRemoteTransactions() >= expectedTransactions;
}
public synchronized void reset() {
localTransactionsSeen.clear();
remoteTransactionsSeen.clear();
localTransactionsOperation.clear();
}
@Override
protected Object handleDefault(InvocationContext ctx, VisitableCommand command) throws Throwable {
return invokeNextAndFinally(ctx, command, (rCtx, rCommand, rv, t) -> {
if (rCtx.isOriginLocal() && rCtx.isInTxScope()) {
GlobalTransaction globalTransaction = ((TxInvocationContext) rCtx).getGlobalTransaction();
addLocalTransaction(globalTransaction);
}
});
}
private synchronized void addLocalTransaction(GlobalTransaction globalTransaction) {
if (!localTransactionsOperation.contains(globalTransaction)) {
localTransactionsOperation.add(globalTransaction);
}
}
private synchronized void seen(GlobalTransaction globalTransaction, boolean local) {
log.tracef("Seen transaction %s. Local? %s", globalTransaction, local);
if (local) {
localTransactionsSeen.add(globalTransaction);
} else {
remoteTransactionsSeen.add(globalTransaction);
}
}
private void sleep() throws InterruptedException {
Thread.sleep(100);
}
private synchronized int completedLocalTransactions() {
int count = 0;
TransactionTable transactionTable = getTransactionTable();
for (GlobalTransaction tx : localTransactionsSeen) {
if (!transactionTable.containsLocalTx(tx)) {
count++;
}
}
return count;
}
private synchronized int completedRemoteTransactions() {
int count = 0;
TransactionTable transactionTable = getTransactionTable();
for (GlobalTransaction tx : remoteTransactionsSeen) {
if (!transactionTable.containRemoteTx(tx)) {
count++;
}
}
return count;
}
private synchronized boolean completedLocalTransactions(GlobalTransaction globalTransaction) {
return localTransactionsSeen.contains(globalTransaction) &&
!getTransactionTable().containsLocalTx(globalTransaction);
}
private synchronized boolean completedRemoteTransactions(GlobalTransaction globalTransaction) {
return remoteTransactionsSeen.contains(globalTransaction) &&
!getTransactionTable().containRemoteTx(globalTransaction);
}
private TransactionTable getTransactionTable() {
return cache.getAdvancedCache().getComponentRegistry().getComponent(TransactionTable.class);
}
private static class ClearGlobalTransaction extends GlobalTransaction {
ClearGlobalTransaction() {
super(null, false);
}
}
}