package org.infinispan.tx.totalorder;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.transaction.RollbackException;
import org.infinispan.Cache;
import org.infinispan.commands.tx.PrepareCommand;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.distribution.MagicKey;
import org.infinispan.interceptors.AsyncInterceptorChain;
import org.infinispan.interceptors.BaseCustomAsyncInterceptor;
import org.infinispan.test.MultipleCacheManagersTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.transaction.TransactionProtocol;
import org.infinispan.transaction.totalorder.TotalOrderManager;
import org.infinispan.util.concurrent.IsolationLevel;
import org.testng.annotations.Test;
/**
* Tests if the locks are cleanup after a TimeoutException
*
* @author Pedro Ruivo
* @since 6.0
*/
@Test(groups = "functional", testName = "tx.totalorder.CleanupAfterFailTest")
public class CleanupAfterFailTest extends MultipleCacheManagersTest {
public void testTimeoutCleanup() throws Exception {
final CountDownLatch block = new CountDownLatch(1);
final BaseCustomAsyncInterceptor interceptor = new BaseCustomAsyncInterceptor() {
@Override
public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable {
block.await();
return invokeNext(ctx, command);
}
};
final AsyncInterceptorChain chain = TestingUtil.extractComponent(cache(1), AsyncInterceptorChain.class);
final Object key = new MagicKey(cache(1));
try {
chain.addInterceptor(interceptor, 0);
tm(0).begin();
cache(0).put(key, "v");
tm(0).commit();
fail("Rollback expected!");
} catch (RollbackException e) {
//expected
} finally {
block.countDown();
chain.removeInterceptor(0);
}
assertNoTransactions();
assertNoLocks();
}
public void testTimeoutCleanupInLocalNode() throws Exception {
final CountDownLatch block = new CountDownLatch(1);
final BaseCustomAsyncInterceptor interceptor = new BaseCustomAsyncInterceptor() {
@Override
public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command)
throws Throwable {
if (!ctx.isOriginLocal()) {
block.await();
}
return invokeNext(ctx, command);
}
};
final AsyncInterceptorChain chain = TestingUtil.extractComponent(cache(0), AsyncInterceptorChain.class);
final Object key1 = new MagicKey(cache(0));
final Object key2 = new MagicKey(cache(1));
try {
chain.addInterceptor(interceptor, 0);
tm(0).begin();
cache(0).put(key1, "v1");
cache(0).put(key2, "v2");
tm(0).commit();
fail("Rollback expected!");
} catch (RollbackException e) {
//expected
} finally {
block.countDown();
chain.removeInterceptor(0);
}
cache(0).put(key1, "v3");
cache(0).put(key2, "v4");
assertCacheValue(key1, "v3");
assertCacheValue(key2, "v4");
assertNoTransactions();
assertNoLocks();
}
@Override
protected final void createCacheManagers() throws Throwable {
ConfigurationBuilder dcc = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, true);
dcc.transaction()
.transactionProtocol(TransactionProtocol.TOTAL_ORDER)
.useSynchronization(false)
.recovery().disable();
dcc.locking().isolationLevel(IsolationLevel.REPEATABLE_READ);
dcc.clustering().hash()
.numOwners(1)
.numSegments(60);
dcc.clustering().remoteTimeout(1, TimeUnit.SECONDS);
createCluster(dcc, 2);
waitForClusterToForm();
}
private void assertCacheValue(Object key, Object value) {
for (Cache cache : caches()) {
assertEquals(cache.get(key), value, "Wrong value for cache " + address(cache) + ". key=" + key);
}
}
private void assertNoLocks() {
eventually(() -> {
for (Cache cache : caches()) {
if (TestingUtil.extractComponent(cache, TotalOrderManager.class).hasAnyLockAcquired()) {
return false;
}
}
return true;
});
}
}