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;
@Slf4j
public class MultipleNonOverlappingTest extends AbstractTransactionsTest {
@Override
public void TXBegin() { WWTXBegin(); }
/**
* High level:
*
* This test will create OBJECT_NUM of objects in an SMRMap. Each object's sum will be incremented by VAL until we
* reach FINAL_SUM. At the end of this test we:
*
* 1) Check that there are OBJECT_NUM number of objects in the SMRMap.
* 2) Ensure that each object's sum is equal to FINAL_SUM
*
* Details (the mechanism by which we increment the values in each object):
*
* 1) We create THREAD_NUM number of threads
* 2) Each thread is given a non-overlapping range on which to increment objects' sum. Each thread will increment
* it only once by VAL.
* 3) We spawn all threads and we ensure that each object in the map is incremented only once. We wait for them
* to finish.
* 4) Repeat the above steps FINAL_SUM number of times.
*/
@Test
public void testStress() throws Exception {
String mapName = "testMapA";
Map<Long, Long> testMap = instantiateCorfuObject(SMRMap.class, mapName);
final int VAL = 1;
// You can fine tune the below parameters. OBJECT_NUM has to be a multiple of THREAD_NUM.
final int OBJECT_NUM = 20;
final int THREAD_NUM = 5;
final int FINAL_SUM = OBJECT_NUM;
final int STEP = OBJECT_NUM / THREAD_NUM;
// test all objects advance in lock-step to FINAL_SUM
for (int i = 0; i < FINAL_SUM; i++) {
for (int j = 0; j < OBJECT_NUM; j += STEP) {
NonOverlappingWriter n = new NonOverlappingWriter(i + 1, j, j + STEP, VAL);
scheduleConcurrently(t -> { n.dowork();});
}
executeScheduled(THREAD_NUM, PARAMETERS.TIMEOUT_NORMAL);
}
Assert.assertEquals(testMap.size(), FINAL_SUM);
for (Long value : testMap.values()) {
Assert.assertEquals((long) FINAL_SUM, (long) value);
}
}
/**
* Same as above, but two maps, not advancing at the same pace
* @throws Exception
*/
@Test
public void testStress2() throws Exception {
String mapName1 = "testMapA";
Map<Long, Long> testMap1 = instantiateCorfuObject(SMRMap.class, mapName1);
String mapName2 = "testMapB";
Map<Long, Long> testMap2 = instantiateCorfuObject(SMRMap.class, mapName2);
final int VAL = 1;
// You can fine tune the below parameters. OBJECT_NUM has to be a multiple of THREAD_NUM.
final int OBJECT_NUM = 20;
final int THREAD_NUM = 5;
final int FINAL_SUM1 = OBJECT_NUM;
final int FINAL_SUM2 = OBJECT_NUM/2+1;
final int STEP = OBJECT_NUM / THREAD_NUM;
// test all objects advance in lock-step to FINAL_SUM
for (int i = 0; i < FINAL_SUM1; i++) {
for (int j = 0; j < OBJECT_NUM; j += STEP) {
NonOverlappingWriter n = new NonOverlappingWriter(i + 1, j, j + STEP, VAL);
scheduleConcurrently(t -> { n.dowork2();});
}
executeScheduled(THREAD_NUM, PARAMETERS.TIMEOUT_NORMAL);
}
Assert.assertEquals(testMap2.size(), FINAL_SUM1);
for (long i = 0; i < OBJECT_NUM; i++) {
log.debug("final testmap1.get({}) = {}", i, testMap1.get(i));
log.debug("final testmap2.get({}) = {}", i, testMap2.get(i));
if (i % 2 == 0)
Assert.assertEquals((long)testMap2.get(i), (long) FINAL_SUM2);
else
Assert.assertEquals((long)testMap2.get(i), (long) FINAL_SUM1);
}
}
public class NonOverlappingWriter {
String mapName1 = "testMapA";
Map<Long, Long> testMap1 = instantiateCorfuObject(SMRMap.class, mapName1);
String mapName2 = "testMapB";
Map<Long, Long> testMap2 = instantiateCorfuObject(SMRMap.class, mapName2);
int start;
int end;
int val;
int expectedSum;
public NonOverlappingWriter(int expectedSum, int start, int end, int val) {
this.expectedSum = expectedSum;
this.start = start;
this.end = end;
this.val = val;
}
/**
* Updates objects between index start and index end.
*/
public void dowork() {
for (int i = start; i < end; i++) {
simpleCreateImpl(i, val);
}
}
/**
* Updates objects between index start and index end.
*/
public void dowork2() {
for (int i = start; i < end; i++) {
duoCreateImpl(i, val);
}
}
/**
* Does the actual work.
*
* @param idx SMRMap key index to update
* @param val how much to increment the value by.
*/
private void simpleCreateImpl(long idx, long val) {
TXBegin();
if (!testMap1.containsKey(idx)) {
if (expectedSum - 1 > 0)
log.debug("OBJ FAIL {} doesn't exist expected={}",
idx, expectedSum);
log.debug("OBJ {} PUT {}", idx, val);
testMap1.put(idx, val);
} else {
log.debug("OBJ {} GET", idx);
Long value = testMap1.get(idx);
if (value != (expectedSum - 1))
log.debug("OBJ FAIL {} value={} expected={}", idx, value,
expectedSum - 1);
log.debug("OBJ {} PUT {}+{}", idx, value, val);
testMap1.put(idx, value + val);
}
TXEnd();
}
/**
* Does the actual work.
*
* @param idx SMRMap key index to update
* @param val how much to increment the value by.
*/
private void duoCreateImpl(long idx, long val) {
TXBegin();
if (!testMap1.containsKey(idx)) {
if (expectedSum - 1 > 0)
log.debug("OBJ FAIL {} doesn't exist expected={}",
idx, expectedSum);
log.debug("OBJ {} PUT {}", idx, val);
testMap1.put(idx, val);
testMap2.put(idx, val);
} else {
log.debug("OBJ {} GET", idx);
Long value = testMap1.get(idx);
if (value != (expectedSum - 1))
log.debug("OBJ FAIL {} value={} expected={}", idx, value,
expectedSum - 1);
log.debug("OBJ {} PUT {}+{}", idx, value, val);
testMap1.put(idx, value + val);
// in map 2, on even rounds, do this on for every other entry
log.debug("OBJ2 {} GET", idx);
Long value2 = testMap2.get(idx);
if (idx % 2 == 0) {
if (value2 != (expectedSum/2))
log.debug("OBJ2 FAIL {} value={} expected={}", idx, value2,
expectedSum/2);
if (expectedSum % 2 == 0) {
log.debug("OBJ2 {} PUT {}+{}", idx, value2, val);
testMap2.put(idx, value2 + val);
}
} else {
if (value2 != (expectedSum - 1))
log.debug("OBJ2 FAIL {} value={} expected={}", idx, value2,
expectedSum - 1);
log.debug("OBJ2 {} PUT {}+{}", idx, value2, val);
testMap2.put(idx, value2 + val);
}
}
TXEnd();
}
}
/**
* A helper function that ends a fransaciton.
*/
protected void TXEnd() {
getRuntime().getObjectsView().TXEnd();
}
protected <T> T instantiateCorfuObject(Class<T> tClass, String name) {
// TODO: Does not work at the moment.
// corfudb.runtime.exceptions.NoRollbackException: Can't roll back due to non-undoable exception
// getRuntime().getParameters().setUndoDisabled(true).setOptimisticUndoDisabled(true);
return (T) getRuntime()
.getObjectsView()
.build()
.setStreamName(name) // stream name
.setType(tClass) // object class backed by this stream\
.open(); // instantiate the object!
}
}