package org.infinispan.transaction.impl; import static org.infinispan.commons.util.Util.toStr; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import org.infinispan.commands.write.WriteCommand; import org.infinispan.commons.util.CollectionFactory; import org.infinispan.container.entries.CacheEntry; import org.infinispan.context.Flag; import org.infinispan.transaction.xa.GlobalTransaction; import org.infinispan.transaction.xa.InvalidTransactionException; import org.infinispan.util.concurrent.CompletableFutures; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * Defines the state of a remotely originated transaction. * * @author Mircea.Markus@jboss.com * @since 4.0 */ public class RemoteTransaction extends AbstractCacheTransaction implements Cloneable { private static final Log log = LogFactory.getLog(RemoteTransaction.class); private static final boolean trace = log.isTraceEnabled(); private static final CompletableFuture<Void> INITIAL_FUTURE = CompletableFutures.completedNull(); /** * This int should be set to the highest topology id for transactions received via state transfer. During state * transfer we do not migrate lookedUpEntries to save bandwidth. If lookedUpEntriesTopology is less than the * topology of the CommitCommand that is received this indicates the preceding PrepareCommand was received by * previous owner before state transfer or the data has now changed owners and the current owner * now has to re-execute prepare to populate lookedUpEntries (and acquire the locks). */ // Default value of MAX_VALUE basically means it hasn't yet received what topology id this is for the entries private volatile int lookedUpEntriesTopology = Integer.MAX_VALUE; private volatile TotalOrderRemoteTransactionState transactionState; private final Object transactionStateLock = new Object(); private final AtomicReference<CompletableFuture<Void>> synchronization = new AtomicReference<>(INITIAL_FUTURE); public RemoteTransaction(WriteCommand[] modifications, GlobalTransaction tx, int topologyId, long txCreationTime) { super(tx, topologyId, txCreationTime); this.modifications = modifications == null || modifications.length == 0 ? Collections.emptyList() : Arrays.asList(modifications); lookedUpEntries = CollectionFactory.makeMap(CollectionFactory.computeCapacity(this.modifications.size())); } public RemoteTransaction(GlobalTransaction tx, int topologyId, long txCreationTime) { super(tx, topologyId, txCreationTime); this.modifications = new LinkedList<>(); lookedUpEntries = CollectionFactory.makeMap(4); } @Override public void setStateTransferFlag(Flag stateTransferFlag) { if (getStateTransferFlag() == null && stateTransferFlag == Flag.PUT_FOR_X_SITE_STATE_TRANSFER) { internalSetStateTransferFlag(stateTransferFlag); } } @Override public void putLookedUpEntry(Object key, CacheEntry e) { checkIfRolledBack(); if (trace) { log.tracef("Adding key %s to tx %s", toStr(key), getGlobalTransaction()); } lookedUpEntries.put(key, e); } @Override public void putLookedUpEntries(Map<Object, CacheEntry> entries) { checkIfRolledBack(); if (trace) { log.tracef("Adding keys %s to tx %s", entries.keySet(), getGlobalTransaction()); } lookedUpEntries.putAll(entries); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof RemoteTransaction)) return false; RemoteTransaction that = (RemoteTransaction) o; return tx.equals(that.tx); } @Override public int hashCode() { return tx.hashCode(); } @Override public Object clone() { try { RemoteTransaction dolly = (RemoteTransaction) super.clone(); dolly.modifications = new ArrayList<>(modifications); dolly.lookedUpEntries = CollectionFactory.makeMap(lookedUpEntries); return dolly; } catch (CloneNotSupportedException e) { throw new IllegalStateException("Impossible!!", e); } } @Override public String toString() { return "RemoteTransaction{" + "modifications=" + modifications + ", lookedUpEntries=" + lookedUpEntries + ", lockedKeys=" + toStr(getLockedKeys()) + ", backupKeyLocks=" + toStr(getBackupLockedKeys()) + ", lookedUpEntriesTopology=" + lookedUpEntriesTopology + ", isMarkedForRollback=" + isMarkedForRollback() + ", tx=" + tx + ", state=" + transactionState + '}'; } public void setLookedUpEntriesTopology(int lookedUpEntriesTopology) { this.lookedUpEntriesTopology = lookedUpEntriesTopology; } public int lookedUpEntriesTopology() { return lookedUpEntriesTopology; } private void checkIfRolledBack() { if (isMarkedForRollback()) { throw new InvalidTransactionException("This remote transaction " + getGlobalTransaction() + " is already rolled back"); } } /** * @return get (or create if needed) the {@code TotalOrderRemoteTransactionState} associated to this remote transaction */ public final TotalOrderRemoteTransactionState getTransactionState() { if (transactionState != null) { return transactionState; } synchronized (transactionStateLock) { if (transactionState == null) { transactionState = new TotalOrderRemoteTransactionState(getGlobalTransaction()); } return transactionState; } } public final CompletableFuture<Void> enterSynchronizationAsync(CompletableFuture<Void> releaseFuture) { CompletableFuture<Void> currentFuture; do { currentFuture = synchronization.get(); } while (!synchronization.compareAndSet(currentFuture, releaseFuture)); return currentFuture; } }