package org.infinispan.tx.recovery.admin; import static org.infinispan.tx.recovery.RecoveryTestUtil.beginAndSuspendTx; import static org.infinispan.tx.recovery.RecoveryTestUtil.commitTransaction; import static org.infinispan.tx.recovery.RecoveryTestUtil.prepareTransaction; import static org.infinispan.tx.recovery.RecoveryTestUtil.rollbackTransaction; import static org.testng.Assert.assertEquals; import org.infinispan.commands.tx.CommitCommand; import org.infinispan.commands.tx.RollbackCommand; import org.infinispan.commons.CacheException; 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.interceptors.impl.InvocationContextInterceptor; import org.infinispan.test.fwk.CleanupAfterMethod; import org.infinispan.transaction.impl.TransactionTable; import org.infinispan.transaction.lookup.EmbeddedTransactionManagerLookup; import org.infinispan.transaction.tm.EmbeddedTransaction; import org.testng.annotations.Test; /** * This test makes sure that when a transaction fails during commit it is reported as in-doubt transaction. * * @author Mircea Markus * @since 5.0 */ @Test(groups = "functional", testName = "tx.recovery.admin.InDoubtWithCommitFailsTest") @CleanupAfterMethod public class InDoubtWithCommitFailsTest extends AbstractRecoveryTest { @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder configuration = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, true); configuration.transaction().transactionManagerLookup(new EmbeddedTransactionManagerLookup()) .useSynchronization(false) .recovery().enable() .locking().useLockStriping(false) .clustering().hash().numOwners(2) .clustering().l1().disable().stateTransfer().fetchInMemoryState(false); createCluster(configuration, 2); waitForClusterToForm(); advancedCache(1).getAsyncInterceptorChain().addInterceptorBefore(new ForceFailureInterceptor(), InvocationContextInterceptor.class); } public void testRecoveryInfoListCommit() throws Exception { test(true); } public void testRecoveryInfoListRollback() throws Exception { test(false); } private void test(boolean commit) { assert recoveryOps(0).showInDoubtTransactions().isEmpty(); TransactionTable tt0 = cache(0).getAdvancedCache().getComponentRegistry().getComponent(TransactionTable.class); EmbeddedTransaction dummyTransaction = beginAndSuspendTx(cache(0)); prepareTransaction(dummyTransaction); assert tt0.getLocalTxCount() == 1; try { if (commit) { commitTransaction(dummyTransaction); } else { rollbackTransaction(dummyTransaction); } assert false : "exception expected"; } catch (Exception e) { //expected } assertEquals(tt0.getLocalTxCount(), 1); assertEquals(countInDoubtTx(recoveryOps(0).showInDoubtTransactions()), 1); assertEquals(countInDoubtTx(recoveryOps(1).showInDoubtTransactions()), 1); } public static class ForceFailureInterceptor extends CommandInterceptor { public boolean fail = true; @Override public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { fail(); return super.visitCommitCommand(ctx, command); } private void fail() { if (fail) throw new CacheException("Induced failure"); } @Override public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command) throws Throwable { fail(); return super.visitRollbackCommand(ctx, command); } } }