Index: java/org/apache/catalina/Container.java =================================================================== --- java/org/apache/catalina/Container.java (revision 1181051) +++ java/org/apache/catalina/Container.java (working copy) @@ -476,4 +476,21 @@ * 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); } Index: java/org/apache/catalina/Host.java =================================================================== --- java/org/apache/catalina/Host.java (revision 1181051) +++ java/org/apache/catalina/Host.java (working copy) @@ -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 void setDeployIgnore(String deployIgnore); + /** + * 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. * Index: java/org/apache/catalina/core/ContainerBase.java =================================================================== --- java/org/apache/catalina/core/ContainerBase.java (revision 1181051) +++ java/org/apache/catalina/core/ContainerBase.java (working copy) @@ -26,6 +26,13 @@ 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; @@ -274,10 +281,57 @@ 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 * this container and its children. Child containers will not be invoked * if their delay value is not negative (which would mean they are using @@ -1001,6 +1055,20 @@ } + @Override + protected void initInternal() throws LifecycleException { + BlockingQueue startStopQueue = + new LinkedBlockingQueue(); + startStopExecutor = new ThreadPoolExecutor( + getStartStopThreadsInternal(), + getStartStopThreadsInternal(), 10, TimeUnit.SECONDS, + startStopQueue); + // Allow executor thread pool size to drop to zero of not required + startStopExecutor.allowCoreThreadTimeOut(true); + super.initInternal(); + } + + /** * Start this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. @@ -1029,9 +1097,24 @@ // Start our child containers, if any Container children[] = findChildren(); + List> results = new ArrayList>(); for (int i = 0; i < children.length; i++) { - children[i].start(); + results.add(startStopExecutor.submit(new StartChild(children[i]))); } + Iterator> iter = results.iterator(); + boolean fail = false; + while (iter.hasNext()) { + try { + iter.next().get(); + } catch (Exception e) { + // TODO Log this + fail = true; + } + } + if (fail) { + // TODO Add a useful message + throw new LifecycleException(); + } // Start the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) @@ -1069,9 +1152,24 @@ // Stop our child containers, if any Container children[] = findChildren(); + List> results = new ArrayList>(); for (int i = 0; i < children.length; i++) { - children[i].stop(); + results.add(startStopExecutor.submit(new StopChild(children[i]))); } + Iterator> iter = results.iterator(); + boolean fail = false; + while (iter.hasNext()) { + try { + iter.next().get(); + } catch (Exception e) { + // TODO Log this + fail = true; + } + } + if (fail) { + // TODO Add a useful message + throw new LifecycleException(); + } // Stop our subordinate components, if any if ((resources != null) && (resources instanceof Lifecycle)) { @@ -1113,6 +1211,8 @@ parent.removeChild(this); } + startStopExecutor.shutdownNow(); + super.destroyInternal(); } @@ -1412,4 +1512,37 @@ } } } + + + // ----------------------------- Inner classes used with start/stop Executor + + private static class StartChild implements Callable { + + 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 { + + private Container child; + + public StopChild(Container child) { + this.child = child; + } + + @Override + public Void call() throws LifecycleException { + child.stop(); + return null; + } + } } Index: java/org/apache/catalina/core/StandardHost.java =================================================================== --- java/org/apache/catalina/core/StandardHost.java (revision 1181051) +++ java/org/apache/catalina/core/StandardHost.java (working copy) @@ -24,6 +24,7 @@ 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; @@ -183,8 +184,13 @@ // ------------------------------------------------------------- Properties + @Override + public ExecutorService getStartStopExecutor() { + return startStopExecutor; + } - /** + + /** * Return the application root for this Host. This can be an absolute * pathname, a relative pathname, or a URL. */ Index: java/org/apache/catalina/core/mbeans-descriptors.xml =================================================================== --- java/org/apache/catalina/core/mbeans-descriptors.xml (revision 1181051) +++ java/org/apache/catalina/core/mbeans-descriptors.xml (working copy) @@ -1011,6 +1011,10 @@ description="Will children be started automatically when they are added." type="boolean"/> + + + + > results = new ArrayList>(); + for (int i = 0; i < files.length; i++) { File contextXml = new File(configBase, files[i]); @@ -530,9 +534,19 @@ if (isServiced(name)) continue; - deployDescriptor(cn, contextXml); + results.add( + es.submit(new DeployDescriptor(this, cn, contextXml))); } } + + Iterator> iter = results.iterator(); + while (iter.hasNext()) { + try { + iter.next().get(); + } catch (Exception e) { + // TODO Log this. Should never happen. + } + } } @@ -670,14 +684,17 @@ if (files == null) return; + ExecutorService es = host.getStartStopExecutor(); + List> results = new ArrayList>(); + for (int i = 0; i < files.length; i++) { if (files[i].equalsIgnoreCase("META-INF")) continue; if (files[i].equalsIgnoreCase("WEB-INF")) continue; - File dir = new File(appBase, files[i]); - if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") && dir.isFile() + File war = new File(appBase, files[i]); + if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") && war.isFile() && !invalidWars.contains(files[i]) ) { ContextName cn = new ContextName(files[i]); @@ -693,9 +710,18 @@ if (isServiced(cn.getName())) continue; - deployWAR(cn, dir); + results.add(es.submit(new DeployWar(this, cn, war))); } } + + Iterator> iter = results.iterator(); + while (iter.hasNext()) { + try { + iter.next().get(); + } catch (Exception e) { + // TODO Log this. Should never happen. + } + } } @@ -935,6 +961,9 @@ if (files == null) return; + ExecutorService es = host.getStartStopExecutor(); + List> results = new ArrayList>(); + for (int i = 0; i < files.length; i++) { if (files[i].equalsIgnoreCase("META-INF")) @@ -948,9 +977,18 @@ if (isServiced(cn.getName())) continue; - deployDirectory(cn, dir); + results.add(es.submit(new DeployDirectory(this, cn, dir))); } } + + Iterator> iter = results.iterator(); + while (iter.hasNext()) { + try { + iter.next().get(); + } catch (Exception e) { + // TODO Log this. Should never happen. + } + } } @@ -1450,7 +1488,61 @@ /** * 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); + } + } } Index: webapps/docs/config/engine.xml =================================================================== --- webapps/docs/config/engine.xml (revision 1181051) +++ webapps/docs/config/engine.xml (working copy) @@ -102,6 +102,17 @@ name.

+ +

The number of threads this Engine will use to start + child Host elements in parallel. If not + specified, the default value of 1 will be used. The special value of 0 + will result in the value of + Runtime.getRuntime().availableProcessors() being used. + Negative values will result in + Runtime.getRuntime().availableProcessors() - value being + used unless this is less than 1 in which case 1 thread will be used.

+
+ Index: webapps/docs/config/host.xml =================================================================== --- webapps/docs/config/host.xml (revision 1181051) +++ webapps/docs/config/host.xml (working copy) @@ -188,6 +188,19 @@ virtual host.

+ +

The number of threads this Host will use to start + child Context elements in parallel. The same + thread pool will be used to deploy new + Contexts if automatic deployment is being + used. If not specified, the default value of 1 will be used. The special + value of 0 will result in the value of + Runtime.getRuntime().availableProcessors() being used. + Negative values will result in + Runtime.getRuntime().availableProcessors() - value being + used unless this is less than 1 in which case 1 thread will be used.

+
+