/* * JBoss, Home of Professional Open Source. * Copyright 2009 Red Hat, Inc. and individual contributors * as indicated by the @author tags. See the copyright.txt file 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.jboss.test.cluster.defaultcfg.simpleweb.test; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import junit.framework.TestCase; import org.apache.catalina.Manager; import org.apache.catalina.Session; import org.apache.catalina.Valve; import org.apache.catalina.connector.Request; import org.jboss.cache.Cache; import org.jboss.cache.Fqn; import org.jboss.cache.pojo.PojoCache; import org.jboss.logging.Logger; import org.jboss.metadata.web.jboss.JBossWebMetaData; import org.jboss.test.cluster.testutil.JBossCacheUtil; import org.jboss.test.cluster.testutil.JGroupsSystemPropertySupport; import org.jboss.test.cluster.testutil.SessionTestUtil; import org.jboss.test.cluster.web.mocks.BasicRequestHandler; import org.jboss.test.cluster.web.mocks.ConcurrentRequestHandler; import org.jboss.test.cluster.web.mocks.SetAttributesRequestHandler; import org.jboss.web.tomcat.service.session.JBossCacheManager; /** * JBAS-7379. Tests that multiple concurrent failover requests for * the same session are handled properly. * * @author <a href="brian.stansberry@jboss.com">Brian Stansberry</a> * @version $Revision: 85945 $ */ public class ConcurrentFailoverRequestsTestCase extends TestCase { private static final Logger log = Logger.getLogger(ConcurrentFailoverRequestsTestCase.class); private static long testCount = System.currentTimeMillis(); private final JGroupsSystemPropertySupport jgSupport = new JGroupsSystemPropertySupport(); private Set<PojoCache> caches = new HashSet<PojoCache>(); private ExecutorService threadPool; /** * Create a new ConcurrentFailoverRequestsTestCase. * * @param name */ public ConcurrentFailoverRequestsTestCase(String name) { super(name); } @Override protected void setUp() throws Exception { super.setUp(); jgSupport.setUpProperties(); } @Override protected void tearDown() throws Exception { try { super.tearDown(); } finally { jgSupport.restoreProperties(); if (threadPool != null) { threadPool.shutdownNow(); } SessionTestUtil.clearDistributedCacheManagerFactory(); for (PojoCache cache : caches) { // Try to clean up so we avoid loading sessions // from storage in later tests try { log.info("Removing /JSESSION from " + cache.getCache().getLocalAddress()); cache.getCache().removeNode(Fqn.fromString("/JSESSION")); } catch (Exception e) { log.error("Cache " + cache.getCache().getLocalAddress() + ": " + e.getMessage(), e); } try { cache.stop(); cache.destroy(); } catch (Exception e) { log.error("Cache " + cache.getCache().getLocalAddress() + ": " + e.getMessage(), e); } } caches.clear(); } } public void testConcurrentFailoverRequests() throws Exception { ++testCount; JBossWebMetaData webMetaData = SessionTestUtil.createWebMetaData(100); String warName = "test" + testCount; JBossCacheManager jbcm0 = SessionTestUtil.createManager(warName, 30, false, null, false, false, null, caches); jbcm0.init(warName, webMetaData); jbcm0.start(); JBossCacheManager jbcm1 = SessionTestUtil.createManager(warName, 30, false, null, false, false, null, caches); jbcm1.init(warName, webMetaData); jbcm1.start(); Cache[] array = new Cache[caches.size()]; int index = 0; for (PojoCache c : caches) { array[index] = c.getCache(); index++; } JBossCacheUtil.blockUntilViewsReceived(array, 10000); Object value = "0"; Map<String, Object> attrs = Collections.unmodifiableMap(Collections.singletonMap("count", value)); SetAttributesRequestHandler setHandler = new SetAttributesRequestHandler(attrs, false); SessionTestUtil.invokeRequest(jbcm0, setHandler, null); String id1 = setHandler.getSessionId(); assertNotNull(id1); // Add a second session that we can check for replication; this is a proxy // for checking that first session has replicated setHandler = new SetAttributesRequestHandler(attrs, false); SessionTestUtil.invokeRequest(jbcm0, setHandler, null); String id2 = setHandler.getSessionId(); assertNotNull(id1); assertFalse(id1.equals(id2)); // Ensure replication of session 2 has occurred boolean found = false; for (int i = 0; i < 10; i++) { BasicRequestHandler getHandler = new BasicRequestHandler(attrs.keySet(), false); SessionTestUtil.invokeRequest(jbcm1, getHandler, id2); if (getHandler.getCheckedAttributes() != null && value.equals(getHandler.getCheckedAttributes().get("count"))) { found = true; break; } Thread.sleep(50); } assertTrue("sessions replicated", found); jbcm0.stop(); int THREADS = 10; threadPool = Executors.newFixedThreadPool(THREADS); CountDownLatch startingGun = new CountDownLatch(THREADS + 1); CountDownLatch finishedSignal = new CountDownLatch(THREADS); ConcurrentRequestHandler concurrentHandler = new ConcurrentRequestHandler(); Valve pipelineHead = SessionTestUtil.setupPipeline(jbcm1, concurrentHandler); Loader[] loaders = new Loader[THREADS]; for (int i = 0; i < loaders.length; i++) { loaders[i] = new Loader(pipelineHead, concurrentHandler, jbcm1, id1, attrs.keySet(), startingGun, finishedSignal); threadPool.execute(loaders[i]); } startingGun.countDown(); assertTrue("loaders completed on time", finishedSignal.await(45, TimeUnit.SECONDS)); for (int i = 0; i < loaders.length; i++) { assertNotNull("got checked attributes for " + i, loaders[i].checkedAttributes); assertTrue("checked 'count' attribute for " + i, loaders[i].checkedAttributes.containsKey("count")); assertEquals("correct value for " + i, value, loaders[i].checkedAttributes.get("count")); } } private static class Loader implements Runnable { private final Valve pipelineHead; private final ConcurrentRequestHandler concurrentHandler; private final Manager manager; private final String sessionId; private final Set<String> attributeKeys; private final CountDownLatch startingGun; private final CountDownLatch finishedSignal; private Map<String, Object> checkedAttributes; private Loader(Valve pipelineHead, ConcurrentRequestHandler concurrentHandler, Manager manager, String sessionId, Set<String> attributeKeys, CountDownLatch startingGun, CountDownLatch finishedSignal) { this.pipelineHead = pipelineHead; this.concurrentHandler = concurrentHandler; this.manager = manager; this.sessionId = sessionId; this.attributeKeys = attributeKeys; this.startingGun = startingGun; this.finishedSignal = finishedSignal; } public void run() { try { BasicRequestHandler getHandler = new BasicRequestHandler(attributeKeys, false); concurrentHandler.registerHandler(getHandler); Request request = SessionTestUtil.setupRequest(manager, sessionId); startingGun.countDown(); startingGun.await(); System.out.println("started"); SessionTestUtil.invokeRequest(pipelineHead, request); this.checkedAttributes = getHandler.getCheckedAttributes(); if (this.checkedAttributes != null) { System.out.println(this.checkedAttributes.keySet()); } } catch (Exception e) { e.printStackTrace(System.out); } finally { finishedSignal.countDown(); concurrentHandler.unregisterHandler(); } } } }