package org.infinispan.container.versioning;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.Future;
import javax.transaction.Status;
import javax.transaction.TransactionManager;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.test.SingleCacheManagerTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.transaction.LockingMode;
import org.infinispan.transaction.TransactionMode;
import org.infinispan.transaction.lookup.EmbeddedTransactionManagerLookup;
import org.infinispan.util.concurrent.IsolationLevel;
import org.testng.annotations.Test;
@Test(groups = "functional", testName = "container.versioning.TransactionalLocalWriteSkewTest")
public class TransactionalLocalWriteSkewTest extends SingleCacheManagerTest {
@Override
protected EmbeddedCacheManager createCacheManager() throws Exception {
ConfigurationBuilder builder = new ConfigurationBuilder();
builder
.transaction()
.transactionManagerLookup(new EmbeddedTransactionManagerLookup())
.transactionMode(TransactionMode.TRANSACTIONAL)
.lockingMode(LockingMode.OPTIMISTIC)
.locking().lockAcquisitionTimeout(TestingUtil.shortTimeoutMillis())
.isolationLevel(IsolationLevel.REPEATABLE_READ);
EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createCacheManager(builder);
cacheManager.defineConfiguration("cache", builder.build());
return cacheManager;
}
public void testSharedCounter() throws Exception {
final int counterMaxValue = 1000;
Cache<String, Integer> c1 = cacheManager.getCache("cache");
// initialize the counter
c1.put("counter", 0);
// check if the counter is initialized in all caches
assertEquals(Integer.valueOf(0), c1.get("counter"));
// this will keep the values put by both threads. any duplicate value
// will be detected because of the
// return value of add() method
ConcurrentSkipListSet<Integer> uniqueValuesIncremented = new ConcurrentSkipListSet<>();
// create both threads (simulate a node)
Future<Void> ict1 = fork(new IncrementCounterTask(c1, uniqueValuesIncremented, counterMaxValue), null);
Future<Void> ict2 = fork(new IncrementCounterTask(c1, uniqueValuesIncremented, counterMaxValue), null);
try {
// wait to finish
ict1.get();
ict2.get();
// check if all caches obtains the counter_max_values
assertTrue(c1.get("counter") >= counterMaxValue);
} finally {
ict1.cancel(true);
ict2.cancel(true);
}
}
private class IncrementCounterTask implements Runnable {
private Cache<String, Integer> cache;
private ConcurrentSkipListSet<Integer> uniqueValuesSet;
private TransactionManager transactionManager;
private int lastValue;
private int counterMaxValue;
public IncrementCounterTask(Cache<String, Integer> cache, ConcurrentSkipListSet<Integer> uniqueValuesSet, int counterMaxValue) {
this.cache = cache;
this.transactionManager = cache.getAdvancedCache().getTransactionManager();
this.uniqueValuesSet = uniqueValuesSet;
this.lastValue = 0;
this.counterMaxValue = counterMaxValue;
}
@Override
public void run() {
int failuresCounter = 0;
while (lastValue < counterMaxValue && !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("counter");
value = value + 1;
lastValue = value;
cache.put("counter", value);
transactionManager.commit();
success = true;
boolean unique = uniqueValuesSet.add(value);
assertTrue("Duplicate value found (value=" + lastValue + ")", unique);
} catch (Exception e) {
// expected exception
failuresCounter++;
assertTrue("Too many failures incrementing the counter", failuresCounter < 10 * counterMaxValue);
} 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);
}
}
}
}
}
}
}