package org.corfudb.runtime.collections; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; import lombok.Data; import lombok.Getter; import lombok.ToString; import org.corfudb.runtime.CorfuRuntime; import org.corfudb.runtime.exceptions.TransactionAbortedException; import org.corfudb.runtime.object.ICorfuSMR; import org.corfudb.runtime.view.AbstractViewTest; import org.corfudb.runtime.view.ObjectOpenOptions; import org.corfudb.util.serializer.Serializers; import org.junit.Before; import org.junit.Test; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * Created by mwei on 1/7/16. */ public class SMRMapTest extends AbstractViewTest { @Getter final String defaultConfigurationString = getDefaultEndpoint(); public CorfuRuntime r; @Before public void setRuntime() throws Exception { r = getDefaultRuntime().connect(); } @Test @SuppressWarnings("unchecked") public void canReadWriteToSingle() throws Exception { Map<String, String> testMap = getRuntime() .getObjectsView() .build() .setStreamName("test") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); testMap.clear(); assertThat(testMap.put("a", "a")) .isNull(); assertThat(testMap.put("a", "b")) .isEqualTo("a"); assertThat(testMap.get("a")) .isEqualTo("b"); } @Test @SuppressWarnings("unchecked") public void canGetID() throws Exception { UUID id = UUID.randomUUID(); ICorfuSMR testMap = (ICorfuSMR) getRuntime().getObjectsView() .build() .setStreamID(id) .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); assertThat(id) .isEqualTo(testMap.getCorfuStreamID()); } @Test @SuppressWarnings("unchecked") public void loadsFollowedByGets() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("test") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); testMap.clear(); for (int i = 0; i < PARAMETERS.NUM_ITERATIONS_LOW; i++) { assertThat(testMap.put(Integer.toString(i), Integer.toString(i))) .isNull(); } for (int i = 0; i < PARAMETERS.NUM_ITERATIONS_LOW; i++) { assertThat(testMap.get(Integer.toString(i))) .isEqualTo(Integer.toString(i)); } } @Test @SuppressWarnings("unchecked") public void canContainOtherCorfuObjects() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("test 1") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); testMap.clear(); testMap.put("z", "e"); Map<String, Map<String, String>> testMap2 = getRuntime().getObjectsView() .build() .setStreamName("test 2") .setTypeToken(new TypeToken<SMRMap<String, Map<String, String>>>() {}) .open(); testMap2.put("a", testMap); assertThat(testMap2.get("a").get("z")) .isEqualTo("e"); testMap2.get("a").put("y", "f"); assertThat(testMap.get("y")) .isEqualTo("f"); Map<String, String> testMap3 = getRuntime().getObjectsView() .build() .setStreamName("test 1") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); assertThat(testMap3.get("y")) .isEqualTo("f"); } @Test @SuppressWarnings("unchecked") public void canContainNullObjects() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("a") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); testMap.clear(); testMap.put("z", null); assertThat(testMap.get("z")) .isEqualTo(null); Map<String, String> testMap2 = getRuntime() .getObjectsView() .build() .setStreamName("a") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); assertThat(testMap2.get("z")) .isEqualTo(null); } @Test public void loadsFollowedByGetsConcurrent() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamID(UUID.randomUUID()) .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); final int num_threads = PARAMETERS.CONCURRENCY_SOME; final int num_records = PARAMETERS.NUM_ITERATIONS_LOW; testMap.clear(); scheduleConcurrently(num_threads, threadNumber -> { int base = threadNumber * num_records; for (int i = base; i < base + num_records; i++) { assertThat(testMap.put(Integer.toString(i), Integer.toString(i))) .isEqualTo(null); } }); long startTime = System.currentTimeMillis(); executeScheduled(num_threads, PARAMETERS.TIMEOUT_LONG); calculateRequestsPerSecond("WPS", num_records * num_threads, startTime); scheduleConcurrently(num_threads, threadNumber -> { int base = threadNumber * num_records; for (int i = base; i < base + num_records; i++) { assertThat(testMap.get(Integer.toString(i))) .isEqualTo(Integer.toString(i)); } }); startTime = System.currentTimeMillis(); executeScheduled(num_threads, PARAMETERS.TIMEOUT_LONG); calculateRequestsPerSecond("RPS", num_records * num_threads, startTime); } @Test @SuppressWarnings("unchecked") public void loadsFollowedByGetsConcurrentMultiView() throws Exception { r.setBackpointersDisabled(true); final int num_threads = 5; final int num_records = 1000; Map<String, String>[] testMap = IntStream.range(0, num_threads) .mapToObj(i -> { return getRuntime().getObjectsView() .build() .setStreamID(UUID.randomUUID()) .setTypeToken(new TypeToken<SMRMap<String, String>>() { }) .addOption(ObjectOpenOptions.NO_CACHE) .open(); }) .toArray(Map[]::new); scheduleConcurrently(num_threads, threadNumber -> { int base = threadNumber * num_records; for (int i = base; i < base + num_records; i++) { assertThat(testMap[threadNumber].put(Integer.toString(i), Integer.toString(i))) .isEqualTo(null); } }); long startTime = System.currentTimeMillis(); executeScheduled(num_threads, PARAMETERS.TIMEOUT_LONG); calculateRequestsPerSecond("WPS", num_records * num_threads, startTime); scheduleConcurrently(num_threads, threadNumber -> { int base = threadNumber * num_records; for (int i = base; i < base + num_records; i++) { assertThat(testMap[threadNumber].get(Integer.toString(i))) .isEqualTo(Integer.toString(i)); } }); startTime = System.currentTimeMillis(); executeScheduled(num_threads, PARAMETERS.TIMEOUT_LONG); calculateRequestsPerSecond("RPS", num_records * num_threads, startTime); } @Test @SuppressWarnings("unchecked") public void collectionsStreamInterface() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("test") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); testMap.put("a", "b"); getRuntime().getObjectsView().TXBegin(); if (testMap.values().stream().anyMatch(x -> x.equals("c"))) { throw new Exception("test"); } testMap.compute("b", (k, v) -> "c"); getRuntime().getObjectsView().TXEnd(); assertThat(testMap) .containsEntry("b", "c"); } @Test @SuppressWarnings("unchecked") public void readSetDiffFromWriteSet() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("test1") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); Map<String, String> testMap2 = getRuntime().getObjectsView() .build() .setStreamName("test2") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); testMap.put("a", "b"); testMap2.put("a", "c"); Semaphore s1 = new Semaphore(0); Semaphore s2 = new Semaphore(0); scheduleConcurrently(1, threadNumber -> { s1.tryAcquire(PARAMETERS.TIMEOUT_NORMAL.toMillis(), TimeUnit.MILLISECONDS); getRuntime().getObjectsView().TXBegin(); testMap2.put("a", "d"); getRuntime().getObjectsView().TXEnd(); s2.release(); }); scheduleConcurrently(1, threadNumber -> { getRuntime().getObjectsView().TXBegin(); testMap.compute("b", (k, v) -> testMap2.get("a")); s1.release(); s2.tryAcquire(PARAMETERS.TIMEOUT_NORMAL.toMillis(), TimeUnit.MILLISECONDS); assertThatThrownBy(() -> getRuntime().getObjectsView().TXEnd()) .isInstanceOf(TransactionAbortedException.class); }); executeScheduled(PARAMETERS.CONCURRENCY_TWO, PARAMETERS.TIMEOUT_NORMAL); } @Test @SuppressWarnings("unchecked") public void canUpdateSingleObjectTransacationally() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("test") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); getRuntime().getObjectsView().TXBegin(); assertThat(testMap.put("a", "a")) .isNull(); assertThat(testMap.put("a", "b")) .isEqualTo("a"); assertThat(testMap.get("a")) .isEqualTo("b"); getRuntime().getObjectsView().TXEnd(); assertThat(testMap.get("a")) .isEqualTo("b"); } @Test @SuppressWarnings("unchecked") public void multipleTXesAreApplied() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("test") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); IntStream.range(0, PARAMETERS.NUM_ITERATIONS_LOW).asLongStream() .forEach(l -> { try { assertThat(testMap) .hasSize((int) l); getRuntime().getObjectsView().TXBegin(); assertThat(testMap.put(Long.toString(l), Long.toString(l))) .isNull(); assertThat(testMap) .hasSize((int) l + 1); getRuntime().getObjectsView().TXEnd(); assertThat(testMap) .hasSize((int) l + 1); } catch (TransactionAbortedException tae) { throw new RuntimeException(tae); } }); assertThat(testMap) .hasSize(PARAMETERS.NUM_ITERATIONS_LOW); } @Test @SuppressWarnings("unchecked") public void multipleTXesAreAppliedWOAccessors() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("test") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); IntStream.range(0, PARAMETERS.NUM_ITERATIONS_LOW).asLongStream() .forEach(l -> { try { getRuntime().getObjectsView().TXBegin(); assertThat(testMap.put(Long.toString(l), Long.toString(l))) .isNull(); getRuntime().getObjectsView().TXEnd(); } catch (TransactionAbortedException tae) { throw new RuntimeException(tae); } }); assertThat(testMap) .hasSize(PARAMETERS.NUM_ITERATIONS_LOW); } @Test @SuppressWarnings("unchecked") public void mutatorFollowedByATransaction() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("test") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); testMap.clear(); getRuntime().getObjectsView().TXBegin(); assertThat(testMap.put("a", "a")) .isNull(); assertThat(testMap.put("a", "b")) .isEqualTo("a"); assertThat(testMap.get("a")) .isEqualTo("b"); getRuntime().getObjectsView().TXEnd(); assertThat(testMap.get("a")) .isEqualTo("b"); } @Test @SuppressWarnings("unchecked") public void objectViewCorrectlyReportsInsideTX() throws Exception { assertThat(getRuntime().getObjectsView().TXActive()) .isFalse(); getRuntime().getObjectsView().TXBegin(); assertThat(getRuntime().getObjectsView().TXActive()) .isTrue(); getRuntime().getObjectsView().TXEnd(); assertThat(getRuntime().getObjectsView().TXActive()) .isFalse(); } @Test @SuppressWarnings("unchecked") public void canUpdateSingleObjectTransacationallyWhenCached() throws Exception { r.setCacheDisabled(false); Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("test") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); getRuntime().getObjectsView().TXBegin(); assertThat(testMap.put("a", "a")) .isNull(); assertThat(testMap.put("a", "b")) .isEqualTo("a"); assertThat(testMap.get("a")) .isEqualTo("b"); getRuntime().getObjectsView().TXEnd(); assertThat(testMap.get("a")) .isEqualTo("b"); } @Test @SuppressWarnings("unchecked") public void abortedTransactionsCannotBeReadOnSingleObject() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("test") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); testMap.clear(); testMap.put("z", "z"); assertThat(testMap.size()) .isEqualTo(1); getRuntime().getObjectsView().TXBegin(); assertThat(testMap.put("a", "a")) .isNull(); assertThat(testMap.put("a", "b")) .isEqualTo("a"); assertThat(testMap.get("a")) .isEqualTo("b"); testMap.clear(); getRuntime().getObjectsView().TXAbort(); assertThat(testMap.size()) .isEqualTo(1); } @Test @SuppressWarnings("unchecked") public void modificationDuringTransactionCausesAbort() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("A") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); assertThat(testMap.put("a", "z")); getRuntime().getObjectsView().TXBegin(); assertThat(testMap.put("a", "a")) .isEqualTo("z"); assertThat(testMap.put("a", "b")) .isEqualTo("a"); assertThat(testMap.get("a")) .isEqualTo("b"); CompletableFuture cf = CompletableFuture.runAsync(() -> { Map<String, String> testMap2 = getRuntime().getObjectsView() .build() .setStreamName("A") .setSerializer(Serializers.JSON) .addOption(ObjectOpenOptions.NO_CACHE) .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); getRuntime().getObjectsView().TXBegin(); testMap2.put("a", "f"); getRuntime().getObjectsView().TXEnd(); }); cf.join(); assertThatThrownBy(() -> getRuntime().getObjectsView().TXEnd()) .isInstanceOf(TransactionAbortedException.class); } @Test @SuppressWarnings("unchecked") public void smrMapCanContainCustomObjects() throws Exception { Map<String, TestObject> testMap = getRuntime().getObjectsView() .build() .setStreamName("A") .setTypeToken(new TypeToken<SMRMap<String, TestObject>>() {}) .open(); testMap.put("A", new TestObject("A", 2, ImmutableMap.of("A", "B"))); assertThat(testMap.get("A").getTestString()) .isEqualTo("A"); assertThat(testMap.get("A").getTestInt()) .isEqualTo(2); } @Test @SuppressWarnings("unchecked") public void smrMapCanContainCustomObjectsInsideTXes() throws Exception { Map<String, TestObject> testMap = getRuntime().getObjectsView() .build() .setStreamName("A") .setTypeToken(new TypeToken<SMRMap<String, TestObject>>() {}) .open(); IntStream.range(0, PARAMETERS.NUM_ITERATIONS_LOW) .forEach(l -> { try { getRuntime().getObjectsView().TXBegin(); testMap.put(Integer.toString(l), new TestObject(Integer.toString(l), l, ImmutableMap.of( Integer.toString(l), l))); if (l > 0) { assertThat(testMap.get(Integer.toString(l - 1)).getTestInt()) .isEqualTo(l - 1); } getRuntime().getObjectsView().TXEnd(); } catch (TransactionAbortedException tae) { throw new RuntimeException(tae); } }); assertThat(testMap.get("3").getTestString()) .isEqualTo("3"); assertThat(testMap.get("3").getTestInt()) .isEqualTo(Integer.parseInt("3")); } @Test @SuppressWarnings("unchecked") public void unusedMutatorAccessor() throws Exception { Map<String, String> testMap = getRuntime().getObjectsView() .build() .setStreamName("A") .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); testMap.put("a", "z"); } AtomicInteger aborts; void getMultiViewSM(int numThreads) { UUID mapStream = UUID.randomUUID(); Map<String, String>[] testMap = IntStream.range(0, numThreads) .mapToObj(i -> { return getRuntime().getObjectsView() .build() .setStreamID(mapStream) .setTypeToken(new TypeToken<SMRMap<String, String>>() { }) .addOption(ObjectOpenOptions.NO_CACHE) .open(); }) .toArray(Map[]::new); // # keys indicate how much contention there will be final int numKeys = numThreads * 5; Random r = new Random(); // state 0: start a transaction addTestStep((ignored_task_num) -> { getRuntime().getObjectsView().TXBegin(); }); // state 1: do a put and a get addTestStep( (task_num) -> { final int putKey = r.nextInt(numKeys); final int getKey = r.nextInt(numKeys); testMap[task_num%numThreads].put(Integer.toString(putKey), testMap[task_num%numThreads].get(Integer.toString(getKey))); }); // state 2 (final): ask to commit the transaction addTestStep( (ignored_task_num) -> { try { getRuntime().getObjectsView().TXEnd(); } catch (TransactionAbortedException tae) { aborts.incrementAndGet(); } }); } @Test @SuppressWarnings("unchecked") public void concurrentAbortMultiViewInterleaved() throws Exception { final int numThreads = PARAMETERS.CONCURRENCY_SOME; final int numRecords = PARAMETERS.NUM_ITERATIONS_LOW; long startTime = System.currentTimeMillis(); aborts = new AtomicInteger(); getMultiViewSM(numThreads); // invoke the interleaving engine scheduleInterleaved(numThreads, numThreads*numRecords); // print stats.. calculateRequestsPerSecond("TPS", numRecords * numThreads, startTime); calculateAbortRate(aborts.get(), numRecords * numThreads); } @Test @SuppressWarnings("unchecked") public void concurrentAbortMultiViewThreaded() throws Exception { final int numThreads = PARAMETERS.CONCURRENCY_SOME; final int numRecords = PARAMETERS.NUM_ITERATIONS_LOW; long startTime = System.currentTimeMillis(); aborts = new AtomicInteger(); getMultiViewSM(numThreads); // invoke the interleaving engine scheduleThreaded(numThreads, numThreads*numRecords); // print stats.. calculateRequestsPerSecond("TPS", numRecords * numThreads, startTime); calculateAbortRate(aborts.get(), numRecords * numThreads); } @Test @SuppressWarnings("unchecked") public void bulkReads() throws Exception { UUID stream = UUID.randomUUID(); Map<String, String> testMap = getRuntime() .getObjectsView() .build() .setStreamID(stream) .setTypeToken(new TypeToken<SMRMap<String, String>>() {}) .open(); testMap.clear(); for (int i = 0; i < PARAMETERS.NUM_ITERATIONS_LOW; i++) { assertThat(testMap.put(Integer.toString(i), Integer.toString(i))) .isNull(); } // Do a bulk read of the stream by initializing a new view. final int num_threads = 1; long startTime = System.nanoTime(); Map<String, String> testMap2 = getRuntime().getObjectsView().build() .setType(SMRMap.class) .setStreamID(stream) .addOption(ObjectOpenOptions.NO_CACHE) .open(); // Do a get to prompt the sync assertThat(testMap2.get(Integer.toString(0))) .isEqualTo(Integer.toString(0)); long endTime = System.nanoTime(); final int MILLISECONDS_TO_MICROSECONDS = 1000; testStatus += "Time to sync whole stream=" + String.format("%d us", (endTime - startTime) / MILLISECONDS_TO_MICROSECONDS); } @Data @ToString static class TestObject { final String testString; final int testInt; final Map<String, Object> deepMap; } }