package org.infinispan.distribution.rehash; import static org.testng.AssertJUnit.assertEquals; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.infinispan.Cache; import org.infinispan.commons.util.CollectionFactory; 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.test.fwk.CleanupAfterMethod; import org.infinispan.transaction.TransactionMode; import org.testng.annotations.Test; /** * Tests data loss during state transfer when the originator of a put operation becomes the primary owner of the * modified key. See https://issues.jboss.org/browse/ISPN-3357 * * @author Dan Berindei */ @Test(groups = "functional", testName = "distribution.rehash.NonTxPutIfAbsentDuringLeaveStressTest") @CleanupAfterMethod public class NonTxPutIfAbsentDuringLeaveStressTest extends MultipleCacheManagersTest { private static final int NUM_WRITERS = 4; private static final int NUM_ORIGINATORS = 2; private static final int NUM_KEYS = 100; @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder c = getConfigurationBuilder(); addClusterEnabledCacheManager(c); addClusterEnabledCacheManager(c); addClusterEnabledCacheManager(c); addClusterEnabledCacheManager(c); addClusterEnabledCacheManager(c); waitForClusterToForm(); } private ConfigurationBuilder getConfigurationBuilder() { ConfigurationBuilder c = new ConfigurationBuilder(); c.clustering().cacheMode(CacheMode.DIST_SYNC); c.transaction().transactionMode(TransactionMode.NON_TRANSACTIONAL); return c; } public void testNodeLeavingDuringPutIfAbsent() throws Exception { ConcurrentMap<String, String> insertedValues = CollectionFactory.makeConcurrentMap(); AtomicBoolean stop = new AtomicBoolean(false); Future[] futures = new Future[NUM_WRITERS]; for (int i = 0; i < NUM_WRITERS; i++) { final int writerIndex = i; futures[i] = fork(new Callable() { @Override public Object call() throws Exception { while (!stop.get()) { for (int j = 0; j < NUM_KEYS; j++) { Cache<Object, Object> cache = cache(writerIndex % NUM_ORIGINATORS); doPut(cache, "key_" + j, "value_" + j + "_" + writerIndex); } } return null; } private void doPut(Cache<Object, Object> cache, String key, String value) { Object oldValue = cache.putIfAbsent(key, value); Object newValue = cache.get(key); if (oldValue == null) { // succeeded log.tracef("Successfully inserted value %s for key %s", value, key); assertEquals(value, newValue); String duplicateInsertedValue = insertedValues.putIfAbsent(key, value); if (duplicateInsertedValue != null) { // ISPN-4286: two concurrent putIfAbsent operations can both return null assertEquals(value, duplicateInsertedValue); } } else { // failed if (newValue == null) { // ISPN-3918: cache.get(key) == null if another command succeeded but didn't finish eventuallyEquals(oldValue, () -> cache.get(key)); } else { assertEquals(oldValue, newValue); } } } }); } killMember(4); TestingUtil.waitForNoRebalance(caches()); killMember(3); TestingUtil.waitForNoRebalance(caches()); stop.set(true); for (int i = 0; i < NUM_WRITERS; i++) { futures[i].get(10, TimeUnit.SECONDS); for (int j = 0; j < NUM_KEYS; j++) { for (int k = 0; k < caches().size(); k++) { String key = "key_" + j; assertEquals(insertedValues.get(key), cache(k).get(key)); } } } } }