package org.radargun.stages.cache.background;
import org.mockito.Mockito;
import org.radargun.stages.cache.generators.KeyGenerator;
import org.radargun.stages.helpers.Range;
import org.radargun.state.SlaveState;
import org.radargun.traits.BasicOperations;
import org.radargun.traits.Transactional;
import org.radargun.util.CacheTestUtils;
import org.radargun.util.CacheTraitRepository;
import org.radargun.util.ReflectionUtils;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.Collection;
import java.util.Map;
import java.util.Random;
import static org.mockito.Mockito.*;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
/**
* @author Matej Cimbora
*/
@Test
public class PrivateLogLogicTest {
public void testMixedOperations() throws Exception {
BasicOperations.Cache cache = new CacheTraitRepository.BasicOperationsCache<>();
GeneralConfiguration gc = new GeneralConfiguration();
gc.transactionSize = 10;
PrivateLogLogic logic = createLogic(gc, new BackgroundStressorLogicConfiguration(), new LogLogicConfiguration(), cache);
// operationId 0, PUT
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, 0));
assertEquals(cache.get("-1"), null);
assertEquals(logic.delayedRemoves.get(0l), null);
assertEquals(logic.delayedRemoves.get(-1l), null);
// operationId 0 -> 1, PUT
logic.operationId++;
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1}));
assertEquals(cache.get("-1"), null);
assertEquals(logic.delayedRemoves.get(0l), null);
assertEquals(logic.delayedRemoves.get(-1l), null);
// operationId 1 -> 2, REMOVE
logic.operationId++;
doReturn(BasicOperations.REMOVE).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1}));
assertEquals(cache.get("-1"), new PrivateLogValue(0, new long[] {0, 1, 2}));
assertEquals(logic.delayedRemoves.get(0l).oldValue, new PrivateLogValue(0, new long[] {0, 1}));
assertEquals(logic.delayedRemoves.get(-1l), null);
// operationId 2 -> 3, REMOVE
logic.operationId++;
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1, 2, 3}));
assertEquals(cache.get("-1"), new PrivateLogValue(0, new long[] {0, 1, 2}));
assertEquals(logic.delayedRemoves.get(0l), null);
assertEquals(logic.delayedRemoves.get(-1l).oldValue, new PrivateLogValue(0, new long[] {0, 1, 2}));
// operationId 3 -> 4, PUT
logic.operationId++;
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4}));
assertEquals(cache.get("-1"), new PrivateLogValue(0, new long[] {0, 1, 2}));
assertEquals(logic.delayedRemoves.get(0l), null);
assertEquals(logic.delayedRemoves.get(-1l).oldValue, new PrivateLogValue(0, new long[] {0, 1, 2}));
// operationId 4 -> 5, REMOVE
logic.operationId++;
doReturn(BasicOperations.REMOVE).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4}));
assertEquals(cache.get("-1"), new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4, 5}));
assertEquals(logic.delayedRemoves.get(0l).oldValue, new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4}));
assertEquals(logic.delayedRemoves.get(-1l), null);
logic.operationId++;
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4, 5, 6}));
assertEquals(cache.get("-1"), new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4, 5}));
assertEquals(logic.delayedRemoves.get(0l), null);
assertEquals(logic.delayedRemoves.get(-1l).oldValue, new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4, 5}));
// No test errors are expected
assertNull(getFailureManager(logic).getError(true));
assertEquals(ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "txModifications", Collection.class).size(), 7);
doNothing().when(logic).startTransaction();
logic.ongoingTx = mock(Transactional.Transaction.class);
logic.afterCommit();
Map<Long, PrivateLogLogic.OperationTimestampPair> timestamps = ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "timestamps", Map.class);
assertEquals(timestamps.size(), 1);
assertEquals(timestamps.get(0l).operationId, 6);
Assert.assertEquals(ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "txModifications", Collection.class).size(), 0);
}
public void testMixedOperationsNonTx() throws Exception {
BasicOperations.Cache cache = new CacheTraitRepository.BasicOperationsCache<>();
PrivateLogLogic logic = createLogic(new GeneralConfiguration(), new BackgroundStressorLogicConfiguration(), new LogLogicConfiguration(), cache);
Map<Long, PrivateLogLogic.OperationTimestampPair> timestamps = ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "timestamps", Map.class);
// operationId 0, PUT
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, 0));
assertEquals(cache.get("-1"), null);
assertEquals(timestamps.get(0l).operationId, 0);
// operationId 0 -> 1, PUT
logic.operationId++;
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1}));
assertEquals(cache.get("-1"), null);
assertEquals(timestamps.get(0l).operationId, 1);
// operationId 1 -> 2, REMOVE
logic.operationId++;
doReturn(BasicOperations.REMOVE).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), null);
assertEquals(cache.get("-1"), new PrivateLogValue(0, new long[] {0, 1, 2}));
assertEquals(timestamps.get(0l).operationId, 2);
// operationId 2 -> 3, REMOVE
logic.operationId++;
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1, 2, 3}));
assertEquals(cache.get("-1"), null);
assertEquals(timestamps.get(0l).operationId, 3);
// operationId 3 -> 4, PUT
logic.operationId++;
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4}));
assertEquals(cache.get("-1"), null);
assertEquals(timestamps.get(0l).operationId, 4);
// operationId 4 -> 5, REMOVE
logic.operationId++;
doReturn(BasicOperations.REMOVE).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), null);
assertEquals(cache.get("-1"), new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4, 5}));
assertEquals(timestamps.get(0l).operationId, 5);
logic.operationId++;
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4, 5, 6}));
assertEquals(cache.get("-1"), null);
assertEquals(timestamps.get(0l).operationId, 6);
assertEquals(timestamps.size(), 1);
// No test errors are expected
assertNull(getFailureManager(logic).getError(true));
}
public void testRemoveOperationsFirst() throws Exception {
BasicOperations.Cache cache = new CacheTraitRepository.BasicOperationsCache<>();
GeneralConfiguration gc = new GeneralConfiguration();
gc.transactionSize = 10;
PrivateLogLogic logic = createLogic(gc, new BackgroundStressorLogicConfiguration(), new LogLogicConfiguration(), cache);
// operationId 0, REMOVE
doReturn(BasicOperations.REMOVE).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, 0));
assertEquals(cache.get("-1"), null);
assertEquals(logic.delayedRemoves.get(0l), null);
assertEquals(logic.delayedRemoves.get(-1l), null);
// operationId 0 -> 1, REMOVE
logic.operationId++;
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, 0));
assertEquals(cache.get("-1"), new PrivateLogValue(0, new long[] {0, 1}));
assertEquals(logic.delayedRemoves.get(0l).oldValue, new PrivateLogValue(0, 0));
assertEquals(logic.delayedRemoves.get(-1l), null);
// operationId 1 -> 2, REMOVE
logic.operationId++;
logic.invokeLogic(0);
assertEquals(cache.get("0"), new PrivateLogValue(0, new long[] {0, 1, 2}));
assertEquals(cache.get("-1"), new PrivateLogValue(0, new long[] {0, 1}));
assertEquals(logic.delayedRemoves.get(0l), null);
assertEquals(logic.delayedRemoves.get(-1l).oldValue, new PrivateLogValue(0, new long[] {0, 1}));
// No test errors are expected
assertNull(getFailureManager(logic).getError(true));
assertEquals(ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "txModifications", Collection.class).size(), 3);
doNothing().when(logic).startTransaction();
logic.ongoingTx = mock(Transactional.Transaction.class);
logic.afterCommit();
Map<Long, PrivateLogLogic.OperationTimestampPair> timestamps = ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "timestamps", Map.class);
assertEquals(timestamps.size(), 1);
assertEquals(timestamps.get(0l).operationId, 2);
assertEquals(ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "txModifications", Collection.class).size(), 0);
}
@Test(expectedExceptions = UnsupportedOperationException.class)
public void testGetFails() throws Exception {
BasicOperations.Cache cache = new CacheTraitRepository.BasicOperationsCache<>();
PrivateLogLogic logic = createLogic(new GeneralConfiguration(), new BackgroundStressorLogicConfiguration(), new LogLogicConfiguration(), cache);
// operationId 0, GET
doReturn(BasicOperations.GET).when(logic).getOperation(any(Random.class));
logic.invokeLogic(0);
}
/**
* Primary doesn't contain the operation, backup null
*/
public void testStaleReadDetected1() throws Exception {
BasicOperations.Cache cache = new CacheTraitRepository.BasicOperationsCache<>();
GeneralConfiguration gc = new GeneralConfiguration();
gc.transactionSize = 10;
PrivateLogLogic logic = createLogic(gc, new BackgroundStressorLogicConfiguration(), new LogLogicConfiguration(), cache);
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
Map<Long, PrivateLogLogic.OperationTimestampPair> timestamps = ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "timestamps", Map.class);
timestamps.put(0l, new PrivateLogLogic.OperationTimestampPair(5l, 123l));
cache.put("0", new PrivateLogValue(0, new long[] {0, 1, 2}));
logic.operationId = 3;
logic.invokeLogic(0);
assertEquals(getFailureManager(logic).getStaleReads(), 1);
}
/**
* Neither primary contains the operation, nor backup
*/
public void testStaleReadDetected2() throws Exception {
BasicOperations.Cache cache = new CacheTraitRepository.BasicOperationsCache<>();
GeneralConfiguration gc = new GeneralConfiguration();
gc.transactionSize = 10;
PrivateLogLogic logic = createLogic(gc, new BackgroundStressorLogicConfiguration(), new LogLogicConfiguration(), cache);
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
Map<Long, PrivateLogLogic.OperationTimestampPair> timestamps = ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "timestamps", Map.class);
timestamps.put(0l, new PrivateLogLogic.OperationTimestampPair(5l, 123l));
cache.put("0", new PrivateLogValue(0, new long[] {0, 1, 2}));
cache.put("-1", new PrivateLogValue(0, new long[] {0, 1, 2, 3}));
logic.operationId = 4;
logic.invokeLogic(0);
assertEquals(getFailureManager(logic).getStaleReads(), 1);
}
/**
* Primary null, backup doesn't contain the operation
*/
public void testStaleReadDetected3() throws Exception {
BasicOperations.Cache cache = new CacheTraitRepository.BasicOperationsCache<>();
GeneralConfiguration gc = new GeneralConfiguration();
gc.transactionSize = 10;
PrivateLogLogic logic = createLogic(gc, new BackgroundStressorLogicConfiguration(), new LogLogicConfiguration(), cache);
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
Map<Long, PrivateLogLogic.OperationTimestampPair> timestamps = ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "timestamps", Map.class);
timestamps.put(0l, new PrivateLogLogic.OperationTimestampPair(5l, 123l));
cache.put("-1", new PrivateLogValue(0, new long[] {0, 1, 2, 3}));
logic.operationId = 4;
logic.invokeLogic(0);
assertEquals(getFailureManager(logic).getStaleReads(), 1);
}
/**
* Both primary and backup null
*/
public void testStaleReadDetected4() throws Exception {
BasicOperations.Cache cache = new CacheTraitRepository.BasicOperationsCache<>();
GeneralConfiguration gc = new GeneralConfiguration();
gc.transactionSize = 10;
PrivateLogLogic logic = createLogic(gc, new BackgroundStressorLogicConfiguration(), new LogLogicConfiguration(), cache);
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
Map<Long, PrivateLogLogic.OperationTimestampPair> timestamps = ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "timestamps", Map.class);
timestamps.put(0l, new PrivateLogLogic.OperationTimestampPair(5l, 123l));
logic.operationId = 4;
logic.invokeLogic(0);
assertEquals(getFailureManager(logic).getStaleReads(), 1);
}
/**
* Using logLogicConfiguration.writeApplyMaxDelay avoids stale reads
*/
public void testStaleReadDetected5() throws Exception {
BasicOperations.Cache cache = new CacheTraitRepository.BasicOperationsCache<>();
GeneralConfiguration gc = new GeneralConfiguration();
gc.transactionSize = 10;
LogLogicConfiguration llc = new LogLogicConfiguration();
llc.writeApplyMaxDelay = 100;
PrivateLogLogic logic = createLogic(gc, new BackgroundStressorLogicConfiguration(), llc, cache);
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
Map<Long, PrivateLogLogic.OperationTimestampPair> timestamps = ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "timestamps", Map.class);
timestamps.put(0l, new PrivateLogLogic.OperationTimestampPair(5l, 123l));
cache.put("0", new PrivateLogValue(0, new long[] {0, 1, 2}));
logic.operationId = 3;
logic.invokeLogic(0);
assertEquals(getFailureManager(logic).getStaleReads(), 0);
}
/**
* Test logLogicConfiguration.valueMaxSize attained & modifying the same key multiple times within the same transaction. Provided
* timestamps are updated only after transaction finishes, we may lose last operation if it has been already checked by checkers,
* leading to stale reads if PrivateLogLogic.txModificationKeyIds are not used. Primary key test.
*/
public void testStaleReadDetected6() throws Exception {
BasicOperations.Cache txCache = new CacheTraitRepository.BasicOperationsCache<>();
GeneralConfiguration gc = new GeneralConfiguration();
gc.transactionSize = 10;
LogLogicConfiguration llc = new LogLogicConfiguration();
llc.valueMaxSize = 5;
PrivateLogLogic logic = createLogic(gc, new BackgroundStressorLogicConfiguration(), llc, txCache);
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
doReturn(4l).when(logic).getCheckedOperation(anyInt(), anyLong());
Map<Long, PrivateLogLogic.OperationTimestampPair> timestamps = ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "timestamps", Map.class);
timestamps.put(0l, new PrivateLogLogic.OperationTimestampPair(4l, 123l));
txCache.put("0", new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4}));
logic.operationId = 5;
logic.invokeLogic(0);
logic.operationId++;
logic.invokeLogic(0);
assertEquals(getFailureManager(logic).getStaleReads(), 0);
}
/**
* Test logLogicConfiguration.valueMaxSize attained & modifying the same key multiple times within the same transaction. Provided
* timestamps are updated only after transaction finishes, we may lose last operation if it has been already checked by checkers,
* leading to stale reads if PrivateLogLogic.txModificationKeyIds are not used. Backup key test.
*/
public void testStaleReadDetected7() throws Exception {
BasicOperations.Cache cache = new CacheTraitRepository.BasicOperationsCache<>();
GeneralConfiguration gc = new GeneralConfiguration();
gc.transactionSize = 10;
LogLogicConfiguration llc = new LogLogicConfiguration();
llc.valueMaxSize = 5;
PrivateLogLogic logic = createLogic(gc, new BackgroundStressorLogicConfiguration(), llc, cache);
doReturn(BasicOperations.PUT).when(logic).getOperation(any(Random.class));
doReturn(4l).when(logic).getCheckedOperation(anyInt(), anyLong());
Map<Long, PrivateLogLogic.OperationTimestampPair> timestamps = ReflectionUtils.getClassProperty(PrivateLogLogic.class, logic, "timestamps", Map.class);
timestamps.put(0l, new PrivateLogLogic.OperationTimestampPair(4l, 123l));
cache.put("-1", new PrivateLogValue(0, new long[] {0, 1, 2, 3, 4}));
logic.operationId = 5;
logic.invokeLogic(0);
logic.operationId++;
logic.invokeLogic(0);
assertEquals(getFailureManager(logic).getStaleReads(), 0);
}
private PrivateLogLogic createLogic(GeneralConfiguration gc, BackgroundStressorLogicConfiguration lc, LogLogicConfiguration llc, BasicOperations.Cache cache) throws NoSuchFieldException, IllegalAccessException {
if (gc == null || lc == null || llc == null || cache == null) {
throw new IllegalArgumentException("All configuration parameters need to be specified");
}
llc.enabled = true;
SlaveState slaveState = new SlaveState();
slaveState.put(KeyGenerator.KEY_GENERATOR, new CacheTestUtils.SimpleStringKeyGenerator());
BackgroundOpsManager manager = BackgroundOpsManager.getOrCreateInstance(slaveState, "test");
ReflectionUtils.setClassProperty(BackgroundOpsManager.class, manager, "generalConfiguration", gc);
ReflectionUtils.setClassProperty(BackgroundOpsManager.class, manager, "backgroundStressorLogicConfiguration", lc);
ReflectionUtils.setClassProperty(BackgroundOpsManager.class, manager, "logLogicConfiguration", llc);
ReflectionUtils.setClassProperty(BackgroundOpsManager.class, manager, "basicCache", cache);
Range range = new Range(0, 1);
PrivateLogLogic logic = Mockito.spy(new PrivateLogLogic(manager, range));
ReflectionUtils.setClassProperty(AbstractLogLogic.class, logic, "nonTxBasicCache", cache);
ReflectionUtils.setClassProperty(AbstractLogLogic.class, logic, "basicCache", cache);
new Stressor(manager, logic, 0);
return logic;
}
private FailureManager getFailureManager(AbstractLogic logic) throws NoSuchFieldException, IllegalAccessException {
BackgroundOpsManager manager = ReflectionUtils.getClassProperty(AbstractLogic.class, logic, "manager", BackgroundOpsManager.class);
return manager.getFailureManager();
}
}