package org.infinispan.persistence; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.infinispan.commons.marshall.StreamingMarshaller; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.factories.ComponentRegistry; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.marshall.core.MarshalledEntryImpl; import org.infinispan.metadata.InternalMetadata; import org.infinispan.persistence.manager.PersistenceManager; import org.infinispan.persistence.manager.PersistenceManagerImpl; import org.infinispan.persistence.spi.AdvancedCacheLoader; import org.infinispan.persistence.spi.AdvancedCacheWriter; import org.infinispan.test.SingleCacheManagerTest; import org.infinispan.test.TestingUtil; import org.infinispan.test.fwk.TestCacheManagerFactory; import org.infinispan.util.concurrent.WithinThreadExecutor; import org.testng.annotations.Test; /** * @author Mircea Markus * @since 6.0 */ @Test (groups = "functional", testName = "persistence.ParallelIterationTest") public abstract class ParallelIterationTest extends SingleCacheManagerTest { private static final int NUM_THREADS = 10; private static final int NUM_ENTRIES = 200; protected AdvancedCacheLoader loader; protected AdvancedCacheWriter writer; protected ExecutorService executor; protected StreamingMarshaller sm; @Override protected EmbeddedCacheManager createCacheManager() throws Exception { ConfigurationBuilder cb = getDefaultStandaloneCacheConfig(false); configurePersistence(cb); EmbeddedCacheManager manager = TestCacheManagerFactory.createCacheManager(cb); ComponentRegistry componentRegistry = manager.getCache().getAdvancedCache().getComponentRegistry(); PersistenceManagerImpl pm = (PersistenceManagerImpl) componentRegistry.getComponent(PersistenceManager.class); sm = pm.getMarshaller(); loader = TestingUtil.getFirstLoader(manager.getCache()); writer = TestingUtil.getFirstWriter(manager.getCache()); executor = new ThreadPoolExecutor(NUM_THREADS, NUM_THREADS, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), getTestThreadFactory("iteration"), new ThreadPoolExecutor.CallerRunsPolicy()); return manager; } @Override protected void teardown() { super.teardown(); if (executor != null) { executor.shutdownNow(); } } protected abstract void configurePersistence(ConfigurationBuilder cb); public void testParallelIterationWithValueAndMetadata() { runIterationTest(executor, true, true); } public void testParallelIterationWithValueWithoutMetadata() { runIterationTest(executor, true, false); } public void testSequentialIterationWithValueAndMetadata() { runIterationTest(new WithinThreadExecutor(), true, true); } public void testSequentialIterationWithValueWithoutMetadata() { runIterationTest(new WithinThreadExecutor(), true, false); } public void testParallelIterationWithoutValueWithMetadata() { runIterationTest(executor, false, true); } public void testParallelIterationWithoutValueOrMetadata() { runIterationTest(executor, false, false); } public void testSequentialIterationWithoutValueWithMetadata() { runIterationTest(new WithinThreadExecutor(), false, true); } public void testSequentialIterationWithoutValueOrMetadata() { runIterationTest(new WithinThreadExecutor(), false, false); } public void testCancelingTaskMultipleProcessors() { insertData(); final ConcurrentMap<Object, Object> entries = new ConcurrentHashMap<>(); final AtomicBoolean stopped = new AtomicBoolean(false); loader.process(null, (marshalledEntry, taskContext) -> { synchronized (entries) { boolean shouldStop = entries.size() == 100 && !stopped.get(); log.trace("shouldStop = " + shouldStop + ",entries size = " + entries.size()); if (shouldStop) { stopped.set(true); taskContext.stop(); return; } entries.put(unwrapKey(marshalledEntry.getKey()), unwrapValue(marshalledEntry.getValue())); } }, executor, true, true); assertTrue(stopped.get()); assertTrue(entries.size() <= 100 + NUM_THREADS, "got " + entries.size() + " elements, expected less than " + (100 + NUM_THREADS)); assertTrue(entries.size() >= 100); } private void runIterationTest(Executor executor, final boolean fetchValues, boolean fetchMetadata) { final ConcurrentMap<Integer, Integer> entries = new ConcurrentHashMap<>(); final ConcurrentMap<Integer, InternalMetadata> metadata = new ConcurrentHashMap<>(); final AtomicBoolean sameKeyMultipleTimes = new AtomicBoolean(); final AtomicInteger processed = new AtomicInteger(); final AtomicBoolean brokenBarrier = new AtomicBoolean(false); assertEquals(loader.size(), 0); insertData(); loader.process(null, (marshalledEntry, taskContext) -> { int key = unwrapKey(marshalledEntry.getKey()); if (fetchValues) { // Note: MarshalledEntryImpl.getValue() fails with NPE when it's got null valueBytes, // that's why we must not call this when values are not retrieved Integer existing = entries.put(key, unwrapValue(marshalledEntry.getValue())); if (existing != null) { log.warnf("Already a value present for key %s: %s", key, existing); sameKeyMultipleTimes.set(true); } } if (marshalledEntry.getMetadata() != null) { log.tracef("For key %d found metadata %s", key, marshalledEntry.getMetadata()); InternalMetadata prevMetadata = metadata.put(key, marshalledEntry.getMetadata()); if (prevMetadata != null) { log.warnf("Already a metadata present for key %s: %s", key, prevMetadata); sameKeyMultipleTimes.set(true); } } else { log.tracef("No metadata found for key %d", key); } processed.incrementAndGet(); }, executor, fetchValues, fetchMetadata); assertFalse(sameKeyMultipleTimes.get()); assertFalse(brokenBarrier.get()); assertEquals(processed.get(), NUM_ENTRIES); for (int i = 0; i < NUM_ENTRIES; i++) { if (fetchValues) { assertEquals(entries.get(i), (Integer) i, "For key " + i); } else { assertNull(entries.get(i), "For key " + i); } if (fetchMetadata && hasMetadata(i)) { assertNotNull(metadata.get(i), "For key " + i); assertEquals(metadata.get(i).lifespan(), lifespan(i), "For key " + i); assertEquals(metadata.get(i).maxIdle(), maxIdle(i), "For key " + i); } else { assertMetadataEmpty(metadata.get(i)); } } } private void insertData() { for (int i = 0; i < NUM_ENTRIES; i++) { MarshalledEntryImpl me = new MarshalledEntryImpl(wrapKey(i), wrapValue(i, i), insertMetadata(i) ? TestingUtil.internalMetadata(lifespan(i), maxIdle(i)) : null, sm); writer.write(me); } } protected void assertMetadataEmpty(InternalMetadata metadata) { assertNull(metadata); } protected boolean insertMetadata(int i) { return i % 2 == 0; } protected boolean hasMetadata(int i) { return insertMetadata(i); } protected long lifespan(int i) { return 1000L * (i + 1000); } protected long maxIdle(int i) { return 10000L * (i + 1000); } protected Object wrapKey(int key) { return key; } protected Integer unwrapKey(Object key) { return (Integer) key; } protected Object wrapValue(int key, int value) { return value; } protected Integer unwrapValue(Object value) { return (Integer) value; } }