package org.infinispan.tx;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.fail;
import java.util.concurrent.CountDownLatch;
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.interceptors.base.CommandInterceptor;
import org.infinispan.test.MultipleCacheManagersTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.InCacheMode;
import org.infinispan.transaction.lookup.EmbeddedTransactionManagerLookup;
import org.infinispan.util.concurrent.TimeoutException;
import org.infinispan.util.mocks.ControlledCommandFactory;
import org.testng.annotations.Test;
@Test(testName = "tx.RollbackBeforePrepareTest", groups = "functional")
@InCacheMode({CacheMode.DIST_SYNC, CacheMode.REPL_SYNC})
public class RollbackBeforePrepareTest extends MultipleCacheManagersTest {
public static final long REPL_TIMEOUT = 1000;
public static final long LOCK_TIMEOUT = 500;
private FailPrepareInterceptor failPrepareInterceptor;
protected int numOwners = 3;
@Override
protected void createCacheManagers() throws Throwable {
ConfigurationBuilder config = getDefaultClusteredCacheConfig(cacheMode, true);
config
.locking().lockAcquisitionTimeout(LOCK_TIMEOUT)
.clustering().remoteTimeout(REPL_TIMEOUT)
.clustering().hash().numOwners(numOwners)
.transaction().transactionManagerLookup(new EmbeddedTransactionManagerLookup())
.transaction().completedTxTimeout(3600000);
createCluster(config, 3);
waitForClusterToForm();
failPrepareInterceptor = new FailPrepareInterceptor();
advancedCache(2).addInterceptor(failPrepareInterceptor, 1);
}
public void testCommitNotSentBeforeAllPrepareAreAck() throws Exception {
ControlledCommandFactory ccf = ControlledCommandFactory.registerControlledCommandFactory(cache(1), PrepareCommand.class);
ccf.gate.close();
try {
cache(0).put("k", "v");
fail();
} catch (Exception e) {
//expected
}
//this will also cause a replication timeout
allowRollbackToRun();
ccf.gate.open();
//give some time for the prepare to execute
Thread.sleep(3000);
eventually(new Condition() {
@Override
public boolean isSatisfied() throws Exception {
int remoteTxCount0 = TestingUtil.getTransactionTable(cache(0)).getRemoteTxCount();
int remoteTxCount1 = TestingUtil.getTransactionTable(cache(1)).getRemoteTxCount();
int remoteTxCount2 = TestingUtil.getTransactionTable(cache(2)).getRemoteTxCount();
log.tracef("remote0=%s, remote1=%s, remote2=%s", remoteTxCount0, remoteTxCount1, remoteTxCount2);
return remoteTxCount0 == 0 && remoteTxCount1 == 0 && remoteTxCount2 == 0;
}
});
assertNull(cache(0).get("k"));
assertNull(cache(1).get("k"));
assertNull(cache(2).get("k"));
assertNotLocked("k");
}
/**
* by using timeouts here the worse case is to have false positives, i.e. the test to pass when it shouldn't. no
* false negatives should be possible. In single threaded suit runs this test will generally fail in order
* to highlight a bug.
*/
private static void allowRollbackToRun() throws InterruptedException {
Thread.sleep(REPL_TIMEOUT * 15);
}
public static class FailPrepareInterceptor extends CommandInterceptor {
CountDownLatch failureFinish = new CountDownLatch(1);
@Override
public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable {
try {
throw new TimeoutException("Induced!");
} finally {
failureFinish.countDown();
}
}
}
}