package org.infinispan.statetransfer;
import static org.infinispan.test.concurrent.StateSequencerUtil.advanceOnInterceptor;
import static org.infinispan.test.concurrent.StateSequencerUtil.matchCommand;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;
import java.util.Arrays;
import org.infinispan.Cache;
import org.infinispan.commands.tx.RollbackCommand;
import org.infinispan.commands.tx.VersionedPrepareCommand;
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.concurrent.StateSequencer;
import org.infinispan.test.fwk.CleanupAfterMethod;
import org.infinispan.transaction.TransactionMode;
import org.infinispan.transaction.xa.GlobalTransaction;
import org.infinispan.util.ControlledConsistentHashFactory;
import org.infinispan.util.concurrent.locks.LockManager;
import org.testng.annotations.Test;
/**
* Test that a commit command that has timed out on a backup owner cannot write entries after the locks have been
* released on the primary owner.
*/
@Test(groups = "functional", testName = "statetransfer.PrepareTimeoutTest")
@CleanupAfterMethod
public class PrepareTimeoutTest extends MultipleCacheManagersTest {
private static final String TEST_KEY = "key";
private static final String TX1_VALUE = "value1";
private static final java.lang.Object TX2_VALUE = "value2";
public static final int COMPLETED_TX_TIMEOUT = 2000;
@Override
protected void createCacheManagers() throws Throwable {
ControlledConsistentHashFactory consistentHashFactory = new ControlledConsistentHashFactory(1, 2);
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.clustering().cacheMode(CacheMode.DIST_SYNC);
builder.clustering().remoteTimeout(2000);
builder.clustering().hash().numSegments(1).consistentHashFactory(consistentHashFactory);
builder.transaction().transactionMode(TransactionMode.TRANSACTIONAL);
builder.transaction().completedTxTimeout(COMPLETED_TX_TIMEOUT);
addClusterEnabledCacheManager(builder);
addClusterEnabledCacheManager(builder);
addClusterEnabledCacheManager(builder);
waitForClusterToForm();
}
public void testCommitDoesntWriteAfterRollback() throws Exception {
// Start a tx on A: put(k, v1), owners(k) = [B (primary) and C (backup)]
// Block the prepare on B and C so that it times out
// Wait for the rollback command to be executed on B and C
// Unblock the prepare on B and C
// Check that there are no locked keys or remote transactions on B and C
StateSequencer sequencer = new StateSequencer();
sequencer.logicalThread("main", "main:start", "main:check");
sequencer.logicalThread("primary", "primary:block_prepare", "primary:after_rollback", "primary:resume_prepare",
"primary:after_prepare");
sequencer.logicalThread("backup", "backup:block_prepare", "backup:after_rollback", "backup:resume_prepare",
"backup:after_prepare");
sequencer.order("main:start", "primary:block_prepare", "primary:after_prepare", "main:check");
sequencer.order("main:start", "backup:block_prepare", "backup:after_prepare", "main:check");
advanceOnInterceptor(sequencer, cache(1), StateTransferInterceptor.class,
matchCommand(VersionedPrepareCommand.class).matchCount(0).build())
.before("primary:block_prepare", "primary:resume_prepare").after("primary:after_prepare");
advanceOnInterceptor(sequencer, cache(1), StateTransferInterceptor.class,
matchCommand(RollbackCommand.class).build())
.after("primary:after_rollback");
advanceOnInterceptor(sequencer, cache(2), StateTransferInterceptor.class,
matchCommand(VersionedPrepareCommand.class).matchCount(0).build())
.before("backup:block_prepare", "backup:resume_prepare").after("backup:after_prepare");
advanceOnInterceptor(sequencer, cache(2), StateTransferInterceptor.class,
matchCommand(RollbackCommand.class).build())
.after("backup:after_rollback");
assertEquals(Arrays.asList(address(1), address(2)), advancedCache(0).getDistributionManager().locate(TEST_KEY));
sequencer.advance("main:start");
tm(0).begin();
cache(0).put(TEST_KEY, TX1_VALUE);
try {
tm(0).commit();
fail("Exception expected during commit");
} catch (Exception e) {
// expected
}
tm(0).begin();
cache(0).put(TEST_KEY, TX2_VALUE);
GlobalTransaction gtx1 = transactionTable(0).getLocalTransaction(tm(0).getTransaction()).getGlobalTransaction();
tm(0).commit();
// Wait for the 1st tx to be removed from the completed txs table
Thread.sleep(COMPLETED_TX_TIMEOUT + 1000);
assertTrue(transactionTable(1).isTransactionCompleted(gtx1));
assertTrue(transactionTable(2).isTransactionCompleted(gtx1));
sequencer.advance("main:check");
LockManager lockManager1 = TestingUtil.extractLockManager(cache(1));
assertFalse(lockManager1.isLocked(TEST_KEY));
assertFalse(transactionTable(1).containRemoteTx(gtx1));
assertFalse(transactionTable(2).containRemoteTx(gtx1));
for (Cache cache : caches()) {
assertEquals(TX2_VALUE, cache.get(TEST_KEY));
}
}
}