package org.corfudb.runtime.view;
import com.google.common.reflect.TypeToken;
import org.corfudb.protocols.logprotocol.LogEntry;
import org.corfudb.protocols.logprotocol.MultiObjectSMREntry;
import org.corfudb.protocols.logprotocol.MultiSMREntry;
import org.corfudb.protocols.logprotocol.SMREntry;
import org.corfudb.protocols.wireprotocol.ILogData;
import org.corfudb.runtime.CorfuRuntime;
import org.corfudb.runtime.collections.SMRMap;
import org.corfudb.runtime.exceptions.TransactionAbortedException;
import org.corfudb.runtime.view.stream.IStreamView;
import org.corfudb.util.serializer.Serializers;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Created by mwei on 2/18/16.
*/
public class ObjectsViewTest extends AbstractViewTest {
public static boolean referenceTX(Map<String, String> smrMap) {
smrMap.put("a", "b");
assertThat(smrMap)
.containsEntry("a", "b");
return true;
}
@Test
@SuppressWarnings("unchecked")
public void canCopyObject()
throws Exception {
//begin tests
CorfuRuntime r = getDefaultRuntime();
Map<String, String> smrMap = r.getObjectsView().build()
.setStreamName("map a")
.setTypeToken(new TypeToken<SMRMap<String, String>>() {})
.open();
smrMap.put("a", "a");
Map<String, String> smrMapCopy = r.getObjectsView()
.copy(smrMap, "map a copy");
smrMapCopy.put("b", "b");
assertThat(smrMapCopy)
.containsEntry("a", "a")
.containsEntry("b", "b");
assertThat(smrMap)
.containsEntry("a", "a")
.doesNotContainEntry("b", "b");
}
@Test
@SuppressWarnings("unchecked")
public void cannotCopyNonCorfuObject()
throws Exception {
//begin tests
CorfuRuntime r = getDefaultRuntime();
assertThatThrownBy(() -> {
r.getObjectsView().copy(new HashMap<String, String>(), CorfuRuntime.getStreamID("test"));
}).isInstanceOf(RuntimeException.class);
}
@Test
@SuppressWarnings("unchecked")
public void canAbortNoTransaction()
throws Exception {
//begin tests
CorfuRuntime r = getDefaultRuntime();
r.getObjectsView().TXAbort();
}
@Test
@SuppressWarnings("unchecked")
public void abortedTransactionDoesNotConflict()
throws Exception {
final String mapA = "map a";
//Enbale transaction logging
CorfuRuntime r = getDefaultRuntime()
.setTransactionLogging(true);
SMRMap<String, String> map = getDefaultRuntime().getObjectsView()
.build()
.setStreamName(mapA)
.setTypeToken(new TypeToken<SMRMap<String, String>>() {})
.open();
// TODO: fix so this does not require mapCopy.
SMRMap<String, String> mapCopy = getDefaultRuntime().getObjectsView()
.build()
.setStreamName(mapA)
.setTypeToken(new TypeToken<SMRMap<String, String>>() {})
.addOption(ObjectOpenOptions.NO_CACHE)
.open();
map.put("initial", "value");
Semaphore s1 = new Semaphore(0);
Semaphore s2 = new Semaphore(0);
// Schedule two threads, the first starts a transaction and reads,
// then waits for the second thread to finish.
// the second starts a transaction, waits for the first tx to read
// and commits.
// The first thread then resumes and attempts to commit. It should abort.
scheduleConcurrently(1, t -> {
assertThatThrownBy(() -> {
getRuntime().getObjectsView().TXBegin();
map.get("k");
s1.release(); // Let thread 2 start.
s2.acquire(); // Wait for thread 2 to commit.
map.put("k", "v1");
getRuntime().getObjectsView().TXEnd();
}).isInstanceOf(TransactionAbortedException.class);
});
scheduleConcurrently(1, t -> {
s1.acquire(); // Wait for thread 1 to read
getRuntime().getObjectsView().TXBegin();
mapCopy.put("k", "v2");
getRuntime().getObjectsView().TXEnd();
s2.release();
});
executeScheduled(2, PARAMETERS.TIMEOUT_LONG);
// The result should contain T2s modification.
assertThat(map)
.containsEntry("k", "v2");
IStreamView txStream = r.getStreamsView().get(ObjectsView
.TRANSACTION_STREAM_ID);
List<ILogData> txns = txStream.remainingUpTo(Long.MAX_VALUE);
assertThat(txns).hasSize(1);
assertThat(txns.get(0).getLogEntry(getRuntime()).getType())
.isEqualTo(LogEntry.LogEntryType.MULTIOBJSMR);
MultiObjectSMREntry tx1 = (MultiObjectSMREntry)txns.get(0).getLogEntry
(getRuntime());
MultiSMREntry entryMap = tx1.getEntryMap().get(CorfuRuntime.getStreamID(mapA));
assertThat(entryMap).isNotNull();
assertThat(entryMap.getUpdates().size()).isEqualTo(1);
SMREntry smrEntry = entryMap.getUpdates().get(0);
Object[] args = smrEntry.getSMRArguments();
assertThat(smrEntry.getSMRMethod()).isEqualTo("put");
assertThat((String) args[0]).isEqualTo("k");
assertThat((String) args[1]).isEqualTo("v2");
}
@Test
@SuppressWarnings("unchecked")
public void unrelatedStreamDoesNotConflict()
throws Exception {
//begin tests
CorfuRuntime r = getDefaultRuntime();
Map<String, String> smrMap = r.getObjectsView().build()
.setStreamName("map a")
.setTypeToken(new TypeToken<SMRMap<String, String>>() {})
.open();
IStreamView streamB = r.getStreamsView().get(CorfuRuntime.getStreamID("b"));
smrMap.put("a", "b");
streamB.append(new SMREntry("hi", new Object[]{"hello"}, Serializers.PRIMITIVE));
//this TX should not conflict
assertThat(smrMap)
.doesNotContainKey("b");
r.getObjectsView().TXBegin();
String b = smrMap.get("a");
smrMap.put("b", b);
r.getObjectsView().TXEnd();
assertThat(smrMap)
.containsEntry("b", "b");
}
@Test
@SuppressWarnings("unchecked")
public void unrelatedTransactionDoesNotConflict()
throws Exception {
//begin tests
CorfuRuntime r = getDefaultRuntime();
Map<String, String> smrMap = r.getObjectsView().build()
.setStreamName("map a")
.setTypeToken(new TypeToken<SMRMap<String, String>>() {})
.open();
Map<String, String> smrMapB = r.getObjectsView().build()
.setStreamName("map b")
.setTypeToken(new TypeToken<SMRMap<String, String>>() {})
.open();
smrMap.put("a", "b");
r.getObjectsView().TXBegin();
String b = smrMap.get("a");
smrMapB.put("b", b);
r.getObjectsView().TXEnd();
//this TX should not conflict
assertThat(smrMap)
.doesNotContainKey("b");
r.getObjectsView().TXBegin();
b = smrMap.get("a");
smrMap.put("b", b);
r.getObjectsView().TXEnd();
assertThat(smrMap)
.containsEntry("b", "b");
}
}