package org.infinispan.api; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.Callable; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.infinispan.AdvancedCache; import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.remoting.transport.Address; import org.infinispan.test.MultipleCacheManagersTest; import org.testng.annotations.Test; /** * @author Mircea Markus * @since 5.2 */ @Test(groups = "functional", testName = "api.ConcurrentOperationsTest") public class ConcurrentOperationsTest extends MultipleCacheManagersTest { protected final int threads; protected final int nodes; protected final int operations; public ConcurrentOperationsTest(int threads, int nodes, int operations) { this.threads = threads; this.nodes = nodes; this.operations = operations; } public ConcurrentOperationsTest() { this(2, 2, 4); } @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder dcc = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, false); dcc.clustering().l1().disable(); createClusteredCaches(nodes, dcc); } public void testNoTimeout() throws Throwable { runTest(false); } public void testNoTimeoutAndCorrectness() throws Throwable { runTest(true); } private void runTest(final boolean checkCorrectness) throws Throwable { final CyclicBarrier barrier = new CyclicBarrier(threads); final Random rnd = new Random(); final AtomicBoolean correctness = new AtomicBoolean(Boolean.TRUE); List<Future<Boolean>> result = new ArrayList<Future<Boolean>>(); for (int t = 0; t < threads; t++) { final int part = t; Future<Boolean> f = fork(new Callable<Boolean>() { @Override public Boolean call() throws Exception { try { for (int i = 0; i < operations; i++) { barrier(); executeOperation(i); barrier(); checkCorrectness(i); printProgress(i); if (!correctness.get()) break; } } catch (Throwable t) { correctness.set(false); throw new Exception(t); } return correctness.get(); } private void printProgress(int i) { if (i % 100 == 0) print("Progressing = " + i); } private void executeOperation(int iteration) { int node = rnd.nextInt(nodes - 1); switch (rnd.nextInt(4)) { case 0: { cache(node).put("k", "v_" + part + "_" + iteration); break; } case 1: { cache(node).remove("k"); break; } case 2: { cache(node).putIfAbsent("k", "v" + part); break; } case 3: { cache(node).replace("k", "v" + part); break; } default: throw new IllegalStateException(); } } private void checkCorrectness(int i) { if (checkCorrectness) { log.tracef("Checking correctness for iteration %s", i); print("Checking correctness"); List<Address> owners = advancedCache(0).getDistributionManager().locate("k"); assert owners.size() == 2; InternalCacheEntry entry0 = advancedCache(owners.get(0)).getDataContainer().get("k"); InternalCacheEntry entry1 = advancedCache(owners.get(1)).getDataContainer().get("k"); Object mainOwnerValue = entry0 == null ? null : entry0.getValue(); Object otherOwnerValue = entry1 == null ? null : entry1.getValue(); log.tracef("Main owner value is %s, other Owner Value is %s", mainOwnerValue, otherOwnerValue); boolean equals = mainOwnerValue == null? otherOwnerValue == null : mainOwnerValue.equals(otherOwnerValue); if (!equals) { print("Consistency error. On main owner(" + owners.get(0) + ") we had " + mainOwnerValue + " and on backup owner(" + owners.get(1) + ") we had " + otherOwnerValue); log.trace("Consistency error. On main owner(" + owners.get(0) + ") we had " + mainOwnerValue + " and on backup owner(" + owners.get(1) + ") we had " + otherOwnerValue); correctness.set(false); return; } print("otherOwnerValue = " + otherOwnerValue); print("mainOwnerValue = " + mainOwnerValue); for (int q = 0; q < nodes; q++) { print(q, cache(0).get("k")); } Object expectedValue = cache(0).get("k"); log.tracef("Original value read from cache 0 is %s", expectedValue); for (int j = 0; j < nodes; j++) { Object actualValue = cache(j).get("k"); boolean areEquals = expectedValue == null ? actualValue == null : expectedValue.equals(actualValue); print("Are " + actualValue + " and " + expectedValue + " equals ? " + areEquals); if (!areEquals) { correctness.set(false); print("Consistency error. On cache 0 we had " + expectedValue + " and on " + j + " we had " + actualValue); log.trace("Consistency error. On cache 0 we had " + expectedValue + " and on " + j + " we had " + actualValue); } } } } private void barrier() throws BrokenBarrierException, java.util.concurrent.TimeoutException, InterruptedException { barrier.await(10000, TimeUnit.MILLISECONDS); log.tracef("Just passed barrier."); } }); result.add(f); } for (Future<Boolean> f: result) { assertTrue(f.get()); } } private AdvancedCache advancedCache(Address address) { for (Cache c : caches()) { if (c.getAdvancedCache().getRpcManager().getAddress().equals(address)) return c.getAdvancedCache(); } throw new IllegalStateException("Couldn't find cache for address : " + address); } private void print(int index, Object value) { print("[" + Thread.currentThread().getName() + "] Cache " + index + " sees value " + value); } private void print(Object value) { log.debug(value); } public void testReplace() { cache(0).put("k", "v1"); for (int i = 0; i < nodes; i++) { assertEquals("v1", cache(i).get("k")); } assert cache(0).replace("k", "v2") != null; assert cache(0).replace("k", "v2", "v3"); assertEquals(cache(0).get("k"), "v3"); } }