package org.infinispan.persistence.support; import static org.infinispan.test.TestingUtil.withCacheManager; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.infinispan.Cache; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.eviction.EvictionStrategy; import org.infinispan.expiration.ExpirationManager; import org.infinispan.factories.AbstractNamedCacheComponentFactory; import org.infinispan.factories.AutoInstantiableFactory; import org.infinispan.factories.GlobalComponentRegistry; import org.infinispan.marshall.core.MarshalledEntry; import org.infinispan.persistence.async.AdvancedAsyncCacheWriter; import org.infinispan.persistence.async.AsyncCacheWriter; import org.infinispan.persistence.dummy.DummyInMemoryStore; import org.infinispan.persistence.dummy.DummyInMemoryStoreConfigurationBuilder; import org.infinispan.persistence.manager.PersistenceManager; import org.infinispan.persistence.manager.PersistenceManagerImpl; import org.infinispan.persistence.modifications.Modification; import org.infinispan.persistence.modifications.Remove; import org.infinispan.persistence.modifications.Store; import org.infinispan.persistence.spi.CacheWriter; import org.infinispan.persistence.spi.PersistenceException; import org.infinispan.test.AbstractInfinispanTest; import org.infinispan.test.CacheManagerCallable; import org.infinispan.test.TestingUtil; import org.infinispan.test.fwk.TestCacheManagerFactory; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.Test; /** * Functional tests of the async store when running associated with a cache instance. * * @author Galder ZamarreƱo * @since 5.2 */ @Test(groups = "functional", testName = "persistence.decorators.AsyncStoreFunctionalTest") public class AsyncStoreFunctionalTest extends AbstractInfinispanTest { private static final Log log = LogFactory.getLog(AsyncStoreFunctionalTest.class); public void testPutAfterPassivation() { ConfigurationBuilder builder = asyncStoreWithEvictionBuilder(); builder.persistence().passivation(true); withCacheManager(new CacheManagerCallable( TestCacheManagerFactory.createCacheManager(builder)) { @Override public void call() { // Hack the component metadata repository // to inject the custom cache loader manager GlobalComponentRegistry gcr = TestingUtil.extractGlobalComponentRegistry(cm); gcr.getComponentMetadataRepo().injectFactoryForComponent( PersistenceManager.class, CustomCacheLoaderManagerFactory.class); Cache<Integer, String> cache = cm.getCache(); MockAsyncCacheWriter cacheStore = TestingUtil.getFirstWriter(cache); CountDownLatch modApplyLatch = cacheStore.modApplyLatch; CountDownLatch lockedWaitLatch = cacheStore.lockedWaitLatch; // Store an entry in the cache cache.put(1, "v1"); // Store a second entry to force the previous entry // to be evicted and passivated cache.put(2, "v2"); try { // Wait for async store to have this modification queued up, // ready to apply it to the cache store... log.trace("Wait for async store to lock keys"); lockedWaitLatch.await(60, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } try { // Even though it's in the process of being passivated, // the entry should still be found in memory assertEquals("v1", cache.get(1)); } finally { modApplyLatch.countDown(); } } }); } public void testPutAfterEviction() { ConfigurationBuilder builder = asyncStoreWithEvictionBuilder(); withCacheManager(new CacheManagerCallable( TestCacheManagerFactory.createCacheManager(builder)) { @Override public void call() { // Hack the component metadata repository // to inject the custom cache loader manager GlobalComponentRegistry gcr = TestingUtil.extractGlobalComponentRegistry(cm); gcr.getComponentMetadataRepo().injectFactoryForComponent( PersistenceManager.class, CustomCacheLoaderManagerFactory.class); Cache<Integer, String> cache = cm.getCache(); MockAsyncCacheWriter cacheStore = TestingUtil.getFirstWriter(cache); CountDownLatch modApplyLatch = cacheStore.modApplyLatch; CountDownLatch lockedWaitLatch = cacheStore.lockedWaitLatch; // Store an entry in the cache cache.put(1, "v1"); try { // Wait for async store to have this modification queued up, // ready to apply it to the cache store... log.trace("Wait for async store to lock keys"); lockedWaitLatch.await(60, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // This shouldn't result in k=1 being evicted // because the k=1 put is queued in the async store cache.put(2, "v2"); try { assertEquals("v1", cache.get(1)); assertEquals("v2", cache.get(2)); } finally { modApplyLatch.countDown(); } } }); } public void testGetAfterRemove() throws Exception { ConfigurationBuilder builder = new ConfigurationBuilder(); builder.persistence() .addStore(DummyInMemoryStoreConfigurationBuilder.class) .async().enabled(true); withCacheManager(new CacheManagerCallable( TestCacheManagerFactory.createCacheManager(builder)) { @Override public void call() { // Hack the component metadata repository // to inject the custom cache loader manager GlobalComponentRegistry gcr = TestingUtil.extractGlobalComponentRegistry(cm); gcr.getComponentMetadataRepo().injectFactoryForComponent( PersistenceManager.class, CustomCacheLoaderManagerFactory.class); Cache<Integer, String> cache = cm.getCache(); MockAsyncCacheWriter cacheStore = TestingUtil.getFirstWriter(cache); CountDownLatch modApplyLatch = cacheStore.modApplyLatch; CountDownLatch lockedWaitLatch = cacheStore.lockedWaitLatch; // Store a value first cache.put(1, "skip"); // Wait until cache store contains the expected key/value pair ((DummyInMemoryStore) cacheStore.undelegate()) .blockUntilCacheStoreContains(1, "skip", 60000); // Remove it from the cache container cache.remove(1); try { // Wait for async store to have this modification queued up, // ready to apply it to the cache store... lockedWaitLatch.await(60, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } try { // Even though the remove it's pending, // the entry should not be retrieved assertEquals(null, cache.get(1)); } finally { modApplyLatch.countDown(); } ExpirationManager expirationManager = TestingUtil.extractComponent(cache, ExpirationManager.class); expirationManager.processExpiration(); Set<Integer> keys = cache.keySet(); assertTrue("Keys not empty: " + keys, keys.isEmpty()); Set<Map.Entry<Integer, String>> entries = cache.entrySet(); assertTrue("Entry set not empty: " + entries, entries.isEmpty()); Collection<String> values = cache.values(); assertTrue("Values not empty: " + values, values.isEmpty()); } }); } public void testClear() { ConfigurationBuilder builder = new ConfigurationBuilder(); builder.persistence() .addStore(DummyInMemoryStoreConfigurationBuilder.class) .async().enabled(true); withCacheManager(new CacheManagerCallable( TestCacheManagerFactory.createCacheManager(builder)) { @Override public void call() { Cache<Integer, String> cache = cm.getCache(); AdvancedAsyncCacheWriter asyncStore = TestingUtil.getFirstWriter(cache); DummyInMemoryStore dummyStore = TestingUtil.extractField(asyncStore, "actual"); cache.put(1, "uno"); cache.put(2, "dos"); cache.put(3, "tres"); eventually(new Condition() { @Override public boolean isSatisfied() throws Exception { return dummyStore.size() == 3; } }); cache.clear(); eventually(new Condition() { @Override public boolean isSatisfied() throws Exception { return dummyStore.size() == 0; } }); } }); } private ConfigurationBuilder asyncStoreWithEvictionBuilder() { ConfigurationBuilder builder = new ConfigurationBuilder(); // Emulate eviction with direct data container eviction builder.eviction().strategy(EvictionStrategy.LRU).maxEntries(1L) .persistence() .addStore(DummyInMemoryStoreConfigurationBuilder.class) .async().enabled(true); return builder; } public static class MockAsyncCacheWriter extends AsyncCacheWriter { private static final Log log = LogFactory.getLog(MockAsyncCacheWriter.class); private final CountDownLatch modApplyLatch; private final CountDownLatch lockedWaitLatch; public MockAsyncCacheWriter(CountDownLatch modApplyLatch, CountDownLatch lockedWaitLatch, CacheWriter delegate) { super(delegate); this.modApplyLatch = modApplyLatch; this.lockedWaitLatch = lockedWaitLatch; } @Override protected void applyModificationsSync(List<Modification> mods) throws PersistenceException { try { // Wait for signal to do the modification if (containsModificationForKey(1, mods) && !isSkip(findModificationForKey(1, mods))) { log.tracef("Wait to apply modifications: %s", mods); lockedWaitLatch.countDown(); modApplyLatch.await(60, TimeUnit.SECONDS); log.tracef("Apply modifications: %s", mods); } super.applyModificationsSync(mods); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } private boolean containsModificationForKey(Object key, List<Modification> mods) { return findModificationForKey(key, mods) != null; } private Modification findModificationForKey(Object key, List<Modification> mods) { for (Modification modification : mods) { switch (modification.getType()) { case STORE: Store store = (Store) modification; if (store.getKey().equals(key)) return store; break; case REMOVE: Remove remove = (Remove) modification; if (remove.getKey().equals(key)) return remove; break; default: return null; } } return null; } private boolean isSkip(Modification mod) { if (mod instanceof Store) { MarshalledEntry storedValue = ((Store) mod).getStoredValue(); return storedValue.getValue().equals("skip"); } return false; } } public static class CustomCacheLoaderManagerFactory extends AbstractNamedCacheComponentFactory implements AutoInstantiableFactory { @Override public <T> T construct(Class<T> componentType) { return (T) new CustomPersistenceManager(); } } public static class CustomPersistenceManager extends PersistenceManagerImpl { @Override protected AsyncCacheWriter createAsyncWriter(CacheWriter tmpStore) { CountDownLatch modApplyLatch = new CountDownLatch(1); CountDownLatch lockedWaitLatch = new CountDownLatch(1); return new MockAsyncCacheWriter(modApplyLatch, lockedWaitLatch, tmpStore); } } }