[jboss-cvs] JBossAS SVN: r85652 - in branches/Branch_5_x: testsuite/src/main/org/jboss/test/cluster/defaultcfg/web/test and 2 other directories.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Mon Mar 9 17:27:03 EDT 2009


Author: pferraro
Date: 2009-03-09 17:27:02 -0400 (Mon, 09 Mar 2009)
New Revision: 85652

Added:
   branches/Branch_5_x/testsuite/src/main/org/jboss/test/cluster/defaultcfg/web/test/CleanShutdownTestCase.java
   branches/Branch_5_x/testsuite/src/resources/cluster/http/sleep.jsp
   branches/Branch_5_x/tomcat/src/main/org/jboss/web/tomcat/service/session/LockingValve.java
Modified:
   branches/Branch_5_x/testsuite/imports/config/tests-clustering.xml
   branches/Branch_5_x/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java
Log:
[JBAS-6534] Graceful shutdown of JBoss Web service does not wait for accepted requests to complete before stopping dependent services

Modified: branches/Branch_5_x/testsuite/imports/config/tests-clustering.xml
===================================================================
--- branches/Branch_5_x/testsuite/imports/config/tests-clustering.xml	2009-03-09 17:43:09 UTC (rev 85651)
+++ branches/Branch_5_x/testsuite/imports/config/tests-clustering.xml	2009-03-09 21:27:02 UTC (rev 85652)
@@ -174,16 +174,15 @@
 
     <echo message="Going to call target tests-clustering-unit again for FIELD granularity tests (ASYNC, no BR)"/>
 
-  	<antcall target="tests-clustering-unit">
+    <antcall target="tests-clustering-unit">
       <param name="cluster.includes.refid" value="${cluster.http.field.includes}"/>
       <param name="jboss-junit-configuration" value="Default-${jboss-junit-configuration}"/>
       <param name="jbosstest.cluster.web.cache.config" value="field-granularity-session-cache"/>
-    	<param name="jbosstest.cluster.web.cache.pojo" value="true"/>
+      <param name="jbosstest.cluster.web.cache.pojo" value="true"/>
       <param name="jbosstest.cluster.node0.config" value="cluster-${jboss-junit-configuration}-0"/>
       <param name="jbosstest.cluster.node1.config" value="cluster-${jboss-junit-configuration}-1"/>
     </antcall>
 
-
     <server:stop name="cluster-${jboss-junit-configuration}-0"/>
     <server:stop name="cluster-${jboss-junit-configuration}-1"/>
 

Added: branches/Branch_5_x/testsuite/src/main/org/jboss/test/cluster/defaultcfg/web/test/CleanShutdownTestCase.java
===================================================================
--- branches/Branch_5_x/testsuite/src/main/org/jboss/test/cluster/defaultcfg/web/test/CleanShutdownTestCase.java	                        (rev 0)
+++ branches/Branch_5_x/testsuite/src/main/org/jboss/test/cluster/defaultcfg/web/test/CleanShutdownTestCase.java	2009-03-09 21:27:02 UTC (rev 85652)
@@ -0,0 +1,178 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, 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.web.test;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import javax.management.MBeanServerConnection;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
+import org.jboss.test.JBossClusteredTestCase;
+
+/**
+ * A CleanShutdownTestCase.
+ * 
+ * @author Paul Ferraro
+ */
+public class CleanShutdownTestCase extends JBossClusteredTestCase
+{
+   private static final String SERVER_NAME = "jboss.web.deployment:war=/http-sr";
+   private static final String SHUTDOWN_METHOD = "stop";
+   private static final String URL = "http://localhost:8080/http-sr/sleep.jsp?sleep=%d";
+   private static final int MAX_THREADS = 2;
+   private static final int REQUEST_DURATION = 10000;
+   
+   ObjectName name;
+   MBeanServerConnection server;
+   HttpClient client;
+   
+   private MultiThreadedHttpConnectionManager manager;
+   
+   public static Test suite() throws Exception
+   {
+      return JBossClusteredTestCase.getDeploySetup(CleanShutdownTestCase.class, "http-sr.war");
+   }
+   
+   /**
+    * Create a new CleanShutdownTestCase.
+    * 
+    * @param name
+    * @throws MalformedObjectNameException 
+    */
+   public CleanShutdownTestCase(String name) throws MalformedObjectNameException
+   {
+      super(name);
+   }
+
+   /**
+    * @see org.jboss.test.JBossClusteredTestCase#setUp()
+    */
+   @Override
+   protected void setUp() throws Exception
+   {
+      super.setUp();
+      
+      this.name = ObjectName.getInstance(SERVER_NAME);
+      this.server = this.getAdaptors()[0];
+
+      this.manager = new MultiThreadedHttpConnectionManager();
+      
+      HttpConnectionManagerParams params = new HttpConnectionManagerParams();
+      params.setDefaultMaxConnectionsPerHost(MAX_THREADS);
+      params.setMaxTotalConnections(MAX_THREADS);
+      
+      this.manager.setParams(params);
+
+      this.client = new HttpClient();
+   }
+
+   /**
+    * @see org.jboss.test.JBossTestCase#tearDown()
+    */
+   @Override
+   protected void tearDown() throws Exception
+   {
+      this.manager.shutdown();
+      
+      try
+      {
+         super.tearDown();
+      }
+      catch (Exception e)
+      {
+         // Webapp undeploy failed because server has shutdown
+      }
+   }
+
+   public void testShutdown() throws Exception
+   {
+      ExecutorService executor = Executors.newFixedThreadPool(MAX_THREADS);
+      
+      try
+      {
+         // Make sure a normal request will succeed
+         Assert.assertEquals(200, new RequestTask(0).call().intValue());
+         
+         // Send a long request - in parallel
+         Future<Integer> future = executor.submit(new RequestTask(REQUEST_DURATION));
+
+         // Make sure long request has started
+         Thread.sleep(1000);
+
+         // Shutdown server
+         this.server.invoke(this.name, SHUTDOWN_METHOD, null, null);
+
+         // Get result of long request
+         // This request should succeed since it initiated before server shutdown
+         try
+         {
+            Assert.assertEquals(200, future.get().intValue());
+         }
+         catch (ExecutionException e)
+         {
+            e.printStackTrace(System.err);
+            
+            Assert.fail(e.getCause().getMessage());
+         }
+      }
+      finally
+      {
+         executor.shutdownNow();
+      }
+   }
+   
+   private class RequestTask implements Callable<Integer>
+   {
+      private final int sleep;
+      
+      RequestTask(int sleep)
+      {
+         this.sleep = sleep;
+      }
+      
+      public Integer call() throws Exception
+      {
+         GetMethod method = new GetMethod(String.format(URL, this.sleep));
+         
+         try
+         {
+            return CleanShutdownTestCase.this.client.executeMethod(method);
+         }
+         finally
+         {
+            method.releaseConnection();
+         }
+      }
+   }
+}

Added: branches/Branch_5_x/testsuite/src/resources/cluster/http/sleep.jsp
===================================================================
--- branches/Branch_5_x/testsuite/src/resources/cluster/http/sleep.jsp	                        (rev 0)
+++ branches/Branch_5_x/testsuite/src/resources/cluster/http/sleep.jsp	2009-03-09 21:27:02 UTC (rev 85652)
@@ -0,0 +1,15 @@
+<%
+   String sleep = request.getParameter("sleep");
+
+   if (sleep != null)
+   {
+      long ms = Long.parseLong(sleep);
+      
+      if (ms > 0)
+      {
+         Thread.sleep(ms);
+      }
+      
+      session.setAttribute("sleep", sleep);
+   }
+%>
\ No newline at end of file

Modified: branches/Branch_5_x/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java
===================================================================
--- branches/Branch_5_x/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java	2009-03-09 17:43:09 UTC (rev 85651)
+++ branches/Branch_5_x/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java	2009-03-09 21:27:02 UTC (rev 85652)
@@ -31,11 +31,17 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
 
+import javax.management.MBeanServer;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
 
+import org.apache.catalina.Container;
 import org.apache.catalina.Context;
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.Session;
@@ -81,6 +87,8 @@
     */
    private static final String info_ = "JBossCacheManager/1.0";
 
+   private static final int TOTAL_PERMITS = Integer.MAX_VALUE;
+   
    // ------------------------------------------------------------------ Fields
 
    /** The transaction manager. */
@@ -143,6 +151,9 @@
    
    private ReplicationConfig replicationConfig_;
 
+   private Semaphore semaphore = new Semaphore(TOTAL_PERMITS, true);
+   private Lock valveLock = new SemaphoreLock(this.semaphore);
+   
    //  ----------------------------------------------------------  Constructors
 
    public JBossCacheManager() throws ClusteringNotSupportedException
@@ -635,6 +646,20 @@
          startUnembedded();
       }
       
+      // Handle re-entrance
+      if (!this.semaphore.tryAcquire())
+      {
+         log_.debug("Opening up LockingValve");
+         
+         // Make all permits available to locking valve
+         this.semaphore.release(TOTAL_PERMITS);
+      }
+      else
+      {
+         // Release the one we just acquired
+         this.semaphore.release();
+      }
+      
       log_.debug("Started");
    }
 
@@ -651,6 +676,24 @@
       
       log_.debug("Stopping");
       
+      // Handle re-entrance
+      if (this.semaphore.tryAcquire())
+      {
+         try
+         {
+            log_.debug("Closing off LockingValve");
+            
+            // Acquire all remaining permits, shutting off locking valve
+            this.semaphore.acquire(TOTAL_PERMITS - 1);
+         }
+         catch (InterruptedException e)
+         {
+            this.semaphore.release();
+            
+            throw new LifecycleException(e);
+         }
+      }
+      
       // Block for any ongoing backgroundProcess, then disable
       synchronized (backgroundProcessAllowed)
       {
@@ -2003,7 +2046,10 @@
     * Add a JvmRouteValve and BatchReplicationClusteredSessionValve if needed.
     */
    private void installValves()
-   {      
+   {
+      log_.debug("Adding LockingValve");
+      this.installValve(new LockingValve(this.valveLock));
+      
       // If JK usage wasn't explicitly configured, default to enabling
       // it if jvmRoute is set on our containing Engine
       if (useJK_ == null)
@@ -2014,8 +2060,8 @@
       if (getUseJK())
       {
          log_.debug("We are using JK for load-balancing. Adding JvmRouteValve.");         
-         installContextValve(new JvmRouteValve(this));         
-      }   
+         this.installValve(new JvmRouteValve(this));
+      }
       
       // Handle batch replication if needed.
       // TODO -- should we add this even if not FIELD in case a cross-context
@@ -2031,7 +2077,7 @@
       // Add clustered session valve
       ClusteredSessionValve valve = new ClusteredSessionValve(this, valveBM);
       log_.debug("Adding ClusteredSessionValve");
-      installContextValve(valve);
+      this.installValve(valve);
    }
 
    /**
@@ -2071,7 +2117,7 @@
       snapshotManager_.start();
    }
    
-   private void installContextValve(Valve valve)
+   private void installValve(Valve valve)
    {
       boolean installed = false;
       
@@ -2079,43 +2125,51 @@
       // with the way the overall context is created in TomcatDeployer.
       // We can't do this in unembedded mode because we are called
       // before our Context is registered with the MBean server
-      if (embedded_ && getContextObjectName() != null) {
-         try
+      if (embedded_)
+      {
+         ObjectName name = this.getObjectName(this.container_);
+         
+         if (name != null)
          {
-            getMBeanServer().invoke(getContextObjectName(), "addValve",
-                                    new Object[]{valve},
-                                    new String[]{"org.apache.catalina.Valve"});
-            installed = true;
+            try
+            {
+               MBeanServer server = this.getMBeanServer();
+               
+               server.invoke(name, "addValve", new Object[] { valve }, new String[] { Valve.class.getName() });
+               
+               installed = true;
+            }
+            catch (Exception e)
+            {
+               // JBAS-2422.  If the context is restarted via JMX, the above
+               // JMX call will fail as the context will not be registered
+               // when it's made.  So we catch the exception and fall back
+               // to adding the valve directly.
+               // TODO consider skipping adding via JMX and just do it directly
+               log_.debug("Caught exception installing valve to Context", e);
+            }
          }
-         catch (Exception e)
-         {
-            // JBAS-2422.  If the context is restarted via JMX, the above
-            // JMX call will fail as the context will not be registered
-            // when it's made.  So we catch the exception and fall back
-            // to adding the valve directly.
-            // TODO consider skipping adding via JMX and just do it directly
-            log_.debug("Caught exception installing valve to Context", e);
-         }
       }
       
       if (!installed)
       {
          // If possible install via the ContainerBase.addValve() API.
-         if (container_ instanceof ContainerBase)
+         if (this.container_ instanceof ContainerBase)
          {            
-            ((ContainerBase) container_).addValve(valve);
+            ((ContainerBase) this.container_).addValve(valve);
          }
          else
          {
             // No choice; have to add it to the context's pipeline
-            container_.getPipeline().addValve(valve);
+            this.container_.getPipeline().addValve(valve);
          }
       }
    }
    
-   private ObjectName getContextObjectName()
+   private ObjectName getObjectName(Container container)
    {
-      String oname = container_.getObjectName();
+      String oname = container.getObjectName();
+      
       try
       {         
          return (oname == null) ? null : new ObjectName(oname);
@@ -2126,7 +2180,7 @@
          return null;
       }
    }
-
+   
    /**
     * Clear the underlying cache store.
     */
@@ -2373,7 +2427,63 @@
          long anotherVal = o.getLastUpdate();
          return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1));
       }
+   }
+   
+   private static class SemaphoreLock implements Lock
+   {
+      private final Semaphore semaphore;
       
+      SemaphoreLock(Semaphore semaphore)
+      {
+         this.semaphore = semaphore;
+      }
       
+      /**
+       * @see java.util.concurrent.locks.Lock#lock()
+       */
+      public void lock()
+      {
+         this.semaphore.acquireUninterruptibly();
+      }
+
+      /**
+       * @see java.util.concurrent.locks.Lock#lockInterruptibly()
+       */
+      public void lockInterruptibly() throws InterruptedException
+      {
+         this.semaphore.acquire();
+      }
+
+      /**
+       * @see java.util.concurrent.locks.Lock#newCondition()
+       */
+      public Condition newCondition()
+      {
+         throw new UnsupportedOperationException();
+      }
+
+      /**
+       * @see java.util.concurrent.locks.Lock#tryLock()
+       */
+      public boolean tryLock()
+      {
+         return this.semaphore.tryAcquire();
+      }
+
+      /**
+       * @see java.util.concurrent.locks.Lock#tryLock(long, java.util.concurrent.TimeUnit)
+       */
+      public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
+      {
+         return this.semaphore.tryAcquire(timeout, unit);
+      }
+
+      /**
+       * @see java.util.concurrent.locks.Lock#unlock()
+       */
+      public void unlock()
+      {
+         this.semaphore.release();
+      }
    }
 }

Added: branches/Branch_5_x/tomcat/src/main/org/jboss/web/tomcat/service/session/LockingValve.java
===================================================================
--- branches/Branch_5_x/tomcat/src/main/org/jboss/web/tomcat/service/session/LockingValve.java	                        (rev 0)
+++ branches/Branch_5_x/tomcat/src/main/org/jboss/web/tomcat/service/session/LockingValve.java	2009-03-09 21:27:02 UTC (rev 85652)
@@ -0,0 +1,101 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, Red Hat Middleware LLC, 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.web.tomcat.service.session;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ValveBase;
+import org.jboss.servlet.http.HttpEvent;
+
+public class LockingValve extends ValveBase
+{
+   private final Lock lock;
+   
+   public LockingValve(Lock lock)
+   {
+      this.lock = lock;
+   }
+   
+   @Override
+   public void invoke(Request request, Response response) throws IOException, ServletException
+   {
+      try
+      {
+         if (this.lock.tryLock(0, TimeUnit.SECONDS))
+         {
+            try
+            {
+               this.next.invoke(request, response);
+            }
+            finally
+            {
+               this.lock.unlock();
+            }
+         }
+         else
+         {
+            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+         }
+      }
+      catch (InterruptedException e)
+      {
+         Thread.currentThread().interrupt();
+      }
+   }
+
+   /**
+    * @see org.apache.catalina.valves.ValveBase#event(org.apache.catalina.connector.Request, org.apache.catalina.connector.Response, org.jboss.servlet.http.HttpEvent)
+    */
+   @Override
+   public void event(Request request, Response response, HttpEvent event) throws IOException, ServletException
+   {
+      try
+      {
+         if (this.lock.tryLock(0, TimeUnit.SECONDS))
+         {
+            try
+            {
+               this.next.event(request, response, event);
+            }
+            finally
+            {
+               this.lock.unlock();
+            }
+         }
+         else
+         {
+            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+         }
+      }
+      catch (InterruptedException e)
+      {
+         Thread.currentThread().interrupt();
+      }
+   }
+}
\ No newline at end of file




More information about the jboss-cvs-commits mailing list