
org.infinispan.server.hotrod.tx.operation.BaseCompleteTransactionOperation Maven / Gradle / Ivy
package org.infinispan.server.hotrod.tx.operation;
import static org.infinispan.remoting.transport.impl.VoidResponseCollector.validOnly;
import java.util.Collection;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import javax.security.auth.Subject;
import javax.transaction.xa.XAException;
import org.infinispan.AdvancedCache;
import org.infinispan.commands.CommandsFactory;
import org.infinispan.commands.remote.CacheRpcCommand;
import org.infinispan.commands.tx.TransactionBoundaryCommand;
import org.infinispan.commons.tx.XidImpl;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.factories.GlobalComponentRegistry;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.security.actions.SecurityActions;
import org.infinispan.server.hotrod.HotRodHeader;
import org.infinispan.server.hotrod.HotRodServer;
import org.infinispan.server.hotrod.logging.Log;
import org.infinispan.server.hotrod.tx.table.CacheNameCollector;
import org.infinispan.server.hotrod.tx.table.CacheXid;
import org.infinispan.server.hotrod.tx.table.GlobalTxTable;
import org.infinispan.server.hotrod.tx.table.TxState;
import org.infinispan.util.ByteString;
import org.infinispan.commons.util.concurrent.AggregateCompletionStage;
import org.infinispan.util.concurrent.BlockingManager;
import org.infinispan.commons.util.concurrent.CompletionStages;
import org.infinispan.util.logging.LogFactory;
/**
* A base class to complete a transaction (commit or rollback).
*
* This class implements {@link Runnable} in order to be executed in a {@link ExecutorService}.
*
* A transaction completion occurs in the following steps:
*
*
* - Search and collects all the cache involved in the transaction
* - Finds the transaction originator (i.e. the node that replayed the transaction)
* - If it is an originator of one or more caches, it completes the transaction in another thread
* - If the originator is not in the topology, it completes the transaction by broadcasting the respective command
* - If the originator is in the topology, it forwards the completion request
*
*
* @author Pedro Ruivo
* @since 9.4
*/
abstract class BaseCompleteTransactionOperation implements CacheNameCollector, Runnable {
private static final Log log = LogFactory.getLog(BaseCompleteTransactionOperation.class, Log.class);
final XidImpl xid;
final GlobalTxTable globalTxTable;
final HotRodHeader header;
final BiConsumer reply;
private final HotRodServer server;
private final Subject subject;
final Collection cacheNames = new ConcurrentLinkedQueue<>();
final BlockingManager blockingManager;
private final AtomicInteger expectedCaches = new AtomicInteger();
volatile boolean hasErrors = false;
volatile boolean hasCommits = false;
volatile boolean hasRollbacks = false;
BaseCompleteTransactionOperation(HotRodHeader header, HotRodServer server, Subject subject, XidImpl xid,
BiConsumer reply) {
GlobalComponentRegistry gcr = SecurityActions.getGlobalComponentRegistry(server.getCacheManager());
globalTxTable = gcr.getComponent(GlobalTxTable.class);
blockingManager = gcr.getComponent(BlockingManager.class);
this.header = header;
this.server = server;
this.subject = subject;
this.xid = xid;
this.reply = reply;
}
@Override
public final void expectedSize(int size) {
expectedCaches.set(size);
}
@Override
public final void noTransactionFound() {
if (log.isTraceEnabled()) {
log.tracef("[%s] No caches found.", xid);
}
//no transactions
reply.accept(header, XAException.XAER_NOTA);
}
/**
* It returns the handler to handle the replies from remote nodes or from a forward request.
*
* It is invoked when this node isn't the originator for a cache. If the originator isn't in the view, it handles the
* replies from the broadcast commit or rollback command. If the originator is in the view, it handles the reply from
* the forward command.
*
* @return The {@link BiFunction} to handle the remote replies.
*/
abstract BiFunction handler();
/**
* When all caches are completed, this method is invoked to reply to the Hot Rod client.
*/
abstract void sendReply();
/**
* When the originator is not in the cache topology, this method builds the command to broadcast to all the nodes.
*
* @return The completion command to broadcast to nodes in the cluster.
*/
abstract C1 buildRemoteCommand(Configuration configuration, CommandsFactory commandsFactory,
TxState state);
/**
* When this node isn't the originator, it builds the forward command to send to the originator.
*
* @return The forward command to send to the originator.
*/
abstract C2 buildForwardCommand(ByteString cacheName, long timeout);
/**
* When this node is the originator, this method is invoked to complete the transaction in the specific cache.
*/
abstract void asyncCompleteLocalTransaction(AdvancedCache, ?> cache, long timeout, AggregateCompletionStage stageCollector);
/**
* Invoked every time a cache is found to be involved in a transaction.
*/
void notifyCacheCollected() {
int result = expectedCaches.decrementAndGet();
if (log.isTraceEnabled()) {
log.tracef("[%s] Cache collected. Missing=%s.", xid, result);
}
if (result == 0) {
onCachesCollected();
}
}
/**
* Invoked when all caches are ready to complete the transaction.
*/
private void onCachesCollected() {
if (log.isTraceEnabled()) {
log.tracef("[%s] All caches collected: %s", xid, cacheNames);
}
int size = cacheNames.size();
if (size == 0) {
//it can happen if all caches either commit or thrown an exception
sendReply();
return;
}
AggregateCompletionStage aggregateCompletionStage = CompletionStages.aggregateCompletionStage();
for (ByteString cacheName : cacheNames) {
try {
completeCache(cacheName, aggregateCompletionStage);
} catch (Throwable t) {
if (log.isTraceEnabled()) {
log.tracef(t, "[%s] Error while trying to complete transaction for cache %s", xid, cacheName);
}
hasErrors = true;
}
}
aggregateCompletionStage.freeze().thenRun(this::sendReply);
}
/**
* Completes the transaction for a specific cache.
*/
private void completeCache(ByteString cacheName, AggregateCompletionStage stageCollector) throws Throwable {
TxState state = globalTxTable.getState(new CacheXid(cacheName, xid));
HotRodServer.ExtendedCacheInfo cacheInfo =
server.getCacheInfo(cacheName.toString(), header.getVersion(), header.getMessageId(), true);
AdvancedCache, ?> cache = server.cache(cacheInfo, header, subject);
var distributionManager = cache.getDistributionManager();
var topology = distributionManager == null ? null : distributionManager.getCacheTopology();
if (topology == null || topology.getLocalAddress().equals(state.getOriginator())) {
if (log.isTraceEnabled()) {
log.tracef("[%s] Completing local executed transaction.", xid);
}
asyncCompleteLocalTransaction(cache, state.getTimeout(), stageCollector);
} else if (topology.getMembers().contains(state.getOriginator())) {
if (log.isTraceEnabled()) {
log.tracef("[%s] Forward remotely executed transaction to %s.", xid, state.getOriginator());
}
forwardCompleteCommand(cacheName, state, cache.getRpcManager(), stageCollector);
} else {
if (log.isTraceEnabled()) {
log.tracef("[%s] Originator, %s, left the cluster.", xid, state.getOriginator());
}
completeWithRemoteCommand(cache, state, cache.getRpcManager(), topology.getTopologyId(), stageCollector);
}
}
/**
* Completes the transaction in the cache when the originator no longer belongs to the cache topology.
*/
private void completeWithRemoteCommand(AdvancedCache, ?> cache, TxState state, RpcManager rpcManager,
int topologyId, AggregateCompletionStage stageCollector) throws Throwable {
var registry = SecurityActions.getCacheComponentRegistry(cache);
var commandsFactory = registry.getCommandsFactory();
var command = buildRemoteCommand(cache.getCacheConfiguration(), commandsFactory, state);
command.setTopologyId(topologyId);
stageCollector.dependsOn(rpcManager.invokeCommandOnAll(command, validOnly(), rpcManager.getSyncRpcOptions())
.handle(handler()));
stageCollector.dependsOn(command.invokeAsync(registry).handle(handler()));
}
/**
* Completes the transaction in the cache when the originator is still in the cache topology.
*/
private void forwardCompleteCommand(ByteString cacheName, TxState state, RpcManager rpcManager, AggregateCompletionStage stageCollector) {
//TODO what if the originator crashes in the meanwhile?
//actually, the reaper would rollback the transaction later...
var originator = state.getOriginator();
var command = buildForwardCommand(cacheName, state.getTimeout());
stageCollector.dependsOn(rpcManager.invokeCommand(originator, command, validOnly(), rpcManager.getSyncRpcOptions())
.handle(handler()));
}
}