package org.infinispan.persistence.support; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.fail; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.cache.PersistenceConfigurationBuilder; import org.infinispan.configuration.cache.SingletonStoreConfiguration; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.marshall.core.MarshalledEntry; import org.infinispan.persistence.dummy.DummyInMemoryStoreConfigurationBuilder; import org.infinispan.persistence.spi.CacheLoader; import org.infinispan.persistence.spi.PersistenceException; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.TestingUtil; import org.infinispan.test.ViewChangeListener; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.Test; @Test(groups = "unstable", testName = "persistence.decorators.SingletonStoreTest", description = "See ISPN-1123 -- original group: functional") public class SingletonStoreTest extends MultipleCacheManagersTest { private static final Log log = LogFactory.getLog(SingletonStoreTest.class); private static final AtomicInteger storeCounter = new AtomicInteger(0); private EmbeddedCacheManager cm0, cm1, cm2; public SingletonStoreTest() { cleanup = CleanupPhase.AFTER_METHOD; } @Override protected void createCacheManagers() { cm0 = addClusterEnabledCacheManager(); cm1 = addClusterEnabledCacheManager(); cm2 = addClusterEnabledCacheManager(); ConfigurationBuilder conf = getDefaultClusteredCacheConfig(CacheMode.REPL_SYNC); cm0.defineConfiguration("pushing", addDummyStore(conf, true).build()); cm1.defineConfiguration("pushing", addDummyStore(conf, true).build()); cm2.defineConfiguration("pushing", addDummyStore(conf, true).build()); conf = getDefaultClusteredCacheConfig(CacheMode.REPL_SYNC); // cannot define on ALL cache managers since the same dummy in memory CL bin will be used! cm0.defineConfiguration("nonPushing", addDummyStore(conf, false).build()); cm1.defineConfiguration("nonPushing", addDummyStore(conf, false).build()); cm2.defineConfiguration("nonPushing", addDummyStore(conf, false).build()); } private ConfigurationBuilder addDummyStore(ConfigurationBuilder config, boolean pushing) { config .persistence() .clearStores() .addStore(DummyInMemoryStoreConfigurationBuilder.class) .storeName("Store-" + storeCounter.getAndIncrement()) .singleton() .enable() .pushStateWhenCoordinator(pushing); return config; } private Cache[] getNamedCaches(String name) { return new Cache[]{ cm0.getCache(name), cm1.getCache(name), cm2.getCache(name) }; } private SingletonCacheWriter[] extractStores(Cache[] caches) { SingletonCacheWriter[] stores = new SingletonCacheWriter[caches.length]; int i = 0; for (Cache c : caches) stores[i++] = (SingletonCacheWriter) TestingUtil.getFirstWriter(c); return stores; } private Object load(SingletonCacheWriter cs, Object key) throws PersistenceException { MarshalledEntry se = ((CacheLoader)cs.undelegate()).load(key); return se == null ? null : se.getValue(); } public void testPutCacheLoaderWithNoPush() throws Exception { Cache[] caches = getNamedCaches("nonPushing"); for (Cache c : caches) c.start(); // block until they all see each other! TestingUtil.blockUntilViewsReceived(60000, true, caches); int i = 1; for (Cache c : caches) { c.put("key" + i, "value" + i); i++; } // all values should be on all caches since they are sync-repl for (Cache c : caches) { for (i = 1; i < 4; i++) assert c.get("key" + i).equals("value" + i); } // now test the stores. These should *only* be on the store on cache 1. SingletonCacheWriter[] stores = extractStores(caches); for (i = 1; i < 4; i++) { // should ONLY be on the first loader! assert load(stores[0], "key" + i).equals("value" + i); assert load(stores[1], "key" + i) == null : "stores[1] should not have stored key key" + i; assert load(stores[2], "key" + i) == null : "stores[2] should not have stored key key" + i; } cm0.stop(); TestingUtil.blockUntilViewsReceived(60000, false, cm1, cm2); caches[1].put("key4", "value4"); caches[2].put("key5", "value5"); assert load(stores[1], "key4").equals("value4"); assert load(stores[1], "key5").equals("value5"); assert load(stores[2], "key4") == null; assert load(stores[2], "key5") == null; cm1.stop(); TestingUtil.blockUntilViewsReceived(60000, false, cm2); caches[2].put("key6", "value6"); assert load(stores[2], "key6").equals("value6"); } public void testPutCacheLoaderWithPush() throws Exception { Cache[] caches = getNamedCaches("pushing"); for (Cache c : caches) c.start(); Map<String, String> expected = new HashMap<String, String>(); expected.put("a-key", "a-value"); expected.put("aa-key", "aa-value"); expected.put("b-key", "b-value"); expected.put("bb-key", "bb-value"); expected.put("c-key", "c-value"); expected.put("d-key", "d-value"); expected.put("e-key", "e-value"); expected.put("g-key", "g-value"); caches[0].putAll(expected); SingletonCacheWriter[] stores = extractStores(caches); for (String key : expected.keySet()) { assert load(stores[0], key).equals(expected.get(key)); assert load(stores[1], key) == null; assert load(stores[2], key) == null; } ViewChangeListener viewChangeListener = new ViewChangeListener(caches[1]); cm0.stop(); viewChangeListener.waitForViewChange(60, TimeUnit.SECONDS); waitForPushStateCompletion(stores[1].pushStateFuture); // cache store 1 should have all state now, and store 2 should have nothing for (String key : expected.keySet()) { assert load(stores[1], key).equals(expected.get(key)); assert load(stores[2], key) == null; } caches[1].put("h-key", "h-value"); caches[2].put("i-key", "i-value"); expected.put("h-key", "h-value"); expected.put("i-key", "i-value"); for (String key : expected.keySet()) { assert load(stores[1], key).equals(expected.get(key)); assert load(stores[2], key) == null; } viewChangeListener = new ViewChangeListener(caches[2]); cm1.stop(); viewChangeListener.waitForViewChange(60, TimeUnit.SECONDS); waitForPushStateCompletion(stores[2].pushStateFuture); for (String key : expected.keySet()) assert load(stores[2], key).equals(expected.get(key)); caches[2].put("aaa-key", "aaa-value"); expected.put("aaa-key", "aaa-value"); for (String key : expected.keySet()) assert load(stores[2], key).equals(expected.get(key)); } public void testAvoidConcurrentStatePush() throws Exception { final CountDownLatch pushStateCanFinish = new CountDownLatch(1); final CountDownLatch secondActiveStatusChangerCanStart = new CountDownLatch(1); SingletonStoreConfiguration singletonConfig = createSingletonStoreConfiguration(); final TestingSingletonStore mscl = new TestingSingletonStore(pushStateCanFinish, secondActiveStatusChangerCanStart, singletonConfig); Future f1 = fork(createActiveStatusChanger(mscl)); assert secondActiveStatusChangerCanStart.await(1000, TimeUnit.MILLISECONDS) : "Failed waiting on latch"; Future f2 = fork(createActiveStatusChanger(mscl)); f1.get(); f2.get(); assertEquals(1, mscl.getNumberCreatedTasks()); } private SingletonStoreConfiguration createSingletonStoreConfiguration() { PersistenceConfigurationBuilder persistenceBuilder = new ConfigurationBuilder().persistence(); return new DummyInMemoryStoreConfigurationBuilder(persistenceBuilder).singleton() .pushStateTimeout(100L) .create(); } public void testPushStateTimedOut() throws Throwable { final CountDownLatch pushStateCanFinish = new CountDownLatch(1); SingletonStoreConfiguration singletonConfig = createSingletonStoreConfiguration(); final TestingSingletonStore mscl = new TestingSingletonStore(pushStateCanFinish, null, singletonConfig); Future f = fork(createActiveStatusChanger(mscl)); pushStateCanFinish.await(200, TimeUnit.MILLISECONDS); pushStateCanFinish.countDown(); try { f.get(); fail("Should have timed out"); } catch (ExecutionException expected) { Throwable e; if ((e = expected.getCause().getCause().getCause()) instanceof TimeoutException) { assert true : "This is expected"; } else { throw e; } } } private void waitForPushStateCompletion(Future pushThreadFuture) throws Exception { if (pushThreadFuture != null) pushThreadFuture.get(); } private Callable<?> createActiveStatusChanger(SingletonCacheWriter mscl) { return new ActiveStatusModifier(mscl); } static class TestingSingletonStore extends SingletonCacheWriter { private int numberCreatedTasks = 0; private CountDownLatch pushStateCanFinish; private CountDownLatch secondActiveStatusChangerCanStart; public TestingSingletonStore(CountDownLatch pushStateCanFinish, CountDownLatch secondActiveStatusChangerCanStart, SingletonStoreConfiguration singletonConfig) { super(null, singletonConfig); this.pushStateCanFinish = pushStateCanFinish; this.secondActiveStatusChangerCanStart = secondActiveStatusChangerCanStart; } public int getNumberCreatedTasks() { return numberCreatedTasks; } public void setNumberCreatedTasks(int numberCreatedTasks) { this.numberCreatedTasks = numberCreatedTasks; } @Override protected Callable<?> createPushStateTask() { return new Callable() { @Override public Object call() throws Exception { numberCreatedTasks++; try { if (secondActiveStatusChangerCanStart != null) { secondActiveStatusChangerCanStart.countDown(); } pushStateCanFinish.await(); } catch (InterruptedException e) { fail("ActiveStatusModifier interrupted"); } return null; } }; } @Override protected void awaitForPushToFinish(Future future, long timeout, TimeUnit unit) { pushStateCanFinish.countDown(); super.awaitForPushToFinish(future, timeout, unit); } } static class ActiveStatusModifier implements Callable { private SingletonCacheWriter scl; public ActiveStatusModifier(SingletonCacheWriter singleton) { scl = singleton; } @Override public Object call() throws Exception { log.debug("active status modifier started"); scl.activeStatusChanged(true); scl.pushStateFuture.get(); return null; } } }