package org.corfudb.runtime.concurrent; import lombok.extern.slf4j.Slf4j; import org.corfudb.runtime.collections.SMRMap; import org.corfudb.runtime.object.transactions.AbstractTransactionsTest; import org.junit.Assert; import org.junit.Test; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; /** * Created by dmalkhi on 3/17/17. */ @Slf4j public class StreamSeekAtomicityTest extends AbstractTransactionsTest { @Override public void TXBegin() { OptimisticTXBegin(); } protected int numIterations = PARAMETERS.NUM_ITERATIONS_LOW; /** * This test verifies commit atomicity against concurrent -read- activity, * which constantly causes rollbacks and optimistic-rollbacks. * * @throws Exception */ @Test public void ckCommitAtomicity() throws Exception { String mapName1 = "testMapA"; Map<Long, Long> testMap1 = instantiateCorfuObject(SMRMap.class, mapName1); CountDownLatch l1 = new CountDownLatch(2); AtomicBoolean commitDone = new AtomicBoolean(false); final int NTHREADS = 3; scheduleConcurrently(t -> { int txCnt; for (txCnt = 0; txCnt < numIterations; txCnt++) { TXBegin(); // on first iteration, wait for all to start; // other iterations will proceed right away l1.await(); // generate optimistic mutation testMap1.put(1L, (long)txCnt); // wait for it to be undon TXEnd(); } // signal done commitDone.set(true); Assert.assertEquals((long)(txCnt-1), (long) testMap1.get(1L)); }); // thread that keeps affecting optimistic-rollback of the above thread scheduleConcurrently(t -> { TXBegin(); testMap1.get(1L); // signal that transaction has started and obtained a snapshot l1.countDown(); // keep accessing the snapshot, causing optimistic rollback while (!commitDone.get()){ testMap1.get(1L); } }); // thread that keeps syncing with the tail of log scheduleConcurrently(t -> { // signal that thread has started l1.countDown(); // keep updating the in-memory proxy from the log while (!commitDone.get() ){ testMap1.get(1L); } }); executeScheduled(NTHREADS, PARAMETERS.TIMEOUT_NORMAL); } /** * This test is similar to above, but with multiple maps. * It verifies commit atomicity against concurrent -read- activity, * which constantly causes rollbacks and optimistic-rollbacks. * * @throws Exception */ @Test public void ckCommitAtomicity2() throws Exception { String mapName1 = "testMapA"; Map<Long, Long> testMap1 = instantiateCorfuObject(SMRMap.class, mapName1); String mapName2 = "testMapB"; Map<Long, Long> testMap2 = instantiateCorfuObject(SMRMap.class, mapName2); CountDownLatch l1 = new CountDownLatch(2); AtomicBoolean commitDone = new AtomicBoolean(false); final int NTHREADS = 3; scheduleConcurrently(t -> { int txCnt; for (txCnt = 0; txCnt < numIterations; txCnt++) { TXBegin(); // on first iteration, wait for all to start; // other iterations will proceed right away l1.await(); // generate optimistic mutation testMap1.put(1L, (long)txCnt); if (txCnt % 2 == 0) testMap2.put(1L, (long)txCnt); // wait for it to be undon TXEnd(); } // signal done commitDone.set(true); Assert.assertEquals((long)(txCnt-1), (long) testMap1.get(1L)); Assert.assertEquals((long)( (txCnt-1) % 2 == 0 ? (txCnt-1) : txCnt-2), (long) testMap2.get(1L)); }); // thread that keeps affecting optimistic-rollback of the above thread scheduleConcurrently(t -> { TXBegin(); testMap1.get(1L); // signal that transaction has started and obtained a snapshot l1.countDown(); // keep accessing the snapshot, causing optimistic rollback while (!commitDone.get()){ testMap1.get(1L); testMap2.get(1L); } }); // thread that keeps syncing with the tail of log scheduleConcurrently(t -> { // signal that thread has started l1.countDown(); // keep updating the in-memory proxy from the log while (!commitDone.get() ){ testMap1.get(1L); testMap2.get(1L); } }); executeScheduled(NTHREADS, PARAMETERS.TIMEOUT_NORMAL); } /** * This test is similar to above, but with concurrent -write- activity * * @throws Exception */ @Test public void ckCommitAtomicity3() throws Exception { String mapName1 = "testMapA"; Map<Long, Long> testMap1 = instantiateCorfuObject(SMRMap.class, mapName1); String mapName2 = "testMapB"; Map<Long, Long> testMap2 = instantiateCorfuObject(SMRMap.class, mapName2); CountDownLatch l1 = new CountDownLatch(2); AtomicBoolean commitDone = new AtomicBoolean(false); final int NTHREADS = 3; scheduleConcurrently(t -> { int txCnt; for (txCnt = 0; txCnt < numIterations; txCnt++) { TXBegin(); // on first iteration, wait for all to start; // other iterations will proceed right away l1.await(); // generate optimistic mutation testMap1.put(1L, (long)txCnt); if (txCnt % 2 == 0) testMap2.put(1L, (long)txCnt); // wait for it to be undon TXEnd(); } // signal done commitDone.set(true); Assert.assertEquals((long)(txCnt-1), (long) testMap1.get(1L)); Assert.assertEquals((long)( (txCnt-1) % 2 == 0 ? (txCnt-1) : txCnt-2), (long) testMap2.get(1L)); }); // thread that keeps affecting optimistic-rollback of the above thread scheduleConcurrently(t -> { long specialVal = numIterations + 1; TXBegin(); testMap1.get(1L); // signal that transaction has started and obtained a snapshot l1.countDown(); // keep accessing the snapshot, causing optimistic rollback while (!commitDone.get()){ testMap1.put(1L, specialVal); testMap2.put(1L, specialVal); } }); // thread that keeps syncing with the tail of log scheduleConcurrently(t -> { // signal that thread has started l1.countDown(); // keep updating the in-memory proxy from the log while (!commitDone.get() ){ testMap1.get(1L); testMap2.get(1L); } }); executeScheduled(NTHREADS, PARAMETERS.TIMEOUT_NORMAL); } }