package org.infinispan.client.hotrod; import static org.infinispan.server.hotrod.test.HotRodTestingUtil.hotRodCacheConfiguration; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertNull; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.infinispan.client.hotrod.test.MultiHotRodServersTest; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.Test; @Test(groups = "stress", testName = "client.hotrod.ReplaceWithVersionConcurrencyTest", timeOut = 15*60*1000) public class ReplaceWithVersionConcurrencyTest extends MultiHotRodServersTest { static final AtomicInteger globalCounter = new AtomicInteger(); static final String KEY = "A"; static final int NUM_THREADS = 20; static final int OPS_PER_THREAD = 200; static final int TIMEOUT_MINUTES = 5; @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder builder = hotRodCacheConfiguration( getDefaultClusteredCacheConfig(CacheMode.REPL_SYNC, false)); // .transaction() // .lockingMode(LockingMode.PESSIMISTIC) // .transactionMode(TransactionMode.TRANSACTIONAL); createHotRodServers(2, builder); } public void testKeepingCounterWithReplaceWithVersion() throws Exception { RemoteCache<String, Integer> cache = client(0).getCache(); assertNull(cache.get(KEY)); long timeSpent = System.currentTimeMillis(); List<Future<Integer>> results = new ArrayList<Future<Integer>>(NUM_THREADS); ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS, getTestThreadFactory("Worker")); for (int i = 0; i < NUM_THREADS; i++) { CounterUpdater app = new CounterUpdater(cache, KEY, OPS_PER_THREAD); Future<Integer> result = executor.submit(app); results.add(result); } executor.shutdown(); executor.awaitTermination(TIMEOUT_MINUTES, TimeUnit.MINUTES); timeSpent = System.currentTimeMillis() - timeSpent; int actual = cache.get(KEY); // server-side value int expected = 0; // client-side value for (Future<Integer> f : results) expected += f.get(); log.info("Time spent: " + timeSpent / 1000.0 + " secs."); assertEquals(expected, actual); } static class CounterUpdater implements Callable<Integer> { static final Log log = LogFactory.getLog(CounterUpdater.class); final RemoteCache<String, Integer> cache; final String key; final int limit; CounterUpdater(RemoteCache<String, Integer> cache, String key, int limit) { this.cache = cache; this.key = key; this.limit = limit; } @Override public Integer call() throws Exception { int counter = 0; log.info("Start to count."); int i = 0; while (i < limit) { incrementCounter(); counter++; i++; } log.info("Counted " + counter); return counter; } private void incrementCounter() { while (true) { VersionedValue<Integer> versioned = cache.getVersioned(key); if (versioned == null) { if (cache.withFlags(Flag.FORCE_RETURN_VALUE).putIfAbsent(key, 1) == null) { log.info("count=" + globalCounter.getAndIncrement() + ",prev=0,new=1 (first-put)"); return; } } else { int val = versioned.getValue() + 1; long version = versioned.getVersion(); if (cache.replaceWithVersion(key, val, version)) { int count = globalCounter.getAndIncrement(); log.info("count=" + count +",prev=" + versioned.getValue() + ",new=" + val + ",prev-version=" + version); // Optimistically updated, no more retries. return; } } } } } }