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 Digesters available to process web deployment descriptor - * files. - */ - protected static Digester[] webDigesters = new Digester[4]; - - - /** - * The Digesters available to process web fragment deployment - * descriptor files. - */ - protected static Digester[] webFragmentDigesters = new Digester[4]; - - - /** - * The Rules used to parse the web.xml - */ - protected static WebRuleSet webRuleSet = new WebRuleSet(false); - - - /** - * The Rules 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.

+
+