package org.infinispan.interceptors.impl;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.infinispan.commands.FlagAffectedCommand;
import org.infinispan.commands.tx.PrepareCommand;
import org.infinispan.configuration.cache.ClusteringConfiguration;
import org.infinispan.context.Flag;
import org.infinispan.context.impl.FlagBitSets;
import org.infinispan.context.impl.LocalTxInvocationContext;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.annotations.Start;
import org.infinispan.interceptors.DDAsyncInterceptor;
import org.infinispan.remoting.inboundhandler.DeliverOrder;
import org.infinispan.remoting.responses.ExceptionResponse;
import org.infinispan.remoting.responses.Response;
import org.infinispan.remoting.rpc.ResponseFilter;
import org.infinispan.remoting.rpc.ResponseMode;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.rpc.RpcOptions;
import org.infinispan.remoting.rpc.RpcOptionsBuilder;
import org.infinispan.remoting.transport.Address;
import org.infinispan.transaction.impl.LocalTransaction;
import org.infinispan.util.concurrent.CompletableFutures;
import org.infinispan.util.logging.Log;
/**
* Acts as a base for all RPC calls
*
* @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
* @author Mircea.Markus@jboss.com
* @since 9.0
*/
public abstract class BaseRpcInterceptor extends DDAsyncInterceptor {
private final static ResponseFilter SUCCESSFUL_OR_EXCEPTIONAL = new ResponseFilter() {
@Override
public boolean isAcceptable(Response response, Address sender) {
return response.isSuccessful() || response instanceof ExceptionResponse;
}
@Override
public boolean needMoreResponses() {
return true;
}
};
protected final boolean trace = getLog().isTraceEnabled();
protected RpcManager rpcManager;
protected boolean defaultSynchronous;
protected volatile RpcOptions singleTargetStaggeredOptions;
protected volatile RpcOptions multiTargetStaggeredOptions;
protected volatile RpcOptions defaultSyncOptions;
protected volatile RpcOptions syncIgnoreLeavers;
protected RpcOptions defaultAsyncOptions;
protected abstract Log getLog();
@Inject
public void inject(RpcManager rpcManager) {
this.rpcManager = rpcManager;
}
@Start
public void init() {
defaultSynchronous = cacheConfiguration.clustering().cacheMode().isSynchronous();
cacheConfiguration.clustering().attributes().attribute(ClusteringConfiguration.REMOTE_TIMEOUT)
.addListener(((a, o) -> initRpcOptions()));
initRpcOptions();
// async options don't depend on the remote timeout
defaultAsyncOptions = rpcManager.getDefaultRpcOptions(false);
}
private void initRpcOptions() {
singleTargetStaggeredOptions = rpcManager.getRpcOptionsBuilder(ResponseMode.SYNCHRONOUS_IGNORE_LEAVERS).build();
multiTargetStaggeredOptions = rpcManager.getRpcOptionsBuilder(ResponseMode.WAIT_FOR_VALID_RESPONSE)
.responseFilter(SUCCESSFUL_OR_EXCEPTIONAL).build();
defaultSyncOptions = rpcManager.getDefaultRpcOptions(true);
syncIgnoreLeavers = rpcManager.getRpcOptionsBuilder(ResponseMode.SYNCHRONOUS_IGNORE_LEAVERS, DeliverOrder.NONE).build();
}
protected RpcOptions getStaggeredOptions(int numTargets) {
// TODO: handle better when dropping MessageDispatcher-based RPC
// This is somwhat a hack to the way staggered reads are implemented: what we intend is to keep
// the sender waiting until it receives successful response. If it did not receive any successful
// response, just return those unsuccessful ones.
// Staggered gets use the filter but add non-accepted responses to Responses anyway. JGroupsTransport
// later won't filter those unaccepted values and we'll get the unsuccesful ones, too.
// When there's only single target, the processing uses GroupRequest and that wouldn't return
// filtered values add all, therefore we'll omit the filter in there.
// Regrettably this cannot be handled properly by current filtering options.
return numTargets == 1 ? singleTargetStaggeredOptions : multiTargetStaggeredOptions;
}
protected final boolean isSynchronous(FlagAffectedCommand command) {
if (command.hasAnyFlag(FlagBitSets.FORCE_SYNCHRONOUS))
return true;
else if (command.hasAnyFlag(FlagBitSets.FORCE_ASYNCHRONOUS))
return false;
return defaultSynchronous;
}
protected final boolean isLocalModeForced(FlagAffectedCommand command) {
if (command.hasAnyFlag(FlagBitSets.CACHE_MODE_LOCAL)) {
if (trace) getLog().trace("LOCAL mode forced on invocation. Suppressing clustered events.");
return true;
}
return false;
}
protected boolean shouldInvokeRemoteTxCommand(TxInvocationContext ctx) {
if (!ctx.isOriginLocal()) {
return false;
}
// Skip the remote invocation if this is a state transfer transaction
LocalTxInvocationContext localCtx = (LocalTxInvocationContext) ctx;
if (localCtx.getCacheTransaction().getStateTransferFlag() == Flag.PUT_FOR_STATE_TRANSFER) {
return false;
}
// just testing for empty modifications isn't enough - the Lock API may acquire locks on keys but won't
// register a Modification. See ISPN-711.
boolean shouldInvokeRemotely = ctx.hasModifications() || !localCtx.getRemoteLocksAcquired().isEmpty() ||
localCtx.getCacheTransaction().getTopologyId() != rpcManager.getTopologyId();
if (trace) {
getLog().tracef("Should invoke remotely? %b. hasModifications=%b, hasRemoteLocksAcquired=%b",
shouldInvokeRemotely, ctx.hasModifications(), !localCtx.getRemoteLocksAcquired().isEmpty());
}
return shouldInvokeRemotely;
}
protected static void transactionRemotelyPrepared(TxInvocationContext ctx) {
if (ctx.isOriginLocal()) {
((LocalTransaction)ctx.getCacheTransaction()).markPrepareSent();
}
}
protected static void totalOrderTxCommit(TxInvocationContext ctx) {
if (ctx.isOriginLocal()) {
((LocalTransaction)ctx.getCacheTransaction()).markCommitOrRollbackSent();
}
}
protected static void totalOrderTxRollback(TxInvocationContext ctx) {
if (ctx.isOriginLocal()) {
((LocalTransaction)ctx.getCacheTransaction()).markCommitOrRollbackSent();
}
}
protected static boolean shouldTotalOrderRollbackBeInvokedRemotely(TxInvocationContext ctx) {
return ctx.isOriginLocal() && ((LocalTransaction)ctx.getCacheTransaction()).isPrepareSent()
&& !((LocalTransaction)ctx.getCacheTransaction()).isCommitOrRollbackSent();
}
protected CompletableFuture<Object> totalOrderPrepare(TxInvocationContext<?> ctx, PrepareCommand command,
Collection<Address> recipients) {
try {
Set<Address> realRecipients = null;
if (recipients != null) {
realRecipients = new HashSet<>(recipients);
realRecipients.add(rpcManager.getAddress());
}
CompletableFuture<Map<Address, Response>> remoteInvocation =
internalTotalOrderPrepare(realRecipients, command);
return remoteInvocation.handle((responses, t) -> {
transactionRemotelyPrepared(ctx);
CompletableFutures.rethrowException(t);
return null;
});
} catch (Throwable t) {
transactionRemotelyPrepared(ctx);
throw t;
}
}
private CompletableFuture<Map<Address, Response>> internalTotalOrderPrepare(Collection<Address> recipients, PrepareCommand prepareCommand) {
RpcOptionsBuilder builder = rpcManager.getRpcOptionsBuilder(ResponseMode.SYNCHRONOUS_IGNORE_LEAVERS, DeliverOrder.TOTAL);
return rpcManager.invokeRemotelyAsync(recipients, prepareCommand, builder.build());
}
}