/* * Copyright 2012 Red Hat, Inc. and/or its affiliates. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ package org.infinispan.container.versioning; import java.util.concurrent.ConcurrentSkipListSet; import javax.transaction.TransactionManager; import org.infinispan.Cache; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.cache.VersioningScheme; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.test.SingleCacheManagerTest; import org.infinispan.test.fwk.TestCacheManagerFactory; import org.infinispan.transaction.LockingMode; import org.infinispan.transaction.TransactionMode; import org.infinispan.transaction.lookup.DummyTransactionManagerLookup; import org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup; import org.infinispan.util.concurrent.IsolationLevel; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.Test; import static org.testng.AssertJUnit.assertTrue; @Test(groups = "functional", testName = "container.versioning.TransactionalLocalWriteSkewTest") public class TransactionalLocalWriteSkewTest extends SingleCacheManagerTest { @Override protected EmbeddedCacheManager createCacheManager() throws Exception { ConfigurationBuilder builder = new ConfigurationBuilder(); builder .transaction() .transactionManagerLookup(new DummyTransactionManagerLookup()) .transactionMode(TransactionMode.TRANSACTIONAL) .lockingMode(LockingMode.OPTIMISTIC).syncCommitPhase(true) .locking() .isolationLevel(IsolationLevel.REPEATABLE_READ) .writeSkewCheck(true) .versioning().enable().scheme(VersioningScheme.SIMPLE); return TestCacheManagerFactory.createCacheManager(builder); } public void testSharedCounter() throws Exception { int counterMaxValue = 1000; Cache<String, Integer> c1 = cacheManager.getCache("cache"); // initialize the counter c1.put("counter", 0); // check if the counter is initialized in all caches assertTrue(c1.get("counter") == 0); // this will keep the values put by both threads. any duplicate value // will be detected because of the // return value of add() method ConcurrentSkipListSet<Integer> uniqueValuesIncremented = new ConcurrentSkipListSet<Integer>(); // create both threads (simulate a node) IncrementCounterThread ict1 = new IncrementCounterThread("Node-1", c1, uniqueValuesIncremented, counterMaxValue); IncrementCounterThread ict2 = new IncrementCounterThread("Node-2", c1, uniqueValuesIncremented, counterMaxValue); // start and wait to finish ict1.start(); ict2.start(); ict1.join(); ict2.join(); // check if all caches obtains the counter_max_values assertTrue(c1.get("counter") >= counterMaxValue); // check is any duplicate value is detected assertTrue(ict1.result); assertTrue(ict2.result); } private static class IncrementCounterThread extends Thread { Log log = LogFactory.getLog(IncrementCounterThread.class); private Cache<String, Integer> cache; private ConcurrentSkipListSet<Integer> uniqueValuesSet; private TransactionManager transactionManager; private int lastValue; private boolean result = true; private int counterMaxValue; public IncrementCounterThread(String name, Cache<String, Integer> cache, ConcurrentSkipListSet<Integer> uniqueValuesSet, int counterMaxValue) { super(name); this.cache = cache; this.transactionManager = cache.getAdvancedCache() .getTransactionManager(); this.uniqueValuesSet = uniqueValuesSet; this.lastValue = 0; this.counterMaxValue = counterMaxValue; } @Override public void run() { while (lastValue < counterMaxValue) { try { Integer value = 0; try { // start transaction, get the counter value, increment // and put it again // check for duplicates in case of success transactionManager.begin(); value = cache.get("counter"); value = value + 1; lastValue = value; cache.put("counter", value); transactionManager.commit(); result = result && uniqueValuesSet.add(value); log.warnf("Add value=%s, result is %b", value, result); } catch (Throwable t) { log.errorf("Exception with value=%d", value); // lets rollback transactionManager.rollback(); } } catch (Throwable t) { // the only possible exception is thrown by the rollback. // just ignore it } } } } }