package org.infinispan.remoting.inboundhandler; import static org.infinispan.factories.KnownComponentNames.REMOTE_COMMAND_EXECUTOR; import java.util.concurrent.CompletableFuture; import org.infinispan.commands.CancellableCommand; import org.infinispan.commands.CancellationService; import org.infinispan.commands.ReplicableCommand; import org.infinispan.commands.TopologyAffectedCommand; import org.infinispan.commands.remote.CacheRpcCommand; import org.infinispan.commands.remote.ClusteredGetAllCommand; import org.infinispan.commands.remote.ClusteredGetCommand; import org.infinispan.commands.remote.SingleRpcCommand; import org.infinispan.commons.CacheException; import org.infinispan.factories.annotations.ComponentName; import org.infinispan.factories.annotations.Inject; import org.infinispan.factories.annotations.Stop; import org.infinispan.remoting.inboundhandler.action.ReadyAction; import org.infinispan.remoting.responses.ExceptionResponse; import org.infinispan.remoting.responses.Response; import org.infinispan.remoting.responses.ResponseGenerator; import org.infinispan.statetransfer.OutdatedTopologyException; import org.infinispan.statetransfer.StateTransferLock; import org.infinispan.statetransfer.StateTransferManager; import org.infinispan.util.concurrent.BlockingRunnable; import org.infinispan.util.concurrent.BlockingTaskAwareExecutorService; import org.infinispan.util.concurrent.CompletableFutures; import org.infinispan.util.logging.Log; /** * Implementation with the default handling methods and utilities methods. * * @author Pedro Ruivo * @since 7.1 */ public abstract class BasePerCacheInboundInvocationHandler implements PerCacheInboundInvocationHandler { private static final int NO_TOPOLOGY_COMMAND = Integer.MIN_VALUE; protected BlockingTaskAwareExecutorService remoteCommandsExecutor; private StateTransferLock stateTransferLock; protected StateTransferManager stateTransferManager; private ResponseGenerator responseGenerator; private CancellationService cancellationService; private volatile boolean stopped = false; private static int extractCommandTopologyId(SingleRpcCommand command) { ReplicableCommand innerCmd = command.getCommand(); if (innerCmd instanceof TopologyAffectedCommand) { return ((TopologyAffectedCommand) innerCmd).getTopologyId(); } return NO_TOPOLOGY_COMMAND; } static int extractCommandTopologyId(CacheRpcCommand command) { switch (command.getCommandId()) { case SingleRpcCommand.COMMAND_ID: return extractCommandTopologyId((SingleRpcCommand) command); case ClusteredGetCommand.COMMAND_ID: case ClusteredGetAllCommand.COMMAND_ID: // These commands are topology aware but we don't block them here - topologyId logic // is handled in StateTransferInterceptor return NO_TOPOLOGY_COMMAND; default: if (command instanceof TopologyAffectedCommand) { return ((TopologyAffectedCommand) command).getTopologyId(); } } return NO_TOPOLOGY_COMMAND; } @Inject public void injectDependencies(@ComponentName(REMOTE_COMMAND_EXECUTOR) BlockingTaskAwareExecutorService remoteCommandsExecutor, ResponseGenerator responseGenerator, CancellationService cancellationService, StateTransferLock stateTransferLock, StateTransferManager stateTransferManager) { this.remoteCommandsExecutor = remoteCommandsExecutor; this.responseGenerator = responseGenerator; this.cancellationService = cancellationService; this.stateTransferLock = stateTransferLock; this.stateTransferManager = stateTransferManager; } @Stop public void stop() { this.stopped = true; } public boolean isStopped() { return stopped; } final CompletableFuture<Response> invokeCommand(CacheRpcCommand cmd) throws Throwable { try { if (isTraceEnabled()) { getLog().tracef("Calling perform() on %s", cmd); } if (cmd instanceof CancellableCommand) { cancellationService.register(Thread.currentThread(), ((CancellableCommand) cmd).getUUID()); } CompletableFuture<Object> future = cmd.invokeAsync(); return future.handle((rv, throwable) -> { if (cmd instanceof CancellableCommand) { cancellationService.unregister(((CancellableCommand) cmd).getUUID()); } CompletableFutures.rethrowException(throwable); return responseGenerator.getResponse(cmd, rv); }); } catch (Throwable throwable) { if (cmd instanceof CancellableCommand) { cancellationService.unregister(((CancellableCommand) cmd).getUUID()); } throw throwable; } } final StateTransferLock getStateTransferLock() { return stateTransferLock; } final ExceptionResponse exceptionHandlingCommand(CacheRpcCommand command, Throwable throwable) { getLog().exceptionHandlingCommand(command, throwable); if (throwable instanceof Exception) { return new ExceptionResponse(((Exception) throwable)); } else { return new ExceptionResponse(new CacheException("Problems invoking command.", throwable)); } } final ExceptionResponse outdatedTopology(OutdatedTopologyException exception) { getLog().tracef("Topology changed, notifying the originator: %s", exception); return new ExceptionResponse(exception); } final ExceptionResponse interruptedException(CacheRpcCommand command) { getLog().shutdownHandlingCommand(command); return new ExceptionResponse(new CacheException("Cache is shutting down")); } final void unexpectedDeliverMode(ReplicableCommand command, DeliverOrder deliverOrder) { throw new IllegalArgumentException(String.format("Unexpected deliver mode %s for command%s", deliverOrder, command)); } final void handleRunnable(BlockingRunnable runnable, boolean onExecutorService) { if (onExecutorService) { remoteCommandsExecutor.execute(runnable); } else { runnable.run(); } } public final boolean isCommandSentBeforeFirstTopology(int commandTopologyId) { if (0 <= commandTopologyId && commandTopologyId < stateTransferManager.getFirstTopologyAsMember()) { if (isTraceEnabled()) { getLog().tracef("Ignoring command sent before the local node was a member (command topology id is %d)", commandTopologyId); } return true; } return false; } final BlockingRunnable createDefaultRunnable(CacheRpcCommand command, Reply reply, int commandTopologyId, boolean waitTransactionalData, boolean onExecutorService, boolean sync) { return new DefaultTopologyRunnable(this, command, reply, TopologyMode.create(onExecutorService, waitTransactionalData), commandTopologyId, sync); } final BlockingRunnable createDefaultRunnable(final CacheRpcCommand command, final Reply reply, final int commandTopologyId, TopologyMode topologyMode, boolean sync) { return new DefaultTopologyRunnable(this, command, reply, topologyMode, commandTopologyId, sync); } protected abstract Log getLog(); protected abstract boolean isTraceEnabled(); final boolean executeOnExecutorService(DeliverOrder order, CacheRpcCommand command) { return !order.preserveOrder() && command.canBlock(); } final BlockingRunnable createReadyActionRunnable(CacheRpcCommand command, Reply reply, int commandTopologyId, boolean sync, ReadyAction readyAction) { if (readyAction != null) { return createNonNullReadyActionRunnable(command, reply, commandTopologyId, sync, readyAction); } else { return new DefaultTopologyRunnable(this, command, reply, TopologyMode.READY_TX_DATA, commandTopologyId, sync); } } final BlockingRunnable createNonNullReadyActionRunnable(CacheRpcCommand command, Reply reply, int commandTopologyId, boolean sync, ReadyAction readyAction) { readyAction.addListener(remoteCommandsExecutor::checkForReadyTasks); return new DefaultTopologyRunnable(this, command, reply, TopologyMode.READY_TX_DATA, commandTopologyId, sync) { @Override public boolean isReady() { return super.isReady() && readyAction.isReady(); } @Override protected void onException(Throwable throwable) { super.onException(throwable); readyAction.onException(); } @Override protected void onFinally() { super.onFinally(); readyAction.onFinally(); } }; } }