package org.infinispan.tx; import static org.testng.Assert.assertEquals; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import javax.transaction.RollbackException; import javax.transaction.SystemException; 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.transaction.LockingMode; import org.infinispan.transaction.impl.TransactionTable; import org.testng.annotations.Test; /** * Test for ISPN-2469. * * @author Carsten Lohmann */ @Test(groups = "functional", testName = "tx.LockAfterNodesLeftTest") public class LockAfterNodesLeftTest extends MultipleCacheManagersTest { private final int INITIAL_CLUSTER_SIZE = 6; private final int NUM_NODES_TO_STOP_FOR_TEST = 3; @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder cacheConfig = getDefaultClusteredCacheConfig(CacheMode.REPL_SYNC, true); cacheConfig.transaction().lockingMode(LockingMode.PESSIMISTIC); createClusteredCaches(INITIAL_CLUSTER_SIZE, cacheConfig); waitForClusterToForm(); } public void test() throws Exception { log.debug("Adding test key"); cache(0).put("k", "v"); // ensure that there are no transactions left for (int i = 0; i < INITIAL_CLUSTER_SIZE; i++) { final TransactionTable transactionTable = TestingUtil.getTransactionTable(cache(i)); eventually(new Condition() { @Override public boolean isSatisfied() throws Exception { return transactionTable.getLocalTransactions().isEmpty(); } }); eventually(new Condition() { @Override public boolean isSatisfied() throws Exception { return transactionTable.getRemoteTransactions().isEmpty(); } }); } TestingUtil.sleepThread(2000); log.debug("Shutting down some nodes .."); for (int i = 0; i < NUM_NODES_TO_STOP_FOR_TEST; i++) { cacheManagers.get(INITIAL_CLUSTER_SIZE - 1 - i).stop(); } log.debug("Shutdown completed"); final int remainingNodesCount = INITIAL_CLUSTER_SIZE - NUM_NODES_TO_STOP_FOR_TEST; TestingUtil.sleepThread(2000); // now do a parallel put on the cache final String key = "key"; final AtomicInteger errorCount = new AtomicInteger(); final AtomicInteger rolledBack = new AtomicInteger(); final CountDownLatch latch = new CountDownLatch(1); Thread[] threads = new Thread[remainingNodesCount]; for (int i = 0; i < remainingNodesCount; i++) { final int nodeIndex = i; threads[i] = new Thread("LockAfterNodesLeftTest.Putter-" + i) { public void run() { try { latch.await(); log.debug("about to begin transaction..."); tm(nodeIndex).begin(); try { log.debug("Getting lock on cache key"); cache(nodeIndex).getAdvancedCache().lock(key); log.debug("Got lock"); cache(nodeIndex).put(key, "value"); log.debug("Done with put"); TestingUtil.sleepRandom(200); tm(nodeIndex).commit(); } catch (Throwable e) { if (e instanceof RollbackException) { rolledBack.incrementAndGet(); } else if (tm(nodeIndex).getTransaction() != null) { // the TX is most likely rolled back already, but we attempt a rollback just in case it isn't try { tm(nodeIndex).rollback(); rolledBack.incrementAndGet(); } catch (SystemException e1) { log.error("Failed to rollback", e1); } } throw e; } } catch (Throwable e) { errorCount.incrementAndGet(); log.error(e); } } }; threads[i].start(); } latch.countDown(); for (Thread t : threads) { t.join(); } log.trace("Got errors: " + errorCount.get()); assertEquals(0, errorCount.get()); } }