package org.corfudb.runtime.object.transactions;
import lombok.extern.slf4j.Slf4j;
import org.corfudb.protocols.logprotocol.SMREntry;
import org.corfudb.protocols.wireprotocol.TokenResponse;
import org.corfudb.runtime.object.ISMRStream;
import org.corfudb.runtime.view.Address;
import org.corfudb.util.Utils;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* Created by mwei on 3/13/17.
*
* SMRStreamAdapter wraps an optimistic transaction execution context, per
* object, with an SMRStream API.
*
* The main purpose of wrapping the write-set of optimistic transactions as an
* SMRStream is to provide the abstraction of a stream of SMREntries. The
* SMRStream maintains for us a position in the sequence. We can consume it
* in a forward direction, and scroll back to previously read entries.
*
* First, forget about nested transactions for now, and neglect the contexts
* stack; that is, assume the stack has size 1.
*
* A reminder from AbstractTransactionalContext about the write-set of a
* transaction:
* * A write-set is a key component of a transaction.
* * We collect the write-set as a map, organized by streams.
* * For each stream, we record a pair:
* * - a set of conflict-parameters modified by this transaction on the
* * stream,
* * - a list of SMR updates by this transcation on the stream.
* *
*
* The implementation of the current() method looks at the write-set, picks
* the list of SMRentries corresponding to the current object id, and returns
* the entry in the list corredponding the the current SMRStream position.
*
* previous() decrements the current SMRStream position and returns the entry
* corresponding to it.
*
* RemainingUpTo() returns a list of entries.
*
* WriteSetSMRStream does not support the full API - neither append nor seek are
* supported.
*
* Enter nested transactions.
*
* WriteSetSMRStream maintains the abstractions also across nested transactions.
* It supports navigating forward/backward across the SMREntries in the entire transcation stack.
*
*/
@Slf4j
public class WriteSetSMRStream implements ISMRStream {
List<AbstractTransactionalContext> contexts;
int currentContext = 0;
// TODO add comment
long currentContextPos;
// TODO add comment
long writePos;
// the specific stream-id for which this SMRstream wraps the write-set
final UUID id;
public WriteSetSMRStream(List<AbstractTransactionalContext> contexts,
UUID id) {
this.contexts = contexts;
this.id = id;
reset();
}
/** Return whether stream current transaction is the thread current transaction.
*
* This is validated by checking whether the current context
* for this stream is the same as the current context for this thread.
*
* @return True, if the stream current context is the thread current context.
* False otherwise.
*/
public boolean isStreamCurrentContextThreadCurrentContext() {
return contexts.get(currentContext)
.equals(TransactionalContext.getCurrentContext());
}
/** Return whether we are the stream for this current thread
*
* This is validated by checking whether the root context
* for this stream is the same as the root context for this thread.
*
* @return True, if the thread owns the optimistic stream
* False otherwise.
*/
public boolean isStreamForThisThread() {
return contexts.get(0)
.equals(TransactionalContext.getRootContext());
}
void mergeTransaction() {
contexts.remove(contexts.size()-1);
if (currentContext == contexts.size()) {
// recalculate the pos based on the write pointer
// TODO add explanation, code below very confusing!
long readPos = Address.maxNonAddress();
for (int i = 0; i < contexts.size(); i++) {
readPos += contexts.get(i).getWriteSetEntryList(id).size();
if (readPos >= writePos) {
currentContextPos = contexts.get(i).getWriteSetEntryList(id).size()
- (writePos - readPos) - 1;
}
}
currentContext--;
}
}
@Override
public List<SMREntry> remainingUpTo(long maxGlobal) {
// Check for any new contexts
if (TransactionalContext.getTransactionStack().size() >
contexts.size()) {
contexts = TransactionalContext.getTransactionStackAsList();
} else if (TransactionalContext.getTransactionStack().size() <
contexts.size()) {
mergeTransaction();
}
List<SMREntry> entryList = new LinkedList<>();
for (int i = currentContext; i < contexts.size(); i++) {
final List<SMREntry> writeSet = contexts.get(i)
.getWriteSetEntryList(id);
long readContextStart = i == currentContext ? currentContextPos + 1: 0;
for (long j = readContextStart; j < writeSet.size(); j++) {
entryList.add(writeSet.get((int) j));
writePos++;
}
if (writeSet.size() > 0) {
currentContext = i;
currentContextPos = writeSet.size() - 1;
}
}
return entryList;
}
@Override
public List<SMREntry> current() {
if (Address.nonAddress(writePos)) {
return null;
}
if (Address.nonAddress(currentContextPos))
currentContextPos = -1;
return Collections.singletonList(contexts
.get(currentContext)
.getWriteSetEntryList(id)
.get((int)(currentContextPos)));
}
@Override
public List<SMREntry> previous() {
writePos--;
if (writePos <= Address.maxNonAddress()) {
writePos = Address.maxNonAddress();
return null;
}
currentContextPos--;
// Pop the context if we're at the beginning of it
if (currentContextPos <= Address.maxNonAddress()) {
do {
if (currentContext == 0) {
throw new RuntimeException("Attempted to pop first context (pos=" + pos() + ")");
} else {
currentContext--;
}
} while (contexts
.get(currentContext)
.getWriteSetEntrySize(id) == 0);
currentContextPos = contexts
.get(currentContext)
.getWriteSetEntrySize(id)-1 ;
}
return current();
}
@Override
public long pos() {
return writePos;
}
@Override
public void reset() {
writePos = Address.maxNonAddress();
currentContext = 0;
currentContextPos = Address.maxNonAddress();
}
@Override
public void seek(long globalAddress) {
throw new UnsupportedOperationException();
}
@Override
public Stream<SMREntry> stream() {
return streamUpTo(Address.MAX);
}
@Override
public Stream<SMREntry> streamUpTo(long maxGlobal) {
return remainingUpTo(maxGlobal)
.stream();
}
@Override
public long append(SMREntry entry,
Function<TokenResponse, Boolean> acquisitionCallback,
Function<TokenResponse, Boolean> deacquisitionCallback) {
throw new UnsupportedOperationException();
}
@Override
public UUID getID() {
return id;
}
@Override
public String toString() {
return "WSSMRStream[" + Utils.toReadableID(getID()) +"]";
}
}