package org.corfudb.runtime.object.transactions; import com.google.common.collect.ImmutableMap; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.corfudb.protocols.logprotocol.MultiObjectSMREntry; import org.corfudb.protocols.logprotocol.MultiSMREntry; import org.corfudb.protocols.logprotocol.SMREntry; import org.corfudb.runtime.exceptions.TransactionAbortedException; import org.corfudb.runtime.object.*; import org.corfudb.runtime.view.Layout; import org.corfudb.util.Utils; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import static org.corfudb.runtime.object.transactions.TransactionalContext.getRootContext; /** * Represents a transactional context. Transactional contexts * manage per-thread transaction state. * * Recall from {@link CorfuCompileProxy} that an SMR object layer implements objects whose history of updates * are backed by a stream. If a Corfu object's method is an Accessor, it invokes the proxy's * access() method. Likewise, if a Corfu object's method is a Mutator or Accessor-Mutator, it invokes the * proxy's logUpdate() method. * * Within transactional context, these methods invoke the transacationalContect accessor/mutator helper. * * For example, OptimisticTransactionalContext.access() is responsible for * sync'ing the proxy state to the snapshot version, adn then doing the access. * * logUpdate() within transactional context is * responsible for updating the write-set. * * Finally, if a Corfu object's method is an Accessor-Mutator, then although the mutation is delayed, * it needs to obtain the result by invoking getUpcallResult() on the optimistic stream. * This is similar to the second stage of access(), accept working on the optimistic stream instead of the * underlying stream. * * Created by mwei on 4/4/16. */ @Slf4j public abstract class AbstractTransactionalContext implements Comparable<AbstractTransactionalContext> { /** Constant for the address of an uncommitted log entry. * */ public static final long UNCOMMITTED_ADDRESS = -1L; /** Constant for a transaction which has been folded into * another transaction. */ public static final long FOLDED_ADDRESS = -2L; /** Constant for a transaction which has been aborted. * */ public static final long ABORTED_ADDRESS = -3L; /** Constant for committing a transaction which did not * modify the log at all. */ public static final long NOWRITE_ADDRESS = -4L; /** The ID of the transaction. This is used for tracking only, it is * NOT recorded in the log. */ @Getter public UUID transactionID; /** The builder used to create this transaction. * */ @Getter public final TransactionBuilder builder; /** * TODO remove this! * The start time of the context. */ @Getter public final long startTime; /** The global-log position that the transaction snapshots in all reads. */ @Getter(lazy = true) private final long snapshotTimestamp = obtainSnapshotTimestamp(); /** The address that the transaction was committed at. */ @Getter public long commitAddress = AbstractTransactionalContext.UNCOMMITTED_ADDRESS; /** The parent context of this transaction, if in a nested transaction.*/ @Getter private final AbstractTransactionalContext parentContext; @Getter private final WriteSetInfo writeSetInfo = new WriteSetInfo(); @Getter private final ReadSetInfo readSetInfo = new ReadSetInfo(); /** * A future which gets completed when this transaction commits. * It is completed exceptionally when the transaction aborts. */ @Getter public CompletableFuture<Boolean> completionFuture = new CompletableFuture<>(); AbstractTransactionalContext(TransactionBuilder builder) { transactionID = UUID.randomUUID(); this.builder = builder; // TODO remove this startTime = System.currentTimeMillis(); parentContext = TransactionalContext.getCurrentContext(); AbstractTransactionalContext.log.debug("TXBegin[{}]", this); } /** Access the state of the object. * * @param proxy The proxy to access the state for. * @param accessFunction The function to execute, which will be provided with the state * of the object. * @param conflictObject Fine-grained conflict information, if available. * @param <R> The return type of the access function. * @param <T> The type of the proxy's underlying object. * @return The return value of the access function. */ abstract public <R,T> R access(ICorfuSMRProxyInternal<T> proxy, ICorfuSMRAccess<R,T> accessFunction, Object[] conflictObject); /** Get the result of an upcall. * * @param proxy The proxy to retrieve the upcall for. * @param timestamp The timestamp to return the upcall for. * @param conflictObject Fine-grained conflict information, if available. * @param <T> The type of the proxy's underlying object. * @return The result of the upcall. */ abstract public <T> Object getUpcallResult(ICorfuSMRProxyInternal<T> proxy, long timestamp, Object[] conflictObject); /** Log an SMR update to the Corfu log. * * @param proxy The proxy which generated the update. * @param updateEntry The entry which we are writing to the log. * @param conflictObject Fine-grained conflict information, if available. * @param <T> The type of the proxy's underlying object. * @return The address the update was written at. */ abstract public <T> long logUpdate(ICorfuSMRProxyInternal<T> proxy, SMREntry updateEntry, Object[] conflictObject); /** Add a given transaction to this transactional context, merging * the read and write sets. * @param tc The transactional context to merge. */ abstract public void addTransaction(AbstractTransactionalContext tc); /** Commit the transaction to the log. * * @throws TransactionAbortedException If the transaction is aborted. */ public long commitTransaction() throws TransactionAbortedException { completionFuture.complete(true); return NOWRITE_ADDRESS; } /** Forcefully abort the transaction. */ public void abortTransaction(TransactionAbortedException ae) { AbstractTransactionalContext.log.debug("TXAbort[{}]", this); commitAddress = ABORTED_ADDRESS; completionFuture .completeExceptionally(ae); } abstract public long obtainSnapshotTimestamp(); /** Add the proxy and conflict-params information to our read set. * @param proxy The proxy to add * @param conflictObjects The fine-grained conflict information, if * available. */ public void addToReadSet(ICorfuSMRProxyInternal proxy, Object[] conflictObjects) { getReadSetInfo().addToReadSet(proxy.getStreamID(), conflictObjects); } /** * merge another readSet into this one * @param other */ void mergeReadSetInto(ReadSetInfo other) { getReadSetInfo().mergeInto(other); } /** * Add an update to the transaction optimistic write-set. * * @param proxy the SMR object for this update * @param updateEntry the update * @param conflictObjects * @return a synthetic "address" in the write-set, to be used for * checking upcall results */ long addToWriteSet(ICorfuSMRProxy proxy, SMREntry updateEntry, Object[] conflictObjects) { return getWriteSetInfo().addToWriteSet(proxy.getStreamID(), updateEntry, conflictObjects); } /** * collect all the conflict-params from the write-set for this transaction * into a set. * @return A set of longs representing all the conflict params */ Map<UUID, Set<Integer>> collectWriteConflictParams() { return getWriteSetInfo().getWriteSetConflicts(); } void mergeWriteSetInto(WriteSetInfo other) { getWriteSetInfo().mergeInto(other); } /** * convert our write set into a new MultiObjectSMREntry. * @return */ MultiObjectSMREntry collectWriteSetEntries() { return getWriteSetInfo().getWriteSet(); } /** Helper function to get a write set for a particular stream. * * @param id The stream to get a append set for. * @return The append set for that stream, as an ordered list. */ List<SMREntry> getWriteSetEntryList(UUID id) { return getWriteSetInfo().getWriteSet().getSMRUpdates(id); } int getWriteSetEntrySize(UUID id) { List<SMREntry> entries = getWriteSetInfo().getWriteSet().getSMRUpdates(id); if (entries == null) return 0; else return entries.size(); } /** Transactions are ordered by their snapshot timestamp. */ @Override public int compareTo(AbstractTransactionalContext o) { return Long.compare(this.getSnapshotTimestamp(), o .getSnapshotTimestamp()); } @Override public String toString() { return "TX[" + Utils.toReadableID(transactionID) + "]"; } } /** * This class captures information about objects accessed (read) during speculative transaction execution */ @Getter class ReadSetInfo { // fine-grained conflict information regarding accessed-objects; // captures values passed using @conflict annotations in @corfuObject Map<UUID, Set<Integer>> readSetConflicts = new HashMap<>(); public void mergeInto(ReadSetInfo other) { other.getReadSetConflicts().forEach((streamID, cSet) -> { getConflictSet(streamID).addAll(cSet); }); } public void addToReadSet(UUID streamID, Object[] conflictObjects) { if (conflictObjects == null) return; Set<Integer> streamConflicts = getConflictSet(streamID); Arrays.asList(conflictObjects).stream() .forEach(V -> streamConflicts.add(Integer.valueOf(V.hashCode()))) ; } public Set<Integer> getConflictSet(UUID streamID) { return getReadSetConflicts().computeIfAbsent(streamID, u -> { return new HashSet<>(); } ); } } /** * This class captures information about objects mutated (written) during speculative transaction execution */ @Getter class WriteSetInfo { // fine-grained conflict information regarding mutated-objects; // captures values passed using @conflict annotations in @corfuObject Map<UUID, Set<Integer>> writeSetConflicts = new HashMap<>(); // the set of mutated objects Set<UUID> affectedStreams = new HashSet<>(); // teh actual updates to mutated objects MultiObjectSMREntry writeSet = new MultiObjectSMREntry(); Set<Integer> getConflictSet(UUID streamID) { return getWriteSetConflicts().computeIfAbsent(streamID, u -> { return new HashSet<>(); } ); } public void mergeInto(WriteSetInfo other) { synchronized (getRootContext().getTransactionID()) { // copy all the conflict-params other.writeSetConflicts.forEach((streamID, cSet) ->{ getConflictSet(streamID).addAll(cSet); }); // copy all the writeSet SMR entries writeSet.mergeInto(other.getWriteSet()); } } public long addToWriteSet(UUID streamID, SMREntry updateEntry, Object[] conflictObjects) { synchronized (getRootContext().getTransactionID()) { // add the SMRentry to the list of updates for this stream writeSet.addTo(streamID, updateEntry); // add all the conflict params to the conflict-params set for this stream if (conflictObjects != null) { Set<Integer> streamConflicts = getConflictSet(streamID); Arrays.asList(conflictObjects).stream() .forEach(V -> streamConflicts.add(Integer.valueOf(V.hashCode()))); } return writeSet.getSMRUpdates(streamID).size()-1; } } }