package org.infinispan.stress;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.transaction.Status;
import javax.transaction.TransactionManager;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.test.MultipleCacheManagersTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.transaction.LockingMode;
import org.infinispan.util.concurrent.IsolationLevel;
import org.testng.annotations.Test;
/**
* @author Pedro Ruivo
* @since 7.0
*/
@Test(testName = "stress.AbstractWriteSkewStressTest", groups = "stress")
public abstract class AbstractWriteSkewStressTest extends MultipleCacheManagersTest {
private static final String SHARED_COUNTER_TEST_KEY = "counter";
private static final int SHARED_COUNTER_TEST_MAX_COUNTER_VALUE = 1000;
@Override
protected void createCacheManagers() throws Throwable {
ConfigurationBuilder builder = TestCacheManagerFactory.getDefaultCacheConfiguration(true);
builder.clustering().cacheMode(getCacheMode())
.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).lockAcquisitionTimeout(TestingUtil.shortTimeoutMillis())
.transaction().lockingMode(LockingMode.OPTIMISTIC);
decorate(builder);
createCluster(builder, 2);
waitForClusterToForm();
}
protected void decorate(ConfigurationBuilder builder) {
// No-op
}
protected abstract CacheMode getCacheMode();
// This test is based on a contribution by Pedro Ruivo of INESC-ID, working on the Cloud-TM project.
public void testSharedCounter() {
final Cache<String, Integer> c1 = cache(0);
final Cache<String, Integer> c2 = cache(1);
//initialize the counter
c1.put(SHARED_COUNTER_TEST_KEY, 0);
//check if the counter is initialized in all caches
assertEquals("Initial value is different from zero in cache 1", 0, (int) c1.get(SHARED_COUNTER_TEST_KEY));
assertEquals("Initial value is different from zero in cache 2", 0, (int) c2.get(SHARED_COUNTER_TEST_KEY));
//this will keep the values put by both threads. any duplicate value will be detected because of the
//return value of add() method
final Set<Integer> uniqueValuesIncremented = new ConcurrentSkipListSet<>();
//create both threads (each of them incrementing the counter on one node)
Future<Boolean> f1 = fork(new IncrementCounterTask(c1, uniqueValuesIncremented));
Future<Boolean> f2 = fork(new IncrementCounterTask(c2, uniqueValuesIncremented));
try {
// wait to finish and check is any duplicate value has been detected
assertTrue("Cache 1 [" + address(c1) + "] has put a duplicate value", f1.get(5, TimeUnit.MINUTES));
assertTrue("Cache 2 [" + address(c2) + "] has put a duplicate value", f2.get(5, TimeUnit.MINUTES));
} catch (InterruptedException e) {
fail("Interrupted exception while running the test");
} catch (ExecutionException e) {
log.error("Exception in running updater threads", e);
fail("Exception running updater threads");
} catch (TimeoutException e) {
fail("Timed out waiting for updater threads");
} finally {
f1.cancel(true);
f2.cancel(true);
}
//check if all caches obtains the counter_max_values
assertTrue("Cache 1 [" + address(c1) + "] fina value is less than " + SHARED_COUNTER_TEST_MAX_COUNTER_VALUE,
c1.get(SHARED_COUNTER_TEST_KEY) >= SHARED_COUNTER_TEST_MAX_COUNTER_VALUE);
assertTrue("Cache 2 [" + address(c2) + "] fina value is less than " + SHARED_COUNTER_TEST_MAX_COUNTER_VALUE,
c2.get(SHARED_COUNTER_TEST_KEY) >= SHARED_COUNTER_TEST_MAX_COUNTER_VALUE);
}
private class IncrementCounterTask implements Callable<Boolean> {
private final Cache<String, Integer> cache;
private final Set<Integer> uniqueValuesSet;
private final TransactionManager transactionManager;
private int lastValue;
public IncrementCounterTask(Cache<String, Integer> cache, Set<Integer> uniqueValuesSet) {
this.cache = cache;
this.transactionManager = cache.getAdvancedCache().getTransactionManager();
this.uniqueValuesSet = uniqueValuesSet;
this.lastValue = 0;
}
@Override
public Boolean call() throws InterruptedException {
boolean unique = true;
while (lastValue < SHARED_COUNTER_TEST_MAX_COUNTER_VALUE && !Thread.interrupted()) {
boolean success = false;
try {
//start transaction, get the counter value, increment and put it again
//check for duplicates in case of success
transactionManager.begin();
Integer value = cache.get(SHARED_COUNTER_TEST_KEY);
value = value + 1;
lastValue = value;
cache.put(SHARED_COUNTER_TEST_KEY, value);
transactionManager.commit();
unique = uniqueValuesSet.add(value);
success = true;
} catch (Exception e) {
// expected exception
} finally {
if (!success) {
try {
//lets rollback
if (transactionManager.getStatus() != Status.STATUS_NO_TRANSACTION)
transactionManager.rollback();
} catch (Throwable t) {
//the only possible exception is thrown by the rollback. just ignore it
log.trace("Exception during rollback", t);
}
}
assertTrue("Duplicate value found in " + address(cache) + " (value=" + lastValue + ")", unique);
}
}
return unique;
}
}
}