org.infinispan.interceptors.impl.TxInterceptor Maven / Gradle / Ivy
package org.infinispan.interceptors.impl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.atomic.AtomicLong;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import org.infinispan.Cache;
import org.infinispan.CacheSet;
import org.infinispan.cache.impl.Caches;
import org.infinispan.commands.CommandsFactory;
import org.infinispan.commands.FlagAffectedCommand;
import org.infinispan.commands.VisitableCommand;
import org.infinispan.commands.control.LockControlCommand;
import org.infinispan.commands.functional.ReadOnlyKeyCommand;
import org.infinispan.commands.functional.ReadOnlyManyCommand;
import org.infinispan.commands.functional.ReadWriteKeyCommand;
import org.infinispan.commands.functional.ReadWriteKeyValueCommand;
import org.infinispan.commands.functional.ReadWriteManyCommand;
import org.infinispan.commands.functional.ReadWriteManyEntriesCommand;
import org.infinispan.commands.functional.WriteOnlyKeyCommand;
import org.infinispan.commands.functional.WriteOnlyKeyValueCommand;
import org.infinispan.commands.functional.WriteOnlyManyCommand;
import org.infinispan.commands.functional.WriteOnlyManyEntriesCommand;
import org.infinispan.commands.read.EntrySetCommand;
import org.infinispan.commands.read.GetAllCommand;
import org.infinispan.commands.read.GetCacheEntryCommand;
import org.infinispan.commands.read.GetKeyValueCommand;
import org.infinispan.commands.read.KeySetCommand;
import org.infinispan.commands.read.SizeCommand;
import org.infinispan.commands.tx.AbstractTransactionBoundaryCommand;
import org.infinispan.commands.tx.CommitCommand;
import org.infinispan.commands.tx.PrepareCommand;
import org.infinispan.commands.tx.RollbackCommand;
import org.infinispan.commands.write.ApplyDeltaCommand;
import org.infinispan.commands.write.ClearCommand;
import org.infinispan.commands.write.InvalidateCommand;
import org.infinispan.commands.write.PutKeyValueCommand;
import org.infinispan.commands.write.PutMapCommand;
import org.infinispan.commands.write.RemoveCommand;
import org.infinispan.commands.write.ReplaceCommand;
import org.infinispan.commands.write.WriteCommand;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.commons.util.CloseableSpliterator;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.Configurations;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.context.Flag;
import org.infinispan.context.InvocationContext;
import org.infinispan.context.impl.FlagBitSets;
import org.infinispan.context.impl.RemoteTxInvocationContext;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.interceptors.BasicInvocationStage;
import org.infinispan.interceptors.DDAsyncInterceptor;
import org.infinispan.interceptors.InvocationStage;
import org.infinispan.jmx.JmxStatisticsExposer;
import org.infinispan.jmx.annotations.DataType;
import org.infinispan.jmx.annotations.DisplayType;
import org.infinispan.jmx.annotations.MBean;
import org.infinispan.jmx.annotations.ManagedAttribute;
import org.infinispan.jmx.annotations.ManagedOperation;
import org.infinispan.jmx.annotations.MeasurementType;
import org.infinispan.partitionhandling.impl.PartitionHandlingManager;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.transport.Address;
import org.infinispan.statetransfer.OutdatedTopologyException;
import org.infinispan.stream.impl.interceptor.AbstractDelegatingEntryCacheSet;
import org.infinispan.stream.impl.interceptor.AbstractDelegatingKeyCacheSet;
import org.infinispan.stream.impl.spliterators.IteratorAsSpliterator;
import org.infinispan.transaction.impl.LocalTransaction;
import org.infinispan.transaction.impl.RemoteTransaction;
import org.infinispan.transaction.impl.TransactionTable;
import org.infinispan.transaction.xa.GlobalTransaction;
import org.infinispan.transaction.xa.recovery.RecoverableTransactionIdentifier;
import org.infinispan.transaction.xa.recovery.RecoveryManager;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* Interceptor in charge with handling transaction related operations, e.g enlisting cache as an transaction
* participant, propagating remotely initiated changes.
*
* @author Manik Surtani ([email protected])
* @author [email protected]
* @see org.infinispan.transaction.xa.TransactionXaAdapter
* @since 9.0
*/
@MBean(objectName = "Transactions", description = "Component that manages the cache's participation in JTA transactions.")
public class TxInterceptor extends DDAsyncInterceptor implements JmxStatisticsExposer {
private static final Log log = LogFactory.getLog(TxInterceptor.class);
private static final boolean trace = log.isTraceEnabled();
private final AtomicLong prepares = new AtomicLong(0);
private final AtomicLong commits = new AtomicLong(0);
private final AtomicLong rollbacks = new AtomicLong(0);
private RpcManager rpcManager;
private CommandsFactory commandsFactory;
private Cache cache;
private RecoveryManager recoveryManager;
private TransactionTable txTable;
private PartitionHandlingManager partitionHandlingManager;
private boolean isTotalOrder;
private boolean useOnePhaseForAutoCommitTx;
private boolean useVersioning;
private boolean statisticsEnabled;
@Inject
public void init(TransactionTable txTable, Configuration configuration, RpcManager rpcManager,
RecoveryManager recoveryManager, CommandsFactory commandsFactory, Cache cache,
PartitionHandlingManager partitionHandlingManager) {
this.cacheConfiguration = configuration;
this.txTable = txTable;
this.rpcManager = rpcManager;
this.recoveryManager = recoveryManager;
this.commandsFactory = commandsFactory;
this.cache = cache;
this.partitionHandlingManager = partitionHandlingManager;
statisticsEnabled = cacheConfiguration.jmxStatistics().enabled();
isTotalOrder = configuration.transaction().transactionProtocol().isTotalOrder();
useOnePhaseForAutoCommitTx = cacheConfiguration.transaction().use1PcForAutoCommitTransactions();
useVersioning = Configurations.isVersioningEnabled(configuration);
}
@Override
public BasicInvocationStage visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable {
return handlePrepareCommand(ctx, command);
}
private InvocationStage handlePrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable {
// Debugging for ISPN-5379
ctx.getCacheTransaction().freezeModifications();
//if it is remote and 2PC then first log the tx only after replying mods
if (this.statisticsEnabled) prepares.incrementAndGet();
if (!ctx.isOriginLocal()) {
((RemoteTransaction) ctx.getCacheTransaction()).setLookedUpEntriesTopology(command.getTopologyId());
return invokeNext(ctx, command).compose((stage, rCtx, rCommand, rv, t) -> {
if (!rCtx.isOriginLocal()) {
return verifyRemoteTransaction(stage, (RemoteTxInvocationContext) rCtx,
(AbstractTransactionBoundaryCommand) rCommand);
}
return stage;
}).thenAccept((rCtx, rCommand, rv) -> {
PrepareCommand prepareCommand = (PrepareCommand) rCommand;
if (prepareCommand.isOnePhaseCommit()) {
txTable.remoteTransactionCommitted(prepareCommand.getGlobalTransaction(), true);
} else {
txTable.remoteTransactionPrepared(prepareCommand.getGlobalTransaction());
}
});
} else {
if (ctx.getCacheTransaction().hasModification(ClearCommand.class)) {
throw new IllegalStateException("No ClearCommand is allowed in Transaction.");
}
return invokeNext(ctx, command);
}
}
@Override
public BasicInvocationStage visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable {
// TODO The local origin check is needed for CommitFailsTest, but it doesn't appear correct to roll back an in-doubt tx
if (!ctx.isOriginLocal()) {
GlobalTransaction gtx = ctx.getGlobalTransaction();
if (txTable.isTransactionCompleted(gtx)) {
if (trace) log.tracef("Transaction %s already completed, skipping commit", gtx);
return returnWith(null);
}
if (!isTotalOrder) {
InvocationStage replayStage = replayRemoteTransactionIfNeeded((RemoteTxInvocationContext) ctx,
command.getTopologyId());
if (replayStage != null) {
return replayStage.compose(
(stage, rCtx, rCommand, rv, t) -> finishCommit((TxInvocationContext>) rCtx, command));
} else {
return finishCommit(ctx, command);
}
}
}
return finishCommit(ctx, command);
}
private InvocationStage finishCommit(TxInvocationContext> ctx, VisitableCommand command) {
GlobalTransaction gtx = ctx.getGlobalTransaction();
if (this.statisticsEnabled) commits.incrementAndGet();
return invokeNext(ctx, command).thenAccept((rCtx, rCommand, rv) -> {
if (!rCtx.isOriginLocal() || isTotalOrder) {
txTable.remoteTransactionCommitted(gtx, false);
}
});
}
@Override
public BasicInvocationStage visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command) throws Throwable {
if (this.statisticsEnabled) rollbacks.incrementAndGet();
// The transaction was marked as completed in RollbackCommand.prepare()
if (!ctx.isOriginLocal() || isTotalOrder) {
txTable.remoteTransactionRollback(command.getGlobalTransaction());
}
return invokeNext(ctx, command).handle((rCtx, rCommand, rv, t) -> {
//for tx that rollback we do not send a TxCompletionNotification, so we should cleanup
// the recovery info here
if (recoveryManager != null) {
GlobalTransaction gtx = ((RollbackCommand) rCommand).getGlobalTransaction();
recoveryManager.removeRecoveryInformation(((RecoverableTransactionIdentifier) gtx).getXid());
}
});
}
@Override
public BasicInvocationStage visitLockControlCommand(TxInvocationContext ctx, LockControlCommand command)
throws Throwable {
enlistIfNeeded(ctx);
if (ctx.isOriginLocal()) {
command.setGlobalTransaction(ctx.getGlobalTransaction());
}
return invokeNext(ctx, command).compose((stage, rCtx, rCommand, rv, t) -> {
if (!rCtx.isOriginLocal()) {
return verifyRemoteTransaction(stage, (RemoteTxInvocationContext) rCtx,
(AbstractTransactionBoundaryCommand) rCommand);
}
return stage;
});
}
@Override
public BasicInvocationStage visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command)
throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitApplyDeltaCommand(InvocationContext ctx, ApplyDeltaCommand command)
throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable {
return invokeNext(ctx, command);
}
@Override
public BasicInvocationStage visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitSizeCommand(InvocationContext ctx, SizeCommand command) throws Throwable {
enlistIfNeeded(ctx);
return invokeNext(ctx, command);
}
@Override
public BasicInvocationStage visitKeySetCommand(InvocationContext ctx, KeySetCommand command) throws Throwable {
enlistIfNeeded(ctx);
return invokeNext(ctx, command).thenApply((rCtx, rCommand, rv) -> {
if (rCtx.isInTxScope()) {
CacheSet set = (CacheSet) rv;
return new AbstractDelegatingKeyCacheSet(Caches.getCacheWithFlags(cache, (FlagAffectedCommand) rCommand), set) {
@Override
public CloseableIterator iterator() {
return new TransactionAwareKeyCloseableIterator<>(super.iterator(),
(TxInvocationContext) rCtx, cache);
}
@Override
public CloseableSpliterator spliterator() {
Spliterator parentSpliterator = super.spliterator();
long estimateSize =
parentSpliterator.estimateSize() + rCtx.getLookedUpEntries().size();
// This is an overestimate for size if we have looked up entries that don't map to
// this node
return new IteratorAsSpliterator.Builder<>(iterator())
.setEstimateRemaining(estimateSize < 0L ? Long.MAX_VALUE : estimateSize)
.setCharacteristics(Spliterator.CONCURRENT | Spliterator.DISTINCT |
Spliterator.NONNULL).get();
}
@Override
public int size() {
long size = stream().count();
if (size > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int) size;
}
};
}
return rv;
});
}
@Override
public BasicInvocationStage visitEntrySetCommand(InvocationContext ctx, EntrySetCommand command) throws Throwable {
enlistIfNeeded(ctx);
return invokeNext(ctx, command).thenApply((rCtx, rCommand, rv) -> {
if (rCtx.isInTxScope()) {
CacheSet> set = (CacheSet>) rv;
return new AbstractDelegatingEntryCacheSet(
Caches.getCacheWithFlags(cache, (FlagAffectedCommand) rCommand), set) {
@Override
public CloseableIterator> iterator() {
return new TransactionAwareEntryCloseableIterator<>(super.iterator(),
(TxInvocationContext) rCtx, cache);
}
@Override
public CloseableSpliterator> spliterator() {
Spliterator> parentSpliterator = super.spliterator();
long estimateSize =
parentSpliterator.estimateSize() + rCtx.getLookedUpEntries().size();
// This is an overestimate for size if we have looked up entries that don't map to
// this node
return new IteratorAsSpliterator.Builder<>(iterator())
.setEstimateRemaining(estimateSize < 0L ? Long.MAX_VALUE : estimateSize)
.setCharacteristics(Spliterator.CONCURRENT | Spliterator.DISTINCT |
Spliterator.NONNULL).get();
}
@Override
public int size() {
long size = stream().count();
if (size > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int) size;
}
};
}
return rv;
});
}
@Override
public BasicInvocationStage visitInvalidateCommand(InvocationContext ctx, InvalidateCommand invalidateCommand)
throws Throwable {
return handleWriteCommand(ctx, invalidateCommand);
}
@Override
public BasicInvocationStage visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command)
throws Throwable {
enlistIfNeeded(ctx);
return invokeNext(ctx, command);
}
@Override
public final BasicInvocationStage visitGetCacheEntryCommand(InvocationContext ctx, GetCacheEntryCommand command)
throws Throwable {
enlistIfNeeded(ctx);
return invokeNext(ctx, command);
}
@Override
public BasicInvocationStage visitGetAllCommand(InvocationContext ctx, GetAllCommand command) throws Throwable {
enlistIfNeeded(ctx);
return invokeNext(ctx, command);
}
private void enlistIfNeeded(InvocationContext ctx) throws SystemException {
if (shouldEnlist(ctx)) {
enlist((TxInvocationContext) ctx);
}
}
@Override
public BasicInvocationStage visitReadOnlyKeyCommand(InvocationContext ctx, ReadOnlyKeyCommand command) throws Throwable {
enlistIfNeeded(ctx);
return invokeNext(ctx, command);
}
@Override
public BasicInvocationStage visitReadOnlyManyCommand(InvocationContext ctx, ReadOnlyManyCommand command) throws Throwable {
enlistIfNeeded(ctx);
return invokeNext(ctx, command);
}
@Override
public BasicInvocationStage visitWriteOnlyKeyCommand(InvocationContext ctx, WriteOnlyKeyCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitReadWriteKeyValueCommand(InvocationContext ctx, ReadWriteKeyValueCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitReadWriteKeyCommand(InvocationContext ctx, ReadWriteKeyCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitWriteOnlyManyEntriesCommand(InvocationContext ctx, WriteOnlyManyEntriesCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitWriteOnlyKeyValueCommand(InvocationContext ctx, WriteOnlyKeyValueCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitWriteOnlyManyCommand(InvocationContext ctx, WriteOnlyManyCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitReadWriteManyCommand(InvocationContext ctx, ReadWriteManyCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
@Override
public BasicInvocationStage visitReadWriteManyEntriesCommand(InvocationContext ctx, ReadWriteManyEntriesCommand command) throws Throwable {
return handleWriteCommand(ctx, command);
}
private BasicInvocationStage handleWriteCommand(InvocationContext ctx, WriteCommand command)
throws Throwable {
if (shouldEnlist(ctx)) {
LocalTransaction localTransaction = enlist((TxInvocationContext) ctx);
boolean implicitWith1Pc = useOnePhaseForAutoCommitTx && localTransaction.isImplicitTransaction();
if (implicitWith1Pc) {
//in this situation we don't support concurrent updates so skip locking entirely
command.addFlags(FlagBitSets.SKIP_LOCKING);
}
}
return invokeNext(ctx, command).handle((rCtx, rCommand, rv, t) -> {
// We shouldn't mark the transaction for rollback if it's going to be retried
WriteCommand writeCommand = (WriteCommand) rCommand;
if (t != null && !(t instanceof OutdatedTopologyException)) {
// Don't mark the transaction for rollback if it's fail silent (i.e. putForExternalRead)
if (rCtx.isOriginLocal() && rCtx.isInTxScope() && !writeCommand.hasAnyFlag(FlagBitSets.FAIL_SILENTLY)) {
TxInvocationContext txCtx = (TxInvocationContext) rCtx;
txCtx.getTransaction().setRollbackOnly();
}
}
if (t == null && shouldEnlist(rCtx) && writeCommand.isSuccessful()) {
TxInvocationContext txContext = (TxInvocationContext) rCtx;
txContext.getCacheTransaction().addModification(writeCommand);
}
});
}
public LocalTransaction enlist(TxInvocationContext ctx) throws SystemException {
Transaction transaction = ctx.getTransaction();
if (transaction == null) throw new IllegalStateException("This should only be called in an tx scope");
int status = transaction.getStatus();
LocalTransaction localTransaction = (LocalTransaction) ctx.getCacheTransaction();
if (isNotValid(status)) {
if (!localTransaction.isEnlisted()) {
// This transaction wouldn't be removed by TM.commit() or TM.rollback()
txTable.removeLocalTransaction(localTransaction);
}
throw new IllegalStateException("Transaction " + transaction +
" is not in a valid state to be invoking cache operations on.");
}
txTable.enlist(transaction, localTransaction);
return localTransaction;
}
private boolean isNotValid(int status) {
return status != Status.STATUS_ACTIVE
&& status != Status.STATUS_PREPARING
&& status != Status.STATUS_COMMITTING;
}
private static boolean shouldEnlist(InvocationContext ctx) {
return ctx.isInTxScope() && ctx.isOriginLocal();
}
@Override
public boolean getStatisticsEnabled() {
return isStatisticsEnabled();
}
@Override
public void setStatisticsEnabled(boolean enabled) {
statisticsEnabled = enabled;
}
@Override
@ManagedOperation(
description = "Resets statistics gathered by this component",
displayName = "Reset Statistics"
)
public void resetStatistics() {
prepares.set(0);
commits.set(0);
rollbacks.set(0);
}
@ManagedAttribute(
displayName = "Statistics enabled",
dataType = DataType.TRAIT,
writable = true
)
public boolean isStatisticsEnabled() {
return this.statisticsEnabled;
}
@ManagedAttribute(
description = "Number of transaction prepares performed since last reset",
displayName = "Prepares",
measurementType = MeasurementType.TRENDSUP,
displayType = DisplayType.SUMMARY
)
public long getPrepares() {
return prepares.get();
}
@ManagedAttribute(
description = "Number of transaction commits performed since last reset",
displayName = "Commits",
measurementType = MeasurementType.TRENDSUP,
displayType = DisplayType.SUMMARY
)
public long getCommits() {
return commits.get();
}
@ManagedAttribute(
description = "Number of transaction rollbacks performed since last reset",
displayName = "Rollbacks",
measurementType = MeasurementType.TRENDSUP,
displayType = DisplayType.SUMMARY
)
public long getRollbacks() {
return rollbacks.get();
}
private BasicInvocationStage verifyRemoteTransaction(BasicInvocationStage stage, RemoteTxInvocationContext ctx,
AbstractTransactionBoundaryCommand command) throws Throwable {
final GlobalTransaction globalTransaction = command.getGlobalTransaction();
// command.getOrigin() and ctx.getOrigin() are not reliable for LockControlCommands started by
// ClusteredGetCommands
final Address origin = globalTransaction.getAddress();
//It is possible to receive a prepare or lock control command from a node that crashed. If that's the case rollback
//the transaction forcefully in order to cleanup resources.
boolean originatorMissing = !rpcManager.getTransport().getMembers().contains(origin);
// It is also possible that the LCC timed out on the originator's end and this node has processed
// a TxCompletionNotification. So we need to check the presence of the remote transaction to
// see if we need to clean up any acquired locks on our end.
boolean alreadyCompleted = txTable.isTransactionCompleted(globalTransaction) || !txTable.containRemoteTx(globalTransaction);
// We want to throw an exception if the originator left the cluster and the transaction is not finished
// and/or it was rolled back by TransactionTable.cleanupLeaverTransactions().
// We don't want to throw an exception if the originator left the cluster but the transaction already
// completed successfully. So far, this only seems possible when forcing the commit of an orphaned
// transaction (with recovery enabled).
boolean completedSuccessfully = alreadyCompleted && !ctx.getCacheTransaction().isMarkedForRollback();
boolean canRollback = command instanceof PrepareCommand && !((PrepareCommand) command).isOnePhaseCommit() ||
command instanceof RollbackCommand || command instanceof LockControlCommand;
if (trace) {
log.tracef("Verifying transaction: originatorMissing=%s, alreadyCompleted=%s",
originatorMissing, alreadyCompleted);
}
if (alreadyCompleted || (originatorMissing && (canRollback || partitionHandlingManager.canRollbackTransactionAfterOriginatorLeave(globalTransaction)))) {
if (trace) {
log.tracef("Rolling back remote transaction %s because either already completed (%s) or originator no longer in the cluster (%s).",
globalTransaction, alreadyCompleted, originatorMissing);
}
RollbackCommand rollback = commandsFactory.buildRollbackCommand(command.getGlobalTransaction());
return invokeNext(ctx, rollback).handle((rCtx, rCommand, rv1, throwable1) -> {
RemoteTransaction remoteTx = ((TxInvocationContext) rCtx).getCacheTransaction();
remoteTx.markForRollback(true);
txTable.removeRemoteTransaction(globalTransaction);
if (throwable1 == null) {
if (originatorMissing && !completedSuccessfully) {
throw log.orphanTransactionRolledBack(globalTransaction);
}
}
});
}
return stage;
}
private InvocationStage replayRemoteTransactionIfNeeded(RemoteTxInvocationContext ctx, int topologyId)
throws Throwable {
// If a commit is received for a transaction that doesn't have its 'lookedUpEntries' populated
// we know for sure this transaction is 2PC and was received via state transfer but the preceding PrepareCommand
// was not received by local node because it was executed on the previous key owners. We need to re-prepare
// the transaction on local node to ensure its locks are acquired and lookedUpEntries is properly populated.
RemoteTransaction remoteTx = ctx.getCacheTransaction();
if (trace) {
log.tracef("Remote tx topology id %d and command topology is %d", remoteTx.lookedUpEntriesTopology(), topologyId);
}
if (remoteTx.lookedUpEntriesTopology() < topologyId) {
PrepareCommand prepareCommand;
if (useVersioning) {
prepareCommand = commandsFactory.buildVersionedPrepareCommand(ctx.getGlobalTransaction(), ctx.getModifications(), false);
} else {
prepareCommand = commandsFactory.buildPrepareCommand(ctx.getGlobalTransaction(), ctx.getModifications(), false);
}
commandsFactory.initializeReplicableCommand(prepareCommand, true);
prepareCommand.setOrigin(ctx.getOrigin());
if (trace) {
log.tracef("Replaying the transactions received as a result of state transfer %s",
prepareCommand);
}
return handlePrepareCommand(ctx, prepareCommand);
}
return null;
}
static class TransactionAwareKeyCloseableIterator extends TransactionAwareCloseableIterator {
private final Cache cache;
public TransactionAwareKeyCloseableIterator(CloseableIterator realIterator,
TxInvocationContext ctx, Cache cache) {
super(realIterator, ctx);
this.cache = cache;
}
@Override
protected K fromEntry(CacheEntry entry) {
return entry.getKey();
}
@Override
protected Object getKey(K value) {
return value;
}
@Override
public void remove() {
if (previousValue == null) {
throw new IllegalStateException();
}
cache.remove(previousValue);
previousValue = null;
}
}
static class TransactionAwareEntryCloseableIterator extends TransactionAwareCloseableIterator, K, V> {
private final Cache cache;
public TransactionAwareEntryCloseableIterator(CloseableIterator> realIterator,
TxInvocationContext ctx, Cache cache) {
super(realIterator, ctx);
this.cache = cache;
}
@Override
public void remove() {
if (previousValue == null) {
throw new IllegalStateException();
}
cache.remove(previousValue.getKey(), previousValue.getValue());
previousValue = null;
}
@Override
protected CacheEntry fromEntry(CacheEntry entry) {
return entry;
}
@Override
protected Object getKey(CacheEntry value) {
return value.getKey();
}
}
/**
* Class that provides transactional support so that the iterator will use the values in the context if they exist.
* This will keep track of seen values from the transactional context and if the transactional context is updated while
* iterating on this iterator it will see those updates unless the changed value was already seen by the iterator.
*
* @author wburns
* @since 8.0
*/
static abstract class TransactionAwareCloseableIterator implements CloseableIterator {
private final TxInvocationContext ctx;
// We store all the not yet seen context entries here. We rely on the fact that the cache entry reference is updated
// if a change occurs in between iterations to see updates.
private final List contextEntries;
private final Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy