package org.corfudb.runtime.object; import com.google.common.reflect.TypeToken; import org.corfudb.runtime.collections.SMRMap; import org.corfudb.runtime.object.transactions.TransactionalContext; import org.corfudb.runtime.view.AbstractViewTest; import org.junit.Test; import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; /** * Created by mwei on 11/11/16. */ public class CompileProxyTest extends AbstractViewTest { @Test public void testObjectMapSimple() throws Exception { Map<String, String> map = getDefaultRuntime() .getObjectsView().build() .setStreamName("my stream") .setTypeToken(new TypeToken<SMRMap<String,String>>() {}) .open(); getDefaultRuntime().getObjectsView().TXBegin(); map.put("hello", "world"); map.put("hell", "world"); getDefaultRuntime().getObjectsView().TXEnd(); assertThat(map) .containsEntry("hello", "world"); assertThat(map) .containsEntry("hell", "world"); } @Test public void testObjectCounterSimple() throws Exception { CorfuSharedCounter sharedCounter = getDefaultRuntime() .getObjectsView().build() .setStreamName("my stream") .setTypeToken(new TypeToken<CorfuSharedCounter>() { }) .open(); final int VALUE = 33; sharedCounter.setValue(VALUE); assertThat(sharedCounter.getValue()) .isEqualTo(VALUE); } /** * test concurrent writes to a shared counter. * set up 'concurrency' threads that concurrently try to set the counter to their thread-index. * the test tracks raw stream updates, one by one, guaranteeing that all the updates appended to the stream differ from each other. * * @throws Exception */ @Test public void testObjectCounterWriteConcurrency() throws Exception { CorfuSharedCounter sharedCounter = getDefaultRuntime() .getObjectsView().build() .setStreamName("my stream") .setTypeToken(new TypeToken<CorfuSharedCounter>() { }) .open(); final int concurrency = PARAMETERS.CONCURRENCY_LOTS; final int INITIAL = -1; sharedCounter.setValue(INITIAL); assertThat(sharedCounter.getValue()) .isEqualTo(INITIAL); // schedule 'concurrency' number of threads, // each one sets the shared counter value to its thread index scheduleConcurrently(concurrency, t -> sharedCounter.setValue(t) ); executeScheduled(concurrency, PARAMETERS.TIMEOUT_NORMAL); // track the raw stream updates caused by the execution so far ICorfuSMR<CorfuSharedCounter> compiledSharedCounter = (ICorfuSMR<CorfuSharedCounter>) sharedCounter; ICorfuSMRProxyInternal<CorfuSharedCounter> proxy_CORFUSMR = (ICorfuSMRProxyInternal<CorfuSharedCounter>) compiledSharedCounter.getCorfuSMRProxy(); //IStreamView objStream = proxy_CORFUSMR.getUnderlyingObject().getStreamViewUnsafe(); int beforeSync, afterSync; // before sync'ing the in-memory object, the in-memory copy does not get updated assertThat(beforeSync = proxy_CORFUSMR.getUnderlyingObject().object.getValue()) .isEqualTo(INITIAL); // sync with the stream entry by entry for (int timestamp = 1; timestamp <= concurrency; timestamp++) { proxy_CORFUSMR.getUnderlyingObject() .syncObjectUnsafe(timestamp); assertThat((afterSync = proxy_CORFUSMR.getUnderlyingObject().object.getValue())) .isBetween(0, concurrency); assertThat(beforeSync) .isNotEqualTo(afterSync); beforeSync = afterSync; } // now we get the LATEST value through the Corfu object API assertThat((afterSync = sharedCounter.getValue())) .isBetween(0, concurrency); assertThat(beforeSync) .isEqualTo(afterSync); } /** * test serializability guarantee of mutatorAccessor methods. * The test invokes CorfuSharedCounter::CAS by 'concurrency' number of concurrent threads. * @throws Exception */ @Test public void testObjectCounterCASConcurrency() throws Exception { CorfuSharedCounter sharedCounter = getDefaultRuntime() .getObjectsView().build() .setStreamName("my stream") .setTypeToken(new TypeToken<CorfuSharedCounter>() { }) .open(); int concurrency = PARAMETERS.CONCURRENCY_LOTS; final int INITIAL = -1; sharedCounter.setValue(INITIAL); // concurrency invoke CAS by multiple threads AtomicInteger casSucceeded = new AtomicInteger(0); scheduleConcurrently(concurrency, t -> { if (sharedCounter.CAS(INITIAL, t) == INITIAL) casSucceeded.incrementAndGet(); }); executeScheduled(concurrency, PARAMETERS.TIMEOUT_SHORT); // check that exactly one CAS succeeded assertThat(sharedCounter.getValue()) .isBetween(0, concurrency); assertThat(casSucceeded.get()) .isEqualTo(1); } /** * the test interleaves reads and writes to a shared counter. * * The test uses the interleaving engine to interleave numTasks*threads state machines. * A simple state-machine is built. It randomly chooses to either read or append the counter. * On a read, the last written value is expected. * * @throws Exception */ @Test public void testObjectCounterReadConcurrency() throws Exception { CorfuSharedCounter sharedCounter = getDefaultRuntime() .getObjectsView().build() .setStreamName("my stream") .setTypeToken(new TypeToken<CorfuSharedCounter>() { }) .open(); int numTasks = PARAMETERS.NUM_ITERATIONS_LOW; Random r = new Random(PARAMETERS.SEED); sharedCounter.setValue(-1); AtomicInteger lastUpdate = new AtomicInteger(-1); assertThat(sharedCounter.getValue()) .isEqualTo(lastUpdate.get()); // only one step: randomly choose between read/append of the shared counter addTestStep ((task_num) -> { if (r.nextBoolean()) { sharedCounter.setValue(task_num); lastUpdate.set(task_num); // remember the last written value } else { assertThat(sharedCounter.getValue()).isEqualTo(lastUpdate.get()); // expect to read the value in lastUpdate } } ); // invoke the interleaving engine scheduleInterleaved(PARAMETERS.CONCURRENCY_SOME, PARAMETERS.CONCURRENCY_SOME*numTasks); } /** * the test is similar to the interleaved read/append above to a shared counter. * in addition to checking read/append values, it tracks the raw stream state between updates and syncs. * * The test uses the interleaving engine to interleave numTasks*threads state machines. * A simple state-machine is built. It randomly chooses to either read or append the counter. * On a read, the last written value is expected. * * @throws Exception */ @Test public void testObjectCounterConcurrencyStream() throws Exception { CorfuSharedCounter sharedCounter = getDefaultRuntime() .getObjectsView().build() .setStreamName("my stream") .setTypeToken(new TypeToken<CorfuSharedCounter>() { }) .open(); ICorfuSMR<CorfuSharedCounter> compiledSharedCounter = (ICorfuSMR<CorfuSharedCounter>) sharedCounter; ICorfuSMRProxyInternal<CorfuSharedCounter> proxy_CORFUSMR = (ICorfuSMRProxyInternal<CorfuSharedCounter>) compiledSharedCounter.getCorfuSMRProxy(); // IStreamView objStream = proxy_CORFUSMR.getUnderlyingObject().getStreamViewUnsafe(); int numTasks = PARAMETERS.NUM_ITERATIONS_LOW; Random r = new Random(PARAMETERS.SEED); final int INITIAL = -1; sharedCounter.setValue(INITIAL); AtomicInteger lastUpdate = new AtomicInteger(INITIAL); AtomicLong lastUpdateStreamPosition = new AtomicLong(0); assertThat(sharedCounter.getValue()) .isEqualTo(lastUpdate.get()); AtomicInteger lastRead = new AtomicInteger(INITIAL); // build a state-machine: // only one step: randomly choose between read/append of the shared counter addTestStep((task_num) -> { if (r.nextBoolean()) { sharedCounter.setValue(task_num); lastUpdate.set(task_num); // remember the last written value lastUpdateStreamPosition.incrementAndGet(); // advance the expected stream position } else { // before sync'ing the in-memory object, the in-memory copy does not get updated // check that the in-memory copy is only as up-to-date as the latest 'get()' assertThat(proxy_CORFUSMR.getUnderlyingObject().object.getValue()) .isEqualTo(lastRead.get()); // now read, expect to get the latest written assertThat(sharedCounter.getValue()).isEqualTo(lastUpdate.get()); // remember the last read lastRead.set(lastUpdate.get()); } } ); // invoke the interleaving engine scheduleInterleaved(PARAMETERS.CONCURRENCY_SOME, PARAMETERS.CONCURRENCY_SOME*numTasks); } /** * test concurrent 'put()' into a map. * the test sets up 'concurrency' number of threads that each inserts a map entry using its own thread index as key. * we then check that the map contains keys with all thread indexes. * * @throws Exception */ @Test public void testCorfuMapConcurrency() throws Exception { Map<String, String> map = getDefaultRuntime() .getObjectsView().build() .setStreamName("my stream") .setTypeToken(new TypeToken<SMRMap<String, String>>() { }) .open(); int concurrency = PARAMETERS.CONCURRENCY_LOTS; // schedule 'concurrency' number of threads, // each one put()'s a key with its thread index scheduleConcurrently(concurrency, t -> { map.put(Integer.toString(t), "world"); }); executeScheduled(concurrency, PARAMETERS.TIMEOUT_SHORT); // check that map contains keys with every thread index for (int i = 0; i < concurrency; i++) assertThat(map.containsKey(Integer.toString(i))) .isTrue(); // check that containsKey can return false.. // any value outside the range 0..(concurrency-1) will work. assertThat(map.containsKey(Integer.toString(concurrency))) .isFalse(); } /** * test a corfu compound object, where a class field contains an inner class. * this is a simple test: set the object fields and check reading back the values. * * @throws Exception */ @Test public void testObjectCompoundSimple() throws Exception { CorfuCompoundObj sharedCorfuCompound = getDefaultRuntime() .getObjectsView().build() .setStreamName("my stream") .setTypeToken(new TypeToken<CorfuCompoundObj>() { }) .open(); final int TESTVALUE = 33; sharedCorfuCompound.set(sharedCorfuCompound.new Inner("A", "B"), TESTVALUE); assertThat(sharedCorfuCompound.getID()) .isEqualTo(TESTVALUE); assertThat(sharedCorfuCompound.getUser().firstName) .isEqualTo("A"); assertThat(sharedCorfuCompound.getUser().lastName) .isEqualTo("B"); } /** * test concurrent updates to a compound Corfu object with an inner class. * the test sets up 'concurrency' number of threads that each sets the compound object fields to thread-specific values. * then it does some sanity checks on the final value of the object. * * @throws Exception */ @Test public void testObjectCompoundWriteConcurrency() throws Exception { CorfuCompoundObj sharedCorfuCompound = getDefaultRuntime() .getObjectsView().build() .setStreamName("my stream") .setTypeToken(new TypeToken<CorfuCompoundObj>() { }) .open(); int concurrency = PARAMETERS.CONCURRENCY_LOTS; // set up 'concurrency' number of threads that concurrency update sharedCorfuCompound, each to a differen value scheduleConcurrently(concurrency, t -> { sharedCorfuCompound.set(sharedCorfuCompound.new Inner("A"+t, "B"+t), t); }); executeScheduled(concurrency, PARAMETERS.TIMEOUT_SHORT); // sanity checks on the final value of sharedCorfuCompound assertThat(sharedCorfuCompound.getID()) .isBetween(0, concurrency); assertThat(sharedCorfuCompound.getUser().firstName) .startsWith("A"); assertThat(sharedCorfuCompound.getUser().lastName) .startsWith("B"); } /** * test concurrent updates and reads to/from a compound Corfu object with an inner class. * this is similar to the tests above, but add tracking of raw stream status. * * the test sets up 'concurrency' number of concurrent threads. * each thread execute a 2-step state machine: * * One step updates the compound object to a task-specific value. * This step also verifies that the stream grows in length by 1. * * The second step checks that the in-memory object state has not been updated. * * The test utilizes the interleaving engine to interleaving executions of these state machines * over PARAMETERS.NUM_ITERATIONS_MODERATE number of tasks with PARAMETERS.CONCURRENCY_SOME concurrent threads. * * @throws Exception */ @Test public void testObjectCompoundWriteConcurrencyStream() throws Exception { CorfuCompoundObj sharedCorfuCompound = getDefaultRuntime() .getObjectsView().build() .setStreamName("my stream") .setTypeToken(new TypeToken<CorfuCompoundObj>() { }) .open(); // for tracking raw stream status ICorfuSMR<CorfuCompoundObj> compiledCorfuCompound = (ICorfuSMR<CorfuCompoundObj>) sharedCorfuCompound; ICorfuSMRProxyInternal<CorfuCompoundObj> proxy_CORFUSMR = (ICorfuSMRProxyInternal<CorfuCompoundObj>) compiledCorfuCompound.getCorfuSMRProxy(); // IStreamView objStream = proxy_CORFUSMR.getUnderlyingObject().getStreamViewUnsafe(); // initialization sharedCorfuCompound.set(sharedCorfuCompound.new Inner("E" + 0, "F" + 0), 0); // invoke some accessor to sync the in-memory object // return value is ignored sharedCorfuCompound.getID(); // build a state-machine: // step 1: update the shared compound to task-specific value addTestStep((task_num) -> { sharedCorfuCompound.set(sharedCorfuCompound.new Inner("C" + task_num, "D" + task_num), task_num); }); // step 2: check the unsync'ed in-memory object state addTestStep((ignored_task_num) -> { // before sync'ing the in-memory object, the in-memory copy does not get updated assertThat(proxy_CORFUSMR.getUnderlyingObject().object.getUser().getFirstName()) .startsWith("E"); assertThat(proxy_CORFUSMR.getUnderlyingObject().object.getUser().getLastName()) .startsWith("F"); }); // invoke the interleaving engine scheduleInterleaved(PARAMETERS.CONCURRENCY_SOME, PARAMETERS.NUM_ITERATIONS_MODERATE); assertThat(sharedCorfuCompound.getUser().getFirstName()) .startsWith("C"); assertThat(sharedCorfuCompound.getUser().getLastName()) .startsWith("D"); } }