diff --git a/java/org/apache/catalina/Container.java b/java/org/apache/catalina/Container.java
index 73b265e..5ca3c09 100644
--- a/java/org/apache/catalina/Container.java
+++ b/java/org/apache/catalina/Container.java
@@ -476,4 +476,21 @@ public interface Container extends Lifecycle {
* 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);
}
diff --git a/java/org/apache/catalina/Host.java b/java/org/apache/catalina/Host.java
index 72f5dc2..9d27191 100644
--- a/java/org/apache/catalina/Host.java
+++ b/java/org/apache/catalina/Host.java
@@ -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.
*
diff --git a/java/org/apache/catalina/core/ContainerBase.java b/java/org/apache/catalina/core/ContainerBase.java
index 4a736a6..0a8edfc 100644
--- a/java/org/apache/catalina/core/ContainerBase.java
+++ b/java/org/apache/catalina/core/ContainerBase.java
@@ -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;
@@ -274,8 +281,55 @@ public abstract class ContainerBase extends LifecycleMBeanBase
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
@@ -1001,6 +1055,17 @@ public abstract class ContainerBase extends LifecycleMBeanBase
}
+ @Override
+ protected void initInternal() throws LifecycleException {
+ BlockingQueue startStopQueue =
+ new LinkedBlockingQueue();
+ startStopExecutor = new ThreadPoolExecutor(0,
+ getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
+ startStopQueue);
+ super.initInternal();
+ }
+
+
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
@@ -1029,8 +1094,24 @@ public abstract class ContainerBase extends LifecycleMBeanBase
// 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])));
+ }
+
+ boolean fail = false;
+ for (Future 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
@@ -1069,8 +1150,23 @@ public abstract class ContainerBase extends LifecycleMBeanBase
// 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])));
+ }
+
+ boolean fail = false;
+ for (Future 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
@@ -1113,6 +1209,8 @@ public abstract class ContainerBase extends LifecycleMBeanBase
parent.removeChild(this);
}
+ startStopExecutor.shutdownNow();
+
super.destroyInternal();
}
@@ -1412,4 +1510,37 @@ public abstract class ContainerBase extends LifecycleMBeanBase
}
}
}
+
+
+ // ----------------------------- 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;
+ }
+ }
}
diff --git a/java/org/apache/catalina/core/LocalStrings.properties b/java/org/apache/catalina/core/LocalStrings.properties
index 839005c..f51c19e 100644
--- a/java/org/apache/catalina/core/LocalStrings.properties
+++ b/java/org/apache/catalina/core/LocalStrings.properties
@@ -63,6 +63,8 @@ asyncContextImpl.requestEnded=The request associated with the AsyncContext has a
containerBase.alreadyStarted=Container {0} has already been started
containerBase.notConfigured=No basic Valve has been configured
containerBase.notStarted=Container {0} has not been started
+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
diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java
index a408da0..8e7b971 100644
--- a/java/org/apache/catalina/core/StandardContext.java
+++ b/java/org/apache/catalina/core/StandardContext.java
@@ -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 Context interface. Each
@@ -5201,9 +5199,7 @@ public class StandardContext extends ContainerBase
}
}
- DedicatedThreadExecutor temporaryExecutor = new DedicatedThreadExecutor();
try {
-
// Create context attributes that will be required
if (ok) {
getServletContext().setAttribute(
@@ -5228,22 +5224,7 @@ public class StandardContext extends ContainerBase
// 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() {
- @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;
}
@@ -5264,22 +5245,7 @@ public class StandardContext extends ContainerBase
// 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() {
- @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;
}
@@ -5287,27 +5253,12 @@ public class StandardContext extends ContainerBase
// 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() {
- @Override
- public Void call() throws Exception {
- ClassLoader old = bindThread();
- try {
loadOnStartup(findChildren());
- return null;
- } finally {
- unbindThread(old);
- }
- }
- });
}
} finally {
// Unbinding thread
unbindThread(oldCCL);
- temporaryExecutor.shutdown();
}
// Set available status depending upon startup success
@@ -5447,24 +5398,11 @@ public class StandardContext extends ContainerBase
// 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
@@ -5475,12 +5413,7 @@ public class StandardContext extends ContainerBase
if (manager != null && manager instanceof Lifecycle &&
((Lifecycle) manager).getState().isAvailable()) {
- try {
((Lifecycle) manager).stop();
- } catch (LifecycleException e) {
- le = e;
- return;
- }
}
// Stop our application listeners
@@ -5488,21 +5421,6 @@ public class StandardContext extends ContainerBase
}finally{
unbindThread(old);
}
- }
- };
-
- 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();
- }
// Finalize our character set mapper
setCharsetMapper(null);
diff --git a/java/org/apache/catalina/core/StandardHost.java b/java/org/apache/catalina/core/StandardHost.java
index 3b1fb50..5419f1f 100644
--- a/java/org/apache/catalina/core/StandardHost.java
+++ b/java/org/apache/catalina/core/StandardHost.java
@@ -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;
@@ -183,6 +184,11 @@ public class StandardHost extends ContainerBase implements Host {
// ------------------------------------------------------------- Properties
+ @Override
+ public ExecutorService getStartStopExecutor() {
+ return startStopExecutor;
+ }
+
/**
* Return the application root for this Host. This can be an absolute
diff --git a/java/org/apache/catalina/core/mbeans-descriptors.xml b/java/org/apache/catalina/core/mbeans-descriptors.xml
index b9253c2..426e2e2 100644
--- a/java/org/apache/catalina/core/mbeans-descriptors.xml
+++ b/java/org/apache/catalina/core/mbeans-descriptors.xml
@@ -1011,6 +1011,10 @@
description="Will children be started automatically when they are added."
type="boolean"/>
+
+
+
+
Digester 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
@@ -141,32 +135,6 @@ public class ContextConfig
/**
- * The Digester
s available to process web deployment descriptor
- * files.
- */
- protected static Digester[] webDigesters = new Digester[4];
-
-
- /**
- * The Digester
s available to process web fragment deployment
- * descriptor files.
- */
- protected static Digester[] webFragmentDigesters = new Digester[4];
-
-
- /**
- * The Rule
s used to parse the web.xml
- */
- protected static WebRuleSet webRuleSet = new WebRuleSet(false);
-
-
- /**
- * The Rule
s used to parse the web-fragment.xml
- */
- protected static WebRuleSet webFragmentRuleSet = new WebRuleSet(true);
-
-
- /**
* Deployment count.
*/
protected static long deploymentCount = 0L;
@@ -227,12 +195,14 @@ public class ContextConfig
* deployment descriptor files.
*/
protected Digester webDigester = null;
+ protected WebRuleSet webRuleSet = null;
/**
* The Digester
we will use to process web fragment
* deployment descriptor files.
*/
protected Digester webFragmentDigester = null;
+ protected WebRuleSet webFragmentRuleSet = null;
// ------------------------------------------------------------- Properties
@@ -476,60 +446,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,
+ webRuleSet = new WebRuleSet(false);
+ webDigester = 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];
+ webDigester.getParser();
- } else {
- if (webDigesters[3] == null) {
- webDigesters[3] = DigesterFactory.newDigester(validation,
- namespaceAware, webRuleSet);
- webFragmentDigesters[3] = DigesterFactory.newDigester(validation,
+ webFragmentRuleSet = new WebRuleSet(true);
+ webFragmentDigester = DigesterFactory.newDigester(validation,
namespaceAware, webFragmentRuleSet);
- webDigesters[3].getParser();
- webFragmentDigesters[3].getParser();
- }
- webDigester = webDigesters[3];
- webFragmentDigester = webFragmentDigesters[3];
- }
+ webFragmentDigester.getParser();
}
@@ -567,7 +498,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 ) {
@@ -584,7 +515,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);
@@ -596,7 +527,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);
@@ -604,7 +535,7 @@ public class ContextConfig
}
}
if (context.getConfigFile() != null)
- processContextConfig(context.getConfigFile());
+ processContextConfig(digester, context.getConfigFile());
}
@@ -612,7 +543,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()
@@ -638,16 +569,16 @@ 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);
+ digester.setClassLoader(this.getClass().getClassLoader());
+ digester.setUseContextClassLoader(false);
+ digester.push(context.getParent());
+ digester.push(context);
XmlErrorHandler errorHandler = new XmlErrorHandler();
- contextDigester.setErrorHandler(errorHandler);
- contextDigester.parse(source);
+ digester.setErrorHandler(errorHandler);
+ digester.parse(source);
if (errorHandler.getWarnings().size() > 0 ||
errorHandler.getErrors().size() > 0) {
errorHandler.logFindings(log, contextXml.toString());
@@ -668,7 +599,6 @@ public class ContextConfig
context.getName()), e);
ok = false;
} finally {
- contextDigester.reset();
try {
if (stream != null) {
stream.close();
@@ -678,7 +608,6 @@ public class ContextConfig
}
}
}
- }
/**
@@ -828,17 +757,15 @@ public class ContextConfig
protected void init() {
// Called from StandardContext.init()
- if (contextDigester == null){
- contextDigester = createContextDigester();
+ Digester contextDigester = createContextDigester();
contextDigester.getParser();
- }
if (log.isDebugEnabled())
log.debug(sm.getString("contextConfig.init"));
context.setConfigured(false);
ok = true;
- contextConfig();
+ contextConfig(contextDigester);
createWebXmlDigester(context.getXmlNamespaceAware(),
context.getXmlValidation());
@@ -1715,9 +1642,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) {
@@ -1728,10 +1652,6 @@ 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);
@@ -1764,7 +1684,6 @@ public class ContextConfig
ruleSet.recycle();
}
}
- }
/**
@@ -2152,7 +2071,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
diff --git a/java/org/apache/catalina/startup/HostConfig.java b/java/org/apache/catalina/startup/HostConfig.java
index faa033d..c9bbe16 100644
--- a/java/org/apache/catalina/startup/HostConfig.java
+++ b/java/org/apache/catalina/startup/HostConfig.java
@@ -14,8 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.catalina.startup;
@@ -30,10 +28,15 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
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 +141,8 @@ public class HostConfig
/**
* Map of deployed applications.
*/
- protected HashMap deployed =
- new HashMap();
+ protected Map deployed =
+ new ConcurrentHashMap();
/**
@@ -497,6 +500,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 +527,29 @@ public class HostConfig
if (files == null)
return;
+ ExecutorService es = host.getStartStopExecutor();
+ List> results = new ArrayList>();
+
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 +560,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 +686,9 @@ public class HostConfig
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"))
@@ -690,10 +709,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 +769,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 +960,9 @@ public class HostConfig
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"))
@@ -945,10 +973,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 +997,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
@@ -1453,4 +1487,58 @@ public class HostConfig
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);
+ }
+ }
}
diff --git a/java/org/apache/catalina/startup/LocalStrings.properties b/java/org/apache/catalina/startup/LocalStrings.properties
index ebfb620..7314bce 100644
--- a/java/org/apache/catalina/startup/LocalStrings.properties
+++ b/java/org/apache/catalina/startup/LocalStrings.properties
@@ -83,11 +83,14 @@ hostConfig.createDirs=Unable to create directory for deployment: {0}
hostConfig.deploy=Deploying web application directory {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}
diff --git a/java/org/apache/tomcat/util/threads/DedicatedThreadExecutor.java b/java/org/apache/tomcat/util/threads/DedicatedThreadExecutor.java
deleted file mode 100644
index 43e4411..0000000
--- a/java/org/apache/tomcat/util/threads/DedicatedThreadExecutor.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.tomcat.util.threads;
-
-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 java.util.concurrent.ThreadFactory;
-
-/**
- * A utility class to execute a {@link Callable} in a dedicated thread.
- * It can be used either with an instance to reuse the same thread for each call
- * to {@link #execute(Callable)} or with the static method
- * {@link #executeInOwnThread(Callable)}. When using an instance,
- * {@link #shutdown()} must be called when the instance is no longer needed to
- * dispose of the dedicated thread.
- */
-public class DedicatedThreadExecutor {
- private final SingleThreadFactory singleThreadFactory =
- new SingleThreadFactory();
- private final ExecutorService executorService =
- Executors.newSingleThreadExecutor(singleThreadFactory);
-
- /**
- * Executes the given {@link Callable} with the thread spawned for the
- * current {@link DedicatedThreadExecutor} instance, and returns its result.
- *
- * @param
- * the type of the returned value
- * @param callable
- * @return the completed result
- */
- public V execute(final Callable callable) {
- final Future futureTask = executorService.submit(callable);
-
- boolean interrupted = false;
- V result;
- while (true) {
- try {
- result = futureTask.get();
- break;
- } catch (InterruptedException e) {
- // keep waiting
- interrupted = true;
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
- if (interrupted) {
- // set interruption status so that the caller may react to it
- Thread.currentThread().interrupt();
- }
- return result;
- }
-
- /**
- * Stops the dedicated thread and waits for its death.
- */
- public void shutdown() {
- executorService.shutdown();
- if (singleThreadFactory.singleThread != null) {
- boolean interrupted = false;
- while (true) {
- try {
- singleThreadFactory.singleThread.join();
- singleThreadFactory.singleThread = null;
- break;
- } catch (InterruptedException e) {
- // keep waiting
- interrupted = true;
- }
- }
- if (interrupted) {
- // set interruption status so that the caller may react to it
- Thread.currentThread().interrupt();
- }
- }
- }
-
- /**
- * Executes the given {@link Callable} in a new thread and returns the
- * result after the thread is stopped.
- *
- * @param
- * @param callable
- * @return the completed result
- */
- public static V executeInOwnThread(
- final Callable callable) {
- DedicatedThreadExecutor executor = new DedicatedThreadExecutor();
- try {
- return executor.execute(callable);
- } finally {
- executor.shutdown();
- }
-
- }
-
- // we use a ThreadFactory so that we can later call Thread.join().
- // Indeed, calling shutdown() on an ExecutorService will eventually stop the
- // thread but it might still be alive when execute() returns (race
- // condition).
- // This can lead to false alarms about potential memory leaks because the
- // thread may have a web application class loader for its context class
- // loader.
- private static class SingleThreadFactory implements ThreadFactory {
- private volatile Thread singleThread;
-
- @Override
- public Thread newThread(Runnable r) {
- if (singleThread != null) {
- throw new IllegalStateException(
- "should not have been called more than once");
- }
- singleThread = new Thread(r);
- singleThread.setDaemon(true);
- return singleThread;
- }
-
- }
-}
diff --git a/test/org/apache/tomcat/util/threads/DedicatedThreadExecutorTest.java b/test/org/apache/tomcat/util/threads/DedicatedThreadExecutorTest.java
deleted file mode 100644
index 8d13ccf..0000000
--- a/test/org/apache/tomcat/util/threads/DedicatedThreadExecutorTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.tomcat.util.threads;
-
-import java.util.concurrent.Callable;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-
-import org.junit.Test;
-
-public class DedicatedThreadExecutorTest {
- private Thread dedicatedThread;
-
- @Test
- public void testExecute() {
- final Thread testingThread = Thread.currentThread();
- DedicatedThreadExecutor executor = new DedicatedThreadExecutor();
- Long result = executor.execute(new Callable() {
- @Override
- public Long call() throws Exception {
- dedicatedThread = Thread.currentThread();
- assertNotSame(testingThread, dedicatedThread);
- return Long.valueOf(123);
- }
- });
- assertEquals(123, result.longValue());
-
- //check that the same thread is reused
- executor.execute(new Callable() {
- @Override
- public Void call() throws Exception {
- assertSame(dedicatedThread, Thread.currentThread());
- return null;
- }
- });
-
- executor.shutdown();
- assertFalse(dedicatedThread.isAlive());
- }
-
- @Test
- public void testExecuteInOwnThread() {
- final Thread testingThread = Thread.currentThread();
- Long result =
- DedicatedThreadExecutor.executeInOwnThread(new Callable() {
- @Override
- public Long call() throws Exception {
- dedicatedThread = Thread.currentThread();
- assertNotSame(testingThread, dedicatedThread);
- return Long.valueOf(456);
- }
- });
- assertEquals(456, result.longValue());
- assertFalse(dedicatedThread.isAlive());
- }
-
-}
diff --git a/webapps/docs/config/engine.xml b/webapps/docs/config/engine.xml
index 07ddec4..c05993b 100644
--- a/webapps/docs/config/engine.xml
+++ b/webapps/docs/config/engine.xml
@@ -102,6 +102,17 @@
name.
+
+ The number of threads this Engine will use to start
+ child Host elements in parallel. 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. If
+ not specified, the default value of 1 will be used.
+
+
diff --git a/webapps/docs/config/host.xml b/webapps/docs/config/host.xml
index 53660f5..ba8f972 100644
--- a/webapps/docs/config/host.xml
+++ b/webapps/docs/config/host.xml
@@ -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. 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. If
+ not specified, the default value of 1 will be used.
+
+