/* * JBoss, Home of Professional Open Source * Copyright 2009 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.loaders.decorators; import org.infinispan.Cache; import org.infinispan.config.CacheLoaderManagerConfig; import org.infinispan.config.Configuration; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.loaders.CacheLoaderException; import org.infinispan.loaders.CacheLoaderManager; import org.infinispan.loaders.CacheStore; import org.infinispan.loaders.dummy.DummyInMemoryCacheStore; import org.infinispan.manager.EmbeddedCacheManager; 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; 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 static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.fail; @Test(groups = "functional", testName = "loaders.decorators.SingletonStoreTest", enabled = false, description = "See ISPN-1123") 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; } protected void createCacheManagers() { cm0 = addClusterEnabledCacheManager(); cm1 = addClusterEnabledCacheManager(); cm2 = addClusterEnabledCacheManager(); Configuration conf = getDefaultClusteredConfig(Configuration.CacheMode.REPL_SYNC); DummyInMemoryCacheStore.Cfg cfg = new DummyInMemoryCacheStore.Cfg(); cfg.setStoreName("Store-" + storeCounter.getAndIncrement()); CacheLoaderManagerConfig pushingCfg = new CacheLoaderManagerConfig(); pushingCfg.addCacheLoaderConfig(cfg); SingletonStoreConfig ssc = new SingletonStoreConfig(); ssc.setPushStateWhenCoordinator(true); ssc.setSingletonStoreEnabled(true); cfg.setSingletonStoreConfig(ssc); conf.setCacheLoaderManagerConfig(pushingCfg); // cannot define on ALL cache managers since the same dummy in memory CL bin will be used! cm0.defineConfiguration("pushing", conf); ((DummyInMemoryCacheStore.Cfg) conf.getCacheLoaderManagerConfig().getFirstCacheLoaderConfig()).setStoreName("Store-" + storeCounter.getAndIncrement()); cm1.defineConfiguration("pushing", conf); ((DummyInMemoryCacheStore.Cfg) conf.getCacheLoaderManagerConfig().getFirstCacheLoaderConfig()).setStoreName("Store-" + storeCounter.getAndIncrement()); cm2.defineConfiguration("pushing", conf); conf = getDefaultClusteredConfig(Configuration.CacheMode.REPL_SYNC); cfg = new DummyInMemoryCacheStore.Cfg(); cfg.setStoreName("Store-" + storeCounter.getAndIncrement()); CacheLoaderManagerConfig nonPushingCfg = new CacheLoaderManagerConfig(); nonPushingCfg.addCacheLoaderConfig(cfg); ssc = new SingletonStoreConfig(); ssc.setPushStateWhenCoordinator(false); ssc.setSingletonStoreEnabled(true); cfg.setSingletonStoreConfig(ssc); conf.setCacheLoaderManagerConfig(nonPushingCfg); // cannot define on ALL cache managers since the same dummy in memory CL bin will be used! cm0.defineConfiguration("nonPushing", conf); ((DummyInMemoryCacheStore.Cfg) conf.getCacheLoaderManagerConfig().getFirstCacheLoaderConfig()).setStoreName("Store-" + storeCounter.getAndIncrement()); cm1.defineConfiguration("nonPushing", conf); ((DummyInMemoryCacheStore.Cfg) conf.getCacheLoaderManagerConfig().getFirstCacheLoaderConfig()).setStoreName("Store-" + storeCounter.getAndIncrement()); cm2.defineConfiguration("nonPushing", conf); } private Cache[] getCaches(String name) { return new Cache[]{ cm0.getCache(name), cm1.getCache(name), cm2.getCache(name) }; } private SingletonStore[] extractStores(Cache[] caches) { SingletonStore[] stores = new SingletonStore[caches.length]; int i = 0; for (Cache c : caches) stores[i++] = (SingletonStore) TestingUtil.extractComponent(c, CacheLoaderManager.class).getCacheStore(); return stores; } private Object load(CacheStore cs, Object key) throws CacheLoaderException { InternalCacheEntry se = cs.load(key); return se == null ? null : se.getValue(); } public void testPutCacheLoaderWithNoPush() throws Exception { Cache[] caches = getCaches("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. CacheStore[] 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 = getCaches("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); SingletonStore[] 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); final TestingSingletonStore mscl = new TestingSingletonStore(pushStateCanFinish, secondActiveStatusChangerCanStart, new SingletonStoreConfig()); 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()); } public void testPushStateTimedOut() throws Throwable { final CountDownLatch pushStateCanFinish = new CountDownLatch(1); SingletonStoreConfig ssdc = new SingletonStoreConfig(); ssdc.setPushStateTimeout(100L); final TestingSingletonStore mscl = new TestingSingletonStore(pushStateCanFinish, null, ssdc); 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(SingletonStore mscl) { return new ActiveStatusModifier(mscl); } static class TestingSingletonStore extends SingletonStore { private int numberCreatedTasks = 0; private CountDownLatch pushStateCanFinish; private CountDownLatch secondActiveStatusChangerCanStart; public TestingSingletonStore(CountDownLatch pushStateCanFinish, CountDownLatch secondActiveStatusChangerCanStart, SingletonStoreConfig cfg) { super(null, null, cfg); 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() { 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 SingletonStore scl; public ActiveStatusModifier(SingletonStore singleton) { scl = singleton; } public Object call() throws Exception { log.debug("active status modifier started"); scl.activeStatusChanged(true); scl.pushStateFuture.get(); return null; } } }