package org.infinispan.atomic;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;
import java.util.Arrays;
import java.util.HashSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
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.ValueFuture;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.transaction.LockingMode;
import org.infinispan.transaction.lookup.EmbeddedTransactionManagerLookup;
import org.infinispan.util.concurrent.IsolationLevel;
import org.infinispan.util.concurrent.TimeoutException;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import org.testng.annotations.Test;
/**
* Test class for AtomicHashMap.
*
* @author Mircea.Markus@jboss.com
* @author Dan Berindei
*/
@Test(groups = "functional", testName = "atomic.AtomicHashMapPessimisticConcurrencyTest")
public class AtomicHashMapPessimisticConcurrencyTest extends SingleCacheManagerTest {
private static final Log log = LogFactory.getLog(AtomicHashMapPessimisticConcurrencyTest.class);
public static final String KEY = "key";
private LockingMode lockingMode = LockingMode.PESSIMISTIC;
@Override
protected EmbeddedCacheManager createCacheManager() throws Exception {
ConfigurationBuilder builder = TestCacheManagerFactory.getDefaultCacheConfiguration(true);
builder.locking().lockAcquisitionTimeout(TestingUtil.shortTimeoutMillis());
builder.invocationBatching().enable();
builder.locking().isolationLevel(IsolationLevel.READ_COMMITTED);
builder.transaction().transactionManagerLookup(new EmbeddedTransactionManagerLookup()).lockingMode(lockingMode);
return TestCacheManagerFactory.createCacheManager(builder);
}
public void testConcurrentCreate() throws Exception {
tm().begin();
AtomicMapLookup.getAtomicMap(cache, KEY);
final AtomicBoolean gotTimeoutException = new AtomicBoolean();
fork(() -> {
try {
tm().begin();
try {
AtomicMapLookup.getAtomicMap(cache, KEY);
} catch (TimeoutException e) {
// this is the exception we were expecting
gotTimeoutException.set(true);
} finally {
tm().rollback();
}
} catch (Exception e) {
log.error("Unexpected error performing transaction", e);
}
}).get(10, TimeUnit.SECONDS);
assert gotTimeoutException.get();
}
public void testLockTimeout() throws Exception {
AtomicMap<Integer, String> atomicMap = AtomicMapLookup.getAtomicMap(cache, KEY);
tm().begin();
atomicMap.put(1, "");
Future<Object> future = fork(() -> {
tm().begin();
try {
AtomicMap<Integer, String> otMap = AtomicMapLookup.getAtomicMap(cache, KEY);
otMap.put(1, "val");
} finally {
tm().rollback();
}
return null;
});
try {
future.get(10, TimeUnit.SECONDS);
fail("Should have failed with a TimeoutException");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof TimeoutException);
}
}
public void testConcurrentPut() throws Exception {
final CountDownLatch readLatch = new CountDownLatch(1);
final CountDownLatch commitLatch = new CountDownLatch(1);
AtomicMap<Integer, String> atomicMap = AtomicMapLookup.getAtomicMap(cache, KEY);
tm().begin();
atomicMap.put(1, "value1");
Future<Object> future = fork(() -> {
try {
tm().begin();
AtomicMap<Integer, String> otMap = AtomicMapLookup.getAtomicMap(cache, KEY);
assertEquals(0, otMap.size());
readLatch.countDown();
otMap.put(2, "value2");
commitLatch.await(10, TimeUnit.SECONDS);
tm().commit();
} catch (Exception e) {
tm().rollback();
throw e;
}
return null;
});
readLatch.await(10, TimeUnit.SECONDS);
tm().commit();
commitLatch.countDown();
future.get(10, TimeUnit.SECONDS);
assertEquals(new HashSet<>(Arrays.asList(1, 2)), atomicMap.keySet());
}
public void testConcurrentRemove() throws Exception {
final CountDownLatch readLatch = new CountDownLatch(1);
final CountDownLatch commitLatch = new CountDownLatch(1);
AtomicMap<Integer, String> atomicMap = AtomicMapLookup.getAtomicMap(cache, KEY);
tm().begin();
atomicMap.put(1, "value1");
atomicMap.put(2, "value2");
atomicMap.put(3, "value3");
tm().commit();
tm().begin();
atomicMap.remove(1);
Future<Object> future = fork(() -> {
try {
tm().begin();
AtomicMap<Integer, String> otMap = AtomicMapLookup.getAtomicMap(cache, KEY);
assertEquals(3, otMap.size());
readLatch.countDown();
otMap.remove(2);
commitLatch.await(10, TimeUnit.SECONDS);
tm().commit();
} catch (Exception e) {
tm().rollback();
throw e;
}
return null;
});
readLatch.await(10, TimeUnit.SECONDS);
tm().commit();
commitLatch.countDown();
future.get(10, TimeUnit.SECONDS);
assertEquals(new HashSet<>(Arrays.asList(3)), atomicMap.keySet());
}
public void testReadAfterTxStarted() throws Exception {
AtomicMap<Integer, String> atomicMap = AtomicMapLookup.getAtomicMap(cache, KEY);
atomicMap.put(1, "existing");
tm().begin();
atomicMap.put(1, "newVal");
final ValueFuture responseBeforeCommit = new ValueFuture();
final ValueFuture responseAfterCommit = new ValueFuture();
final CountDownLatch commitLatch = new CountDownLatch(1);
Future<Object> future = fork(() -> {
tm().begin();
try {
AtomicMap<Integer, String> otMap = AtomicMapLookup.getAtomicMap(cache, KEY);
responseBeforeCommit.set(otMap.get(1));
// wait until the main thread commits the transaction
commitLatch.await();
responseAfterCommit.set(otMap.get(1));
} finally {
tm().rollback();
}
return null;
});
assertEquals("existing", responseBeforeCommit.get());
tm().commit();
commitLatch.countDown();
future.get(10, TimeUnit.SECONDS);
assertEquals("newVal", atomicMap.get(1));
assertEquals("newVal", responseAfterCommit.get());
}
}