Author: markt Date: Fri Oct 28 07:37:34 2011 New Revision: 1190185 URL: http://svn.apache.org/viewvc?rev=1190185&view=rev Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=46264 Implement threaded start/stop for containers
Removed: tomcat/trunk/java/org/apache/tomcat/util/threads/DedicatedThreadExecutor.java tomcat/trunk/test/org/apache/tomcat/util/threads/DedicatedThreadExecutorTest.java Modified: tomcat/trunk/java/org/apache/catalina/Container.java tomcat/trunk/java/org/apache/catalina/Host.java tomcat/trunk/java/org/apache/catalina/core/ContainerBase.java tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties tomcat/trunk/java/org/apache/catalina/core/StandardContext.java tomcat/trunk/java/org/apache/catalina/core/StandardHost.java tomcat/trunk/java/org/apache/catalina/core/mbeans-descriptors.xml tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java tomcat/trunk/java/org/apache/catalina/startup/HostConfig.java tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties tomcat/trunk/java/org/apache/coyote/AbstractProtocol.java tomcat/trunk/webapps/docs/config/engine.xml tomcat/trunk/webapps/docs/config/host.xml Modified: tomcat/trunk/java/org/apache/catalina/Container.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/Container.java?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/Container.java (original) +++ tomcat/trunk/java/org/apache/catalina/Container.java Fri Oct 28 07:37:34 2011 @@ -461,4 +461,21 @@ public interface Container extends Lifec * that the request/response still appears in the correct access logs. */ public AccessLog getAccessLog(); + + + /** + * Returns the number of threads available for starting and stopping any + * children associated with this container. This allows start/stop calls to + * children to be processed in parallel. + */ + public int getStartStopThreads(); + + + /** + * Sets the number of threads available for starting and stopping any + * children associated with this container. This allows start/stop calls to + * children to be processed in parallel. + * @param startStopThreads The new number of threads to be used + */ + public void setStartStopThreads(int startStopThreads); } Modified: tomcat/trunk/java/org/apache/catalina/Host.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/Host.java?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/Host.java (original) +++ tomcat/trunk/java/org/apache/catalina/Host.java Fri Oct 28 07:37:34 2011 @@ -17,6 +17,7 @@ package org.apache.catalina; import java.io.File; +import java.util.concurrent.ExecutorService; import java.util.regex.Pattern; @@ -180,9 +181,16 @@ public interface Host extends Container public void setDeployIgnore(String deployIgnore); - // --------------------------------------------------------- Public Methods + /** + * Return the executor that is used for starting and stopping contexts. This + * is primarily for use by components deploying contexts that want to do + * this in a multi-threaded manner. + */ + public ExecutorService getStartStopExecutor(); + // --------------------------------------------------------- Public Methods + /** * Add an alias name that should be mapped to this same Host. * Modified: tomcat/trunk/java/org/apache/catalina/core/ContainerBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/ContainerBase.java?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/ContainerBase.java (original) +++ tomcat/trunk/java/org/apache/catalina/core/ContainerBase.java Fri Oct 28 07:37:34 2011 @@ -26,6 +26,13 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import javax.management.ObjectName; import javax.naming.directory.DirContext; @@ -273,8 +280,55 @@ public abstract class ContainerBase exte protected volatile AccessLog accessLog = null; private volatile boolean accessLogScanComplete = false; + + /** + * The number of threads available to process start and stop events for any + * children associated with this container. + */ + private int startStopThreads = 1; + protected ThreadPoolExecutor startStopExecutor; + // ------------------------------------------------------------- Properties + @Override + public int getStartStopThreads() { + return startStopThreads; + } + + /** + * Handles the special values. + */ + private int getStartStopThreadsInternal() { + int result = getStartStopThreads(); + + // Positive values are unchanged + if (result > 0) { + return result; + } + + // Zero == Runtime.getRuntime().availableProcessors() + // -ve == Runtime.getRuntime().availableProcessors() + value + // These two are the same + result = Runtime.getRuntime().availableProcessors() + result; + if (result < 1) { + result = 1; + } + return result; + } + + @Override + public void setStartStopThreads(int startStopThreads) { + this.startStopThreads = startStopThreads; + + // Use local copies to ensure thread safety + ThreadPoolExecutor executor = startStopExecutor; + if (executor != null) { + int newThreads = getStartStopThreadsInternal(); + executor.setMaximumPoolSize(newThreads); + executor.setCorePoolSize(newThreads); + } + } + /** * Get the delay between the invocation of the backgroundProcess method on @@ -980,6 +1034,19 @@ public abstract class ContainerBase exte } + @Override + protected void initInternal() throws LifecycleException { + BlockingQueue<Runnable> startStopQueue = + new LinkedBlockingQueue<Runnable>(); + startStopExecutor = new ThreadPoolExecutor( + getStartStopThreadsInternal(), + getStartStopThreadsInternal(), 10, TimeUnit.SECONDS, + startStopQueue); + startStopExecutor.allowCoreThreadTimeOut(true); + super.initInternal(); + } + + /** * Start this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. @@ -1008,8 +1075,24 @@ public abstract class ContainerBase exte // Start our child containers, if any Container children[] = findChildren(); + List<Future<Void>> results = new ArrayList<Future<Void>>(); for (int i = 0; i < children.length; i++) { - children[i].start(); + results.add(startStopExecutor.submit(new StartChild(children[i]))); + } + + boolean fail = false; + for (Future<Void> result : results) { + try { + result.get(); + } catch (Exception e) { + log.error(sm.getString("containerBase.threadedStartFailed"), e); + fail = true; + } + + } + if (fail) { + throw new LifecycleException( + sm.getString("containerBase.threadedStartFailed")); } // Start the Valves in our pipeline (including the basic), if any @@ -1048,8 +1131,23 @@ public abstract class ContainerBase exte // Stop our child containers, if any Container children[] = findChildren(); + List<Future<Void>> results = new ArrayList<Future<Void>>(); for (int i = 0; i < children.length; i++) { - children[i].stop(); + results.add(startStopExecutor.submit(new StopChild(children[i]))); + } + + boolean fail = false; + for (Future<Void> result : results) { + try { + result.get(); + } catch (Exception e) { + log.error(sm.getString("containerBase.threadedStopFailed"), e); + fail = true; + } + } + if (fail) { + throw new LifecycleException( + sm.getString("containerBase.threadedStopFailed")); } // Stop our subordinate components, if any @@ -1092,6 +1190,8 @@ public abstract class ContainerBase exte parent.removeChild(this); } + startStopExecutor.shutdownNow(); + super.destroyInternal(); } @@ -1391,4 +1491,37 @@ public abstract class ContainerBase exte } } } + + + // ----------------------------- Inner classes used with start/stop Executor + + private static class StartChild implements Callable<Void> { + + private Container child; + + public StartChild(Container child) { + this.child = child; + } + + @Override + public Void call() throws LifecycleException { + child.start(); + return null; + } + } + + private static class StopChild implements Callable<Void> { + + private Container child; + + public StopChild(Container child) { + this.child = child; + } + + @Override + public Void call() throws LifecycleException { + child.stop(); + return null; + } + } } Modified: tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/catalina/core/LocalStrings.properties Fri Oct 28 07:37:34 2011 @@ -53,6 +53,8 @@ aprListener.sslInit=Failed to initialize aprListener.tcnValid=Loaded APR based Apache Tomcat Native library {0}. aprListener.flags=APR capabilities: IPv6 [{0}], sendfile [{1}], accept filters [{2}], random [{3}]. asyncContextImpl.requestEnded=The request associated with the AsyncContext has already completed processing. +containerBase.threadedStartFailed=A child container failed during start +containerBase.threadedStopFailed=A child container failed during stop containerBase.backgroundProcess.cluster=Exception processing cluster {0} background process containerBase.backgroundProcess.loader=Exception processing loader {0} background process containerBase.backgroundProcess.manager=Exception processing manager {0} background process Modified: tomcat/trunk/java/org/apache/catalina/core/StandardContext.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/StandardContext.java?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/StandardContext.java (original) +++ tomcat/trunk/java/org/apache/catalina/core/StandardContext.java Fri Oct 28 07:37:34 2011 @@ -37,7 +37,6 @@ import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.TreeMap; -import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicLong; import javax.management.ListenerNotFoundException; @@ -118,7 +117,6 @@ import org.apache.tomcat.JarScanner; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.modeler.Registry; import org.apache.tomcat.util.scan.StandardJarScanner; -import org.apache.tomcat.util.threads.DedicatedThreadExecutor; /** * Standard implementation of the <b>Context</b> interface. Each @@ -5101,9 +5099,7 @@ public class StandardContext extends Con } } - DedicatedThreadExecutor temporaryExecutor = new DedicatedThreadExecutor(); try { - // Create context attributes that will be required if (ok) { getServletContext().setAttribute( @@ -5128,22 +5124,7 @@ public class StandardContext extends Con // Configure and call application event listeners if (ok) { - // we do it in a dedicated thread for memory leak protection, in - // case the Listeners registers some ThreadLocals that they - // forget to cleanup - Boolean listenerStarted = - temporaryExecutor.execute(new Callable<Boolean>() { - @Override - public Boolean call() throws Exception { - ClassLoader old = bindThread(); - try { - return Boolean.valueOf(listenerStart()); - } finally { - unbindThread(old); - } - } - }); - if (!listenerStarted.booleanValue()) { + if (!listenerStart()) { log.error( "Error listenerStart"); ok = false; } @@ -5164,22 +5145,7 @@ public class StandardContext extends Con // Configure and call application filters if (ok) { - // we do it in a dedicated thread for memory leak protection, in - // case the Filters register some ThreadLocals that they forget - // to cleanup - Boolean filterStarted = - temporaryExecutor.execute(new Callable<Boolean>() { - @Override - public Boolean call() throws Exception { - ClassLoader old = bindThread(); - try { - return Boolean.valueOf(filterStart()); - } finally { - unbindThread(old); - } - } - }); - if (!filterStarted.booleanValue()) { + if (!filterStart()) { log.error("Error filterStart"); ok = false; } @@ -5187,27 +5153,12 @@ public class StandardContext extends Con // Load and initialize all "load on startup" servlets if (ok) { - // we do it in a dedicated thread for memory leak protection, in - // case the Servlets register some ThreadLocals that they forget - // to cleanup - temporaryExecutor.execute(new Callable<Void>() { - @Override - public Void call() throws Exception { - ClassLoader old = bindThread(); - try { - loadOnStartup(findChildren()); - return null; - } finally { - unbindThread(old); - } - } - }); + loadOnStartup(findChildren()); } } finally { // Unbinding thread unbindThread(oldCCL); - temporaryExecutor.shutdown(); } // Set available status depending upon startup success @@ -5347,61 +5298,28 @@ public class StandardContext extends Con // Stop our child containers, if any final Container[] children = findChildren(); - // we do it in a dedicated thread for memory leak protection, in - // case some webapp code registers some ThreadLocals that they - // forget to cleanup - // TODO Figure out why DedicatedThreadExecutor hangs randomly in the - // unit tests if used here - RunnableWithLifecycleException stop = - new RunnableWithLifecycleException() { - @Override - public void run() { - ClassLoader old = bindThread(); - try { - for (int i = 0; i < children.length; i++) { - try { - children[i].stop(); - } catch (LifecycleException e) { - le = e; - return; - } - } - // Stop our filters - filterStop(); + ClassLoader old = bindThread(); + try { + for (int i = 0; i < children.length; i++) { + children[i].stop(); + } - // Stop ContainerBackgroundProcessor thread - threadStop(); + // Stop our filters + filterStop(); - if (manager != null && manager instanceof Lifecycle && - ((Lifecycle) manager).getState().isAvailable()) { - try { - ((Lifecycle) manager).stop(); - } catch (LifecycleException e) { - le = e; - return; - } - } + // Stop ContainerBackgroundProcessor thread + threadStop(); - // Stop our application listeners - listenerStop(); - }finally{ - unbindThread(old); - } + if (manager != null && manager instanceof Lifecycle && + ((Lifecycle) manager).getState().isAvailable()) { + ((Lifecycle) manager).stop(); } - }; - Thread t = new Thread(stop); - t.setName("stop children - " + getObjectName().toString()); - t.start(); - try { - t.join(); - } catch (InterruptedException e) { - // Shouldn't happen - throw new LifecycleException(e); - } - if (stop.getLifecycleException() != null) { - throw stop.getLifecycleException(); + // Stop our application listeners + listenerStop(); + } finally{ + unbindThread(old); } // Finalize our character set mapper @@ -6396,14 +6314,4 @@ public class StandardContext extends Con public long getStartTime() { return startTime; } - - private abstract static class RunnableWithLifecycleException - implements Runnable { - - protected LifecycleException le = null; - - public LifecycleException getLifecycleException() { - return le; - } - } } Modified: tomcat/trunk/java/org/apache/catalina/core/StandardHost.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/StandardHost.java?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/StandardHost.java (original) +++ tomcat/trunk/java/org/apache/catalina/core/StandardHost.java Fri Oct 28 07:37:34 2011 @@ -24,6 +24,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.WeakHashMap; +import java.util.concurrent.ExecutorService; import java.util.regex.Pattern; import org.apache.catalina.Container; @@ -177,6 +178,11 @@ public class StandardHost extends Contai // ------------------------------------------------------------- Properties + @Override + public ExecutorService getStartStopExecutor() { + return startStopExecutor; + } + /** * Return the application root for this Host. This can be an absolute Modified: tomcat/trunk/java/org/apache/catalina/core/mbeans-descriptors.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/mbeans-descriptors.xml?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/mbeans-descriptors.xml (original) +++ tomcat/trunk/java/org/apache/catalina/core/mbeans-descriptors.xml Fri Oct 28 07:37:34 2011 @@ -1011,6 +1011,10 @@ description="Will children be started automatically when they are added." type="boolean"/> + <attribute name="startStopThreads" + description="The number of threads to use when starting and stopping child Hosts" + type="int"/> + <attribute name="stateName" description="The name of the LifecycleState that this component is currently in" type="java.lang.String" @@ -1198,6 +1202,10 @@ description="Will children be started automatically when they are added?" type="boolean"/> + <attribute name="startStopThreads" + description="The number of threads to use when starting, stopping and deploying child Contexts" + type="int"/> + <attribute name="stateName" description="The name of the LifecycleState that this component is currently in" type="java.lang.String" Modified: tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/ContextConfig.java Fri Oct 28 07:37:34 2011 @@ -127,12 +127,6 @@ public class ContextConfig protected static final LoginConfig DUMMY_LOGIN_CONFIG = new LoginConfig("NONE", null, null, null); - /** - * The <code>Digester</code> we will use to process web application - * context files. - */ - protected static Digester contextDigester = null; - /** * The set of Authenticators that we know how to configure. The key is @@ -143,32 +137,6 @@ public class ContextConfig /** - * The <code>Digester</code>s available to process web deployment descriptor - * files. - */ - protected static Digester[] webDigesters = new Digester[4]; - - - /** - * The <code>Digester</code>s available to process web fragment deployment - * descriptor files. - */ - protected static Digester[] webFragmentDigesters = new Digester[4]; - - - /** - * The <code>Rule</code>s used to parse the web.xml - */ - protected static WebRuleSet webRuleSet = new WebRuleSet(false); - - - /** - * The <code>Rule</code>s used to parse the web-fragment.xml - */ - protected static WebRuleSet webFragmentRuleSet = new WebRuleSet(true); - - - /** * Deployment count. */ protected static long deploymentCount = 0L; @@ -236,12 +204,14 @@ public class ContextConfig * deployment descriptor files. */ protected Digester webDigester = null; + protected WebRuleSet webRuleSet = null; /** * The <code>Digester</code> we will use to process web fragment * deployment descriptor files. */ protected Digester webFragmentDigester = null; + protected WebRuleSet webFragmentRuleSet = null; // ------------------------------------------------------------- Properties @@ -486,60 +456,21 @@ public class ContextConfig /** - * Create (if necessary) and return a Digester configured to process the + * Create and return a Digester configured to process the * web application deployment descriptor (web.xml). */ public void createWebXmlDigester(boolean namespaceAware, boolean validation) { - if (!namespaceAware && !validation) { - if (webDigesters[0] == null) { - webDigesters[0] = DigesterFactory.newDigester(validation, - namespaceAware, webRuleSet); - webFragmentDigesters[0] = DigesterFactory.newDigester(validation, - namespaceAware, webFragmentRuleSet); - webDigesters[0].getParser(); - webFragmentDigesters[0].getParser(); - } - webDigester = webDigesters[0]; - webFragmentDigester = webFragmentDigesters[0]; - - } else if (!namespaceAware && validation) { - if (webDigesters[1] == null) { - webDigesters[1] = DigesterFactory.newDigester(validation, - namespaceAware, webRuleSet); - webFragmentDigesters[1] = DigesterFactory.newDigester(validation, - namespaceAware, webFragmentRuleSet); - webDigesters[1].getParser(); - webFragmentDigesters[1].getParser(); - } - webDigester = webDigesters[1]; - webFragmentDigester = webFragmentDigesters[1]; - - } else if (namespaceAware && !validation) { - if (webDigesters[2] == null) { - webDigesters[2] = DigesterFactory.newDigester(validation, - namespaceAware, webRuleSet); - webFragmentDigesters[2] = DigesterFactory.newDigester(validation, - namespaceAware, webFragmentRuleSet); - webDigesters[2].getParser(); - webFragmentDigesters[2].getParser(); - } - webDigester = webDigesters[2]; - webFragmentDigester = webFragmentDigesters[2]; - - } else { - if (webDigesters[3] == null) { - webDigesters[3] = DigesterFactory.newDigester(validation, - namespaceAware, webRuleSet); - webFragmentDigesters[3] = DigesterFactory.newDigester(validation, - namespaceAware, webFragmentRuleSet); - webDigesters[3].getParser(); - webFragmentDigesters[3].getParser(); - } - webDigester = webDigesters[3]; - webFragmentDigester = webFragmentDigesters[3]; - } + webRuleSet = new WebRuleSet(false); + webDigester = DigesterFactory.newDigester(validation, + namespaceAware, webRuleSet); + webDigester.getParser(); + + webFragmentRuleSet = new WebRuleSet(true); + webFragmentDigester = DigesterFactory.newDigester(validation, + namespaceAware, webFragmentRuleSet); + webFragmentDigester.getParser(); } @@ -577,7 +508,7 @@ public class ContextConfig /** * Process the default configuration file, if it exists. */ - protected void contextConfig() { + protected void contextConfig(Digester digester) { // Open the default context.xml file, if it exists if( defaultContextXml==null && context instanceof StandardContext ) { @@ -596,7 +527,7 @@ public class ContextConfig if (defaultContextFile.exists()) { try { URL defaultContextUrl = defaultContextFile.toURI().toURL(); - processContextConfig(defaultContextUrl); + processContextConfig(digester, defaultContextUrl); } catch (MalformedURLException e) { log.error(sm.getString( "contextConfig.badUrl", defaultContextFile), e); @@ -608,7 +539,7 @@ public class ContextConfig if (hostContextFile.exists()) { try { URL hostContextUrl = hostContextFile.toURI().toURL(); - processContextConfig(hostContextUrl); + processContextConfig(digester, hostContextUrl); } catch (MalformedURLException e) { log.error(sm.getString( "contextConfig.badUrl", hostContextFile), e); @@ -616,7 +547,7 @@ public class ContextConfig } } if (context.getConfigFile() != null) { - processContextConfig(context.getConfigFile()); + processContextConfig(digester, context.getConfigFile()); } } @@ -625,7 +556,7 @@ public class ContextConfig /** * Process a context.xml. */ - protected void processContextConfig(URL contextXml) { + protected void processContextConfig(Digester digester, URL contextXml) { if (log.isDebugEnabled()) { log.debug("Processing context [" + context.getName() @@ -653,45 +584,43 @@ public class ContextConfig if (source == null) { return; } - synchronized (contextDigester) { - try { - source.setByteStream(stream); - contextDigester.setClassLoader(this.getClass().getClassLoader()); - contextDigester.setUseContextClassLoader(false); - contextDigester.push(context.getParent()); - contextDigester.push(context); - XmlErrorHandler errorHandler = new XmlErrorHandler(); - contextDigester.setErrorHandler(errorHandler); - contextDigester.parse(source); - if (errorHandler.getWarnings().size() > 0 || - errorHandler.getErrors().size() > 0) { - errorHandler.logFindings(log, contextXml.toString()); - ok = false; - } - if (log.isDebugEnabled()) { - log.debug("Successfully processed context [" + context.getName() - + "] configuration file [" + contextXml + "]"); - } - } catch (SAXParseException e) { - log.error(sm.getString("contextConfig.contextParse", - context.getName()), e); - log.error(sm.getString("contextConfig.defaultPosition", - "" + e.getLineNumber(), - "" + e.getColumnNumber())); - ok = false; - } catch (Exception e) { - log.error(sm.getString("contextConfig.contextParse", - context.getName()), e); + + try { + source.setByteStream(stream); + digester.setClassLoader(this.getClass().getClassLoader()); + digester.setUseContextClassLoader(false); + digester.push(context.getParent()); + digester.push(context); + XmlErrorHandler errorHandler = new XmlErrorHandler(); + digester.setErrorHandler(errorHandler); + digester.parse(source); + if (errorHandler.getWarnings().size() > 0 || + errorHandler.getErrors().size() > 0) { + errorHandler.logFindings(log, contextXml.toString()); ok = false; - } finally { - contextDigester.reset(); - try { - if (stream != null) { - stream.close(); - } - } catch (IOException e) { - log.error(sm.getString("contextConfig.contextClose"), e); + } + if (log.isDebugEnabled()) { + log.debug("Successfully processed context [" + context.getName() + + "] configuration file [" + contextXml + "]"); + } + } catch (SAXParseException e) { + log.error(sm.getString("contextConfig.contextParse", + context.getName()), e); + log.error(sm.getString("contextConfig.defaultPosition", + "" + e.getLineNumber(), + "" + e.getColumnNumber())); + ok = false; + } catch (Exception e) { + log.error(sm.getString("contextConfig.contextParse", + context.getName()), e); + ok = false; + } finally { + try { + if (stream != null) { + stream.close(); } + } catch (IOException e) { + log.error(sm.getString("contextConfig.contextClose"), e); } } } @@ -846,10 +775,8 @@ public class ContextConfig protected void init() { // Called from StandardContext.init() - if (contextDigester == null){ - contextDigester = createContextDigester(); - contextDigester.getParser(); - } + Digester contextDigester = createContextDigester(); + contextDigester.getParser(); if (log.isDebugEnabled()) { log.debug(sm.getString("contextConfig.init")); @@ -857,7 +784,7 @@ public class ContextConfig context.setConfigured(false); ok = true; - contextConfig(); + contextConfig(contextDigester); createWebXmlDigester(context.getXmlNamespaceAware(), context.getXmlValidation()); @@ -1416,8 +1343,9 @@ public class ContextConfig } // Parsing global web.xml is relatively expensive. Use a sync block to - // make sure it only happens once - synchronized (host) { + // make sure it only happens once. Use the pipeline since a lock will + // already be held on the host by another thread + synchronized (host.getPipeline()) { entry = hostWebXmlCache.get(host); if (entry != null && entry.getGlobalTimeStamp() == globalTimeStamp && entry.getHostTimeStamp() == hostTimeStamp) { @@ -1679,7 +1607,6 @@ public class ContextConfig return getWebXmlSource(defaultWebXml, getBaseDir()); } - /** * Identify the host web.xml to be used and obtain an input source for * it. @@ -1806,9 +1733,6 @@ public class ContextConfig XmlErrorHandler handler = new XmlErrorHandler(); - // Web digesters and rulesets are shared between contexts but are not - // thread safe. Whilst there should only be one thread at a time - // processing a config, play safe and sync. Digester digester; WebRuleSet ruleSet; if (fragment) { @@ -1819,41 +1743,36 @@ public class ContextConfig ruleSet = webRuleSet; } - // Sync on the ruleSet since the same ruleSet is shared across all four - // digesters - synchronized(ruleSet) { - - digester.push(dest); - digester.setErrorHandler(handler); - - if(log.isDebugEnabled()) { - log.debug(sm.getString("contextConfig.applicationStart", - source.getSystemId())); - } + digester.push(dest); + digester.setErrorHandler(handler); - try { - digester.parse(source); + if(log.isDebugEnabled()) { + log.debug(sm.getString("contextConfig.applicationStart", + source.getSystemId())); + } - if (handler.getWarnings().size() > 0 || - handler.getErrors().size() > 0) { - ok = false; - handler.logFindings(log, source.getSystemId()); - } - } catch (SAXParseException e) { - log.error(sm.getString("contextConfig.applicationParse", - source.getSystemId()), e); - log.error(sm.getString("contextConfig.applicationPosition", - "" + e.getLineNumber(), - "" + e.getColumnNumber())); - ok = false; - } catch (Exception e) { - log.error(sm.getString("contextConfig.applicationParse", - source.getSystemId()), e); + try { + digester.parse(source); + + if (handler.getWarnings().size() > 0 || + handler.getErrors().size() > 0) { ok = false; - } finally { - digester.reset(); - ruleSet.recycle(); + handler.logFindings(log, source.getSystemId()); } + } catch (SAXParseException e) { + log.error(sm.getString("contextConfig.applicationParse", + source.getSystemId()), e); + log.error(sm.getString("contextConfig.applicationPosition", + "" + e.getLineNumber(), + "" + e.getColumnNumber())); + ok = false; + } catch (Exception e) { + log.error(sm.getString("contextConfig.applicationParse", + source.getSystemId()), e); + ok = false; + } finally { + digester.reset(); + ruleSet.recycle(); } } @@ -2244,7 +2163,8 @@ public class ContextConfig /** * process filter annotation and merge with existing one! - * FIXME: refactoring method to long and has redundant subroutines with processAnnotationWebServlet! + * FIXME: refactoring method too long and has redundant subroutines with + * processAnnotationWebServlet! * @param className * @param ae * @param fragment Modified: tomcat/trunk/java/org/apache/catalina/startup/HostConfig.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/HostConfig.java?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/HostConfig.java (original) +++ tomcat/trunk/java/org/apache/catalina/startup/HostConfig.java Fri Oct 28 07:37:34 2011 @@ -14,8 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - package org.apache.catalina.startup; @@ -33,7 +31,11 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.regex.Matcher; @@ -138,8 +140,8 @@ public class HostConfig /** * Map of deployed applications. */ - protected HashMap<String, DeployedApplication> deployed = - new HashMap<String, DeployedApplication>(); + protected Map<String, DeployedApplication> deployed = + new ConcurrentHashMap<String, DeployedApplication>(); /** @@ -497,6 +499,10 @@ public class HostConfig ContextName cn = new ContextName(name); String baseName = cn.getBaseName(); + if (deploymentExists(baseName)) { + return; + } + // Deploy XML descriptors from configBase File xml = new File(configBase, baseName + ".xml"); if (xml.exists()) @@ -520,17 +526,29 @@ public class HostConfig if (files == null) return; + ExecutorService es = host.getStartStopExecutor(); + List<Future<?>> results = new ArrayList<Future<?>>(); + for (int i = 0; i < files.length; i++) { File contextXml = new File(configBase, files[i]); if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".xml")) { ContextName cn = new ContextName(files[i]); - String name = cn.getName(); - if (isServiced(name)) + if (isServiced(cn.getName()) || deploymentExists(cn.getName())) continue; - deployDescriptor(cn, contextXml); + results.add( + es.submit(new DeployDescriptor(this, cn, contextXml))); + } + } + + for (Future<?> result : results) { + try { + result.get(); + } catch (Exception e) { + log.error(sm.getString( + "hostConfig.deployDescriptor.threaded.error"), e); } } } @@ -541,9 +559,6 @@ public class HostConfig * @param contextXml */ protected void deployDescriptor(ContextName cn, File contextXml) { - if (deploymentExists(cn.getName())) { - return; - } DeployedApplication deployedApp = new DeployedApplication(cn.getName()); @@ -670,6 +685,9 @@ public class HostConfig if (files == null) return; + ExecutorService es = host.getStartStopExecutor(); + List<Future<?>> results = new ArrayList<Future<?>>(); + for (int i = 0; i < files.length; i++) { if (files[i].equalsIgnoreCase("META-INF")) @@ -690,10 +708,19 @@ public class HostConfig continue; } - if (isServiced(cn.getName())) + if (isServiced(cn.getName()) || deploymentExists(cn.getName())) continue; - deployWAR(cn, war); + results.add(es.submit(new DeployWar(this, cn, war))); + } + } + + for (Future<?> result : results) { + try { + result.get(); + } catch (Exception e) { + log.error(sm.getString( + "hostConfig.deployWar.threaded.error"), e); } } } @@ -741,9 +768,6 @@ public class HostConfig */ protected void deployWAR(ContextName cn, File war) { - if (deploymentExists(cn.getName())) - return; - // Checking for a nested /META-INF/context.xml JarFile jar = null; JarEntry entry = null; @@ -935,6 +959,9 @@ public class HostConfig if (files == null) return; + ExecutorService es = host.getStartStopExecutor(); + List<Future<?>> results = new ArrayList<Future<?>>(); + for (int i = 0; i < files.length; i++) { if (files[i].equalsIgnoreCase("META-INF")) @@ -945,10 +972,19 @@ public class HostConfig if (dir.isDirectory()) { ContextName cn = new ContextName(files[i]); - if (isServiced(cn.getName())) + if (isServiced(cn.getName()) || deploymentExists(cn.getName())) continue; - deployDirectory(cn, dir); + results.add(es.submit(new DeployDirectory(this, cn, dir))); + } + } + + for (Future<?> result : results) { + try { + result.get(); + } catch (Exception e) { + log.error(sm.getString( + "hostConfig.deployDir.threaded.error"), e); } } } @@ -960,9 +996,6 @@ public class HostConfig */ protected void deployDirectory(ContextName cn, File dir) { - if (deploymentExists(cn.getName())) - return; - DeployedApplication deployedApp = new DeployedApplication(cn.getName()); // Deploy the application in this directory @@ -1450,7 +1483,61 @@ public class HostConfig /** * Instant where the application was last put in service. */ - public long timestamp = System.currentTimeMillis(); + public long timestamp = System.currentTimeMillis(); + } + + private static class DeployDescriptor implements Runnable { + + private HostConfig config; + private ContextName cn; + private File descriptor; + + public DeployDescriptor(HostConfig config, ContextName cn, + File descriptor) { + this.config = config; + this.cn = cn; + this.descriptor= descriptor; + } + + @Override + public void run() { + config.deployDescriptor(cn, descriptor); + } + } + + private static class DeployWar implements Runnable { + + private HostConfig config; + private ContextName cn; + private File war; + + public DeployWar(HostConfig config, ContextName cn, File war) { + this.config = config; + this.cn = cn; + this.war = war; + } + + @Override + public void run() { + config.deployWAR(cn, war); + } } + private static class DeployDirectory implements Runnable { + + private HostConfig config; + private ContextName cn; + private File dir; + + public DeployDirectory(HostConfig config, ContextName cn, File dir) { + this.config = config; + this.cn = cn; + this.dir = dir; + } + + @Override + public void run() { + config.deployDirectory(cn, dir); + } + } } Modified: tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/catalina/startup/LocalStrings.properties Fri Oct 28 07:37:34 2011 @@ -80,11 +80,19 @@ hostConfig.context.restart=Error during hostConfig.createDirs=Unable to create directory for deployment: {0} hostConfig.deployDescriptor=Deploying configuration descriptor {0} hostConfig.deployDescriptor.error=Error deploying configuration descriptor {0} +hostConfig.deployDescriptor.threaded.error=Error waiting for multi-thread deployment of context descriptors to complete hostConfig.deployDescriptor.localDocBaseSpecified=A docBase {0} inside the host appBase has been specified, and will be ignored hostConfig.deployDir=Deploying web application directory {0} hostConfig.deployDir.error=Error deploying web application directory {0} +hostConfig.deployDir.threaded.error=Error waiting for multi-thread deployment of directories to completehostConfig.deployWar=Deploying web application archive {0} hostConfig.deployWar=Deploying web application archive {0} hostConfig.deployWar.error=Error deploying web application archive {0} +hostConfig.deployWar.threaded.error=Error waiting for multi-thread deployment of WAR files to complete +hostConfig.deploy.error=Exception while deploying web application directory {0} +hostConfig.deploying=Deploying discovered web applications +hostConfig.expand=Expanding web application archive {0} +hostConfig.expand.error=Exception while expanding web application archive {0} +hostConfig.expanding=Expanding discovered web application archives hostConfig.ignorePath=Ignoring path [{0}] in appBase for automatic deployment hostConfig.illegalWarName=The war name [{0}] is invalid. The archive will be ignored. hostConfig.jmx.register=Register context [{0}] failed Modified: tomcat/trunk/java/org/apache/coyote/AbstractProtocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/AbstractProtocol.java?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/AbstractProtocol.java (original) +++ tomcat/trunk/java/org/apache/coyote/AbstractProtocol.java Fri Oct 28 07:37:34 2011 @@ -493,6 +493,12 @@ public abstract class AbstractProtocol i SocketStatus status) { P processor = connections.remove(socket.getSocket()); + if (getLog().isDebugEnabled()) { + getLog().debug("process() entry " + + "Socket: [" + logHashcode(socket.getSocket()) + "], " + + "Processor [" + logHashcode(processor) + "]"); + } + socket.setAsync(false); try { @@ -503,20 +509,51 @@ public abstract class AbstractProtocol i processor = createProcessor(); } + if (getLog().isDebugEnabled()) { + getLog().debug("process() gotProcessor " + + "Socket: [" + logHashcode(socket.getSocket()) + "], " + + "Processor [" + logHashcode(processor) + "]"); + } + initSsl(socket, processor); SocketState state = SocketState.CLOSED; do { if (processor.isAsync() || state == SocketState.ASYNC_END) { state = processor.asyncDispatch(status); + if (getLog().isDebugEnabled()) { + getLog().debug("process() asyncDispatch " + + "Socket: [" + logHashcode(socket.getSocket()) + "], " + + "Processor: [" + logHashcode(processor) + "], " + + "State: [" + state.toString() + "]"); + } } else if (processor.isComet()) { state = processor.event(status); + if (getLog().isDebugEnabled()) { + getLog().debug("process() event " + + "Socket: [" + logHashcode(socket.getSocket()) + "], " + + "Processor: [" + logHashcode(processor) + "], " + + "State: [" + state.toString() + "]"); + } } else { state = processor.process(socket); + if (getLog().isDebugEnabled()) { + getLog().debug("process() process " + + "Socket: [" + logHashcode(socket.getSocket()) + "], " + + "Processor: [" + logHashcode(processor) + "], " + + "State: [" + state.toString() + "]"); + } } if (state != SocketState.CLOSED && processor.isAsync()) { state = processor.asyncPostProcess(); + if (getLog().isDebugEnabled()) { + getLog().debug("process() asyncPostProcess " + + "Socket: [" + logHashcode(socket.getSocket()) + "], " + + "Processor: [" + logHashcode(processor) + "], " + + "State: [" + state.toString() + "]"); + } + } } while (state == SocketState.ASYNC_END); @@ -562,6 +599,14 @@ public abstract class AbstractProtocol i return SocketState.CLOSED; } + private String logHashcode (Object o) { + if (o == null) { + return "null"; + } else { + return Integer.toString(o.hashCode()); + } + } + protected abstract P createProcessor(); protected abstract void initSsl(SocketWrapper<S> socket, P processor); protected abstract void longPoll(SocketWrapper<S> socket, P processor); Modified: tomcat/trunk/webapps/docs/config/engine.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/engine.xml?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/config/engine.xml (original) +++ tomcat/trunk/webapps/docs/config/engine.xml Fri Oct 28 07:37:34 2011 @@ -102,6 +102,17 @@ name.</em></p> </attribute> + <attribute name="startStopThreads" required="false"> + <p>The number of threads this <strong>Engine</strong> will use to start + child <a href="host.html">Host</a> elements in parallel. The special + value of 0 will result in the value of + <code>Runtime.getRuntime().availableProcessors()</code> being used. + Negative values will result in + <code>Runtime.getRuntime().availableProcessors() + value</code> being + used unless this is less than 1 in which case 1 thread will be used. If + not specified, the default value of 1 will be used. </p> + </attribute> + </attributes> </subsection> Modified: tomcat/trunk/webapps/docs/config/host.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/host.xml?rev=1190185&r1=1190184&r2=1190185&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/config/host.xml (original) +++ tomcat/trunk/webapps/docs/config/host.xml Fri Oct 28 07:37:34 2011 @@ -188,6 +188,19 @@ virtual host.</p> </attribute> + <attribute name="startStopThreads" required="false"> + <p>The number of threads this <strong>Host</strong> will use to start + child <a href="context.html">Context</a> elements in parallel. The same + thread pool will be used to deploy new + <a href="context.html">Context</a>s if automatic deployment is being + used. The special value of 0 will result in the value of + <code>Runtime.getRuntime().availableProcessors()</code> being used. + Negative values will result in + <code>Runtime.getRuntime().availableProcessors() + value</code> being + used unless this is less than 1 in which case 1 thread will be used. If + not specified, the default value of 1 will be used.</p> + </attribute> + </attributes> </subsection> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org