package org.infinispan.interceptors.totalorder;
import org.infinispan.commands.VisitableCommand;
import org.infinispan.commands.tx.PrepareCommand;
import org.infinispan.context.InvocationContext;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.interceptors.InvocationExceptionFunction;
import org.infinispan.interceptors.impl.BaseStateTransferInterceptor;
import org.infinispan.remoting.RemoteException;
import org.infinispan.transaction.impl.RemoteTransaction;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* Synchronizes the incoming totally ordered transactions with the state transfer.
*
* @author Pedro Ruivo
*/
public class TotalOrderStateTransferInterceptor extends BaseStateTransferInterceptor {
private static final Log log = LogFactory.getLog(TotalOrderStateTransferInterceptor.class);
private static final boolean trace = log.isTraceEnabled();
private final InvocationExceptionFunction handleLocalPrepareReturn = this::handleLocalPrepareReturn;
@Override
public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable {
if (ctx.isOriginLocal()) {
return localPrepare(ctx, command);
}
return remotePrepare(ctx, command);
}
private Object remotePrepare(TxInvocationContext ctx, PrepareCommand command) throws Throwable {
final int topologyId = currentTopologyId();
((RemoteTransaction) ctx.getCacheTransaction()).setLookedUpEntriesTopology(command.getTopologyId());
if (trace) {
log.tracef("Remote transaction received %s. Tx topology id is %s and current topology is is %s",
ctx.getGlobalTransaction().globalId(), command.getTopologyId(), topologyId);
}
if (command.getTopologyId() < topologyId) {
if (log.isDebugEnabled()) {
log.debugf("Transaction %s delivered in new topology Id. Discard it because it should be retransmitted",
ctx.getGlobalTransaction().globalId());
}
throw new RetryPrepareException();
} else if (command.getTopologyId() > topologyId) {
throw new IllegalStateException("This should never happen");
}
return invokeNext(ctx, command);
}
private Object localPrepare(TxInvocationContext ctx, PrepareCommand command) throws Throwable {
command.setTopologyId(currentTopologyId());
if (trace) {
log.tracef("Local transaction received %s. setting topology Id to %s",
command.getGlobalTransaction().globalId(), command.getTopologyId());
}
return invokeNextAndExceptionally(ctx, command, handleLocalPrepareReturn);
}
private Object handleLocalPrepareReturn(InvocationContext ctx, VisitableCommand command, Throwable t)
throws Throwable {
assert t != null;
// If we receive a RetryPrepareException it was because the prepare was delivered during a state transfer.
// Remember that the REBALANCE_START and CH_UPDATE are totally ordered with the prepares and the
// prepares are unblocked after the rebalance has finished.
boolean needsToPrepare = needsToRePrepare(t);
PrepareCommand prepareCommand = (PrepareCommand) command;
if (log.isDebugEnabled()) {
log.tracef("Exception caught while preparing transaction %s (cause = %s). Needs to retransmit? %s",
prepareCommand.getGlobalTransaction().globalId(), t.getCause(), needsToPrepare);
}
if (!needsToPrepare) {
throw t;
} else {
logRetry(command);
prepareCommand.setTopologyId(currentTopologyId());
return invokeNextAndExceptionally(ctx, command, handleLocalPrepareReturn);
}
}
private boolean needsToRePrepare(Throwable throwable) {
return throwable instanceof RemoteException && throwable.getCause() instanceof RetryPrepareException;
}
@Override
protected Log getLog() {
return log;
}
}