--- 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);
}
--- 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.
*
--- 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;
+ }
+ }
}
--- 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.
*/
--- 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);
+ }
+ }
}
--- 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.
+
+
--- 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.
+
+