ASF Bugzilla – Attachment 27767 Details for
Bug 46264
Shutting down tomcat with large number of contexts is slow
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Threaded start, stop and deployment for Contexts
tc8-bug46264-v3.patch (text/plain), 49.33 KB, created by
Mark Thomas
on 2011-10-13 10:37:08 UTC
(
hide
)
Description:
Threaded start, stop and deployment for Contexts
Filename:
MIME Type:
Creator:
Mark Thomas
Created:
2011-10-13 10:37:08 UTC
Size:
49.33 KB
patch
obsolete
>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..cabdc00 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,20 @@ public abstract class ContainerBase extends LifecycleMBeanBase > } > > >+ @Override >+ protected void initInternal() throws LifecycleException { >+ BlockingQueue<Runnable> startStopQueue = >+ new LinkedBlockingQueue<Runnable>(); >+ startStopExecutor = new ThreadPoolExecutor( >+ getStartStopThreadsInternal(), >+ getStartStopThreadsInternal(), 10, TimeUnit.SECONDS, >+ startStopQueue); >+ // Allow executor thread pool size to drop to zero if not required >+ startStopExecutor.allowCoreThreadTimeOut(true); >+ super.initInternal(); >+ } >+ >+ > /** > * Start this component and implement the requirements > * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. >@@ -1029,8 +1097,23 @@ public abstract class ContainerBase extends LifecycleMBeanBase > > // Start our child containers, if any > Container children[] = findChildren(); >+ List<Future<Void>> results = new ArrayList<Future<Void>>(); > for (int i = 0; i < children.length; i++) { >- children[i].start(); >+ results.add(startStopExecutor.submit(new StartChild(children[i]))); >+ } >+ Iterator<Future<Void>> iter = results.iterator(); >+ boolean fail = false; >+ while (iter.hasNext()) { >+ try { >+ iter.next().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 +1152,23 @@ public abstract class ContainerBase extends LifecycleMBeanBase > > // Stop our child containers, if any > Container children[] = findChildren(); >+ List<Future<Void>> results = new ArrayList<Future<Void>>(); > for (int i = 0; i < children.length; i++) { >- children[i].stop(); >+ results.add(startStopExecutor.submit(new StopChild(children[i]))); >+ } >+ Iterator<Future<Void>> iter = results.iterator(); >+ boolean fail = false; >+ while (iter.hasNext()) { >+ try { >+ iter.next().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 +1211,8 @@ public abstract class ContainerBase extends LifecycleMBeanBase > parent.removeChild(this); > } > >+ startStopExecutor.shutdownNow(); >+ > super.destroyInternal(); > } > >@@ -1412,4 +1512,37 @@ public abstract class ContainerBase extends LifecycleMBeanBase > } > } > } >+ >+ >+ // ----------------------------- Inner classes used with start/stop Executor >+ >+ private static class StartChild implements Callable<Void> { >+ >+ private Container child; >+ >+ public StartChild(Container child) { >+ this.child = child; >+ } >+ >+ @Override >+ public Void call() throws LifecycleException { >+ child.start(); >+ return null; >+ } >+ } >+ >+ private static class StopChild implements Callable<Void> { >+ >+ private Container child; >+ >+ public StopChild(Container child) { >+ this.child = child; >+ } >+ >+ @Override >+ public Void call() throws LifecycleException { >+ child.stop(); >+ return null; >+ } >+ } > } >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 <b>Context</b> 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<Boolean>() { >- @Override >- public Boolean call() throws Exception { >- ClassLoader old = bindThread(); >- try { >- return Boolean.valueOf(listenerStart()); >- } finally { >- unbindThread(old); >- } >- } >- }); >- if (!listenerStarted.booleanValue()) { >+ if (!listenerStart()) { > log.error( "Error listenerStart"); > ok = false; > } >@@ -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<Boolean>() { >- @Override >- public Boolean call() throws Exception { >- ClassLoader old = bindThread(); >- try { >- return Boolean.valueOf(filterStart()); >- } finally { >- unbindThread(old); >- } >- } >- }); >- if (!filterStarted.booleanValue()) { >+ if (!filterStart()) { > log.error("Error filterStart"); > ok = false; > } >@@ -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<Void>() { >- @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"/> > >+ <attribute name="startStopThreads" >+ description="The number of threads to use when starting and stopping child Hosts" >+ type="int"/> >+ > <attribute name="stateName" > description="The name of the LifecycleState that this component is currently in" > type="java.lang.String" >@@ -1198,6 +1202,10 @@ > description="Will children be started automatically when they are added?" > type="boolean"/> > >+ <attribute name="startStopThreads" >+ description="The number of threads to use when starting, stopping and deploying child Contexts" >+ type="int"/> >+ > <attribute name="stateName" > description="The name of the LifecycleState that this component is currently in" > type="java.lang.String" >diff --git a/java/org/apache/catalina/startup/ContextConfig.java b/java/org/apache/catalina/startup/ContextConfig.java >index bb17406..16b42ba 100644 >--- a/java/org/apache/catalina/startup/ContextConfig.java >+++ b/java/org/apache/catalina/startup/ContextConfig.java >@@ -125,12 +125,6 @@ public class ContextConfig > protected static final LoginConfig DUMMY_LOGIN_CONFIG = > new LoginConfig("NONE", null, null, null); > >- /** >- * The <code>Digester</code> we will use to process web application >- * context files. >- */ >- protected static Digester contextDigester = null; >- > > /** > * The set of Authenticators that we know how to configure. The key is >@@ -141,32 +135,6 @@ public class ContextConfig > > > /** >- * The <code>Digester</code>s available to process web deployment descriptor >- * files. >- */ >- protected static Digester[] webDigesters = new Digester[4]; >- >- >- /** >- * The <code>Digester</code>s available to process web fragment deployment >- * descriptor files. >- */ >- protected static Digester[] webFragmentDigesters = new Digester[4]; >- >- >- /** >- * The <code>Rule</code>s used to parse the web.xml >- */ >- protected static WebRuleSet webRuleSet = new WebRuleSet(false); >- >- >- /** >- * The <code>Rule</code>s used to parse the web-fragment.xml >- */ >- protected static WebRuleSet webFragmentRuleSet = new WebRuleSet(true); >- >- >- /** > * Deployment count. > */ > protected static long deploymentCount = 0L; >@@ -227,12 +195,14 @@ public class ContextConfig > * deployment descriptor files. > */ > protected Digester webDigester = null; >+ protected WebRuleSet webRuleSet = null; > > /** > * The <code>Digester</code> we will use to process web fragment > * deployment descriptor files. > */ > protected Digester webFragmentDigester = null; >+ protected WebRuleSet webFragmentRuleSet = null; > > > // ------------------------------------------------------------- Properties >@@ -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 14ce01b..476bb8f 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<String, DeployedApplication> deployed = >- new HashMap<String, DeployedApplication>(); >+ protected Map<String, DeployedApplication> deployed = >+ new ConcurrentHashMap<String, DeployedApplication>(); > > > /** >@@ -520,17 +523,30 @@ public class HostConfig > if (files == null) > return; > >+ ExecutorService es = host.getStartStopExecutor(); >+ List<Future<?>> results = new ArrayList<Future<?>>(); >+ > for (int i = 0; i < files.length; i++) { > File contextXml = new File(configBase, files[i]); > > if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".xml")) { > ContextName cn = new ContextName(files[i]); >- String name = cn.getName(); > >- if (isServiced(name)) >+ if (isServiced(cn.getName()) || deploymentExists(cn.getName())) > continue; > >- deployDescriptor(cn, contextXml); >+ results.add( >+ es.submit(new DeployDescriptor(this, cn, contextXml))); >+ } >+ } >+ >+ Iterator<Future<?>> iter = results.iterator(); >+ while (iter.hasNext()) { >+ try { >+ iter.next().get(); >+ } catch (Exception e) { >+ log.error(sm.getString( >+ "hostConfig.deployDescriptor.threaded.error"), e); > } > } > } >@@ -541,9 +557,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,14 +683,17 @@ public class HostConfig > if (files == null) > return; > >+ ExecutorService es = host.getStartStopExecutor(); >+ List<Future<?>> results = new ArrayList<Future<?>>(); >+ > for (int i = 0; i < files.length; i++) { > > if (files[i].equalsIgnoreCase("META-INF")) > 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]); >@@ -690,10 +706,20 @@ public class HostConfig > continue; > } > >- if (isServiced(cn.getName())) >+ if (isServiced(cn.getName()) || deploymentExists(cn.getName())) > continue; > >- deployWAR(cn, dir); >+ results.add(es.submit(new DeployWar(this, cn, war))); >+ } >+ } >+ >+ Iterator<Future<?>> iter = results.iterator(); >+ while (iter.hasNext()) { >+ try { >+ iter.next().get(); >+ } catch (Exception e) { >+ log.error(sm.getString( >+ "hostConfig.deployWar.threaded.error"), e); > } > } > } >@@ -741,9 +767,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 +958,9 @@ public class HostConfig > if (files == null) > return; > >+ ExecutorService es = host.getStartStopExecutor(); >+ List<Future<?>> results = new ArrayList<Future<?>>(); >+ > for (int i = 0; i < files.length; i++) { > > if (files[i].equalsIgnoreCase("META-INF")) >@@ -945,10 +971,20 @@ 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))); >+ } >+ } >+ >+ Iterator<Future<?>> iter = results.iterator(); >+ while (iter.hasNext()) { >+ try { >+ iter.next().get(); >+ } catch (Exception e) { >+ log.error(sm.getString( >+ "hostConfig.deployDir.threaded.error"), e); > } > } > } >@@ -960,9 +996,6 @@ public class HostConfig > */ > protected void deployDirectory(ContextName cn, File dir) { > >- if (deploymentExists(cn.getName())) >- return; >- > DeployedApplication deployedApp = new DeployedApplication(cn.getName()); > > // Deploy the application in this directory >@@ -1453,4 +1486,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..4dd7f2d 100644 >--- a/java/org/apache/catalina/startup/LocalStrings.properties >+++ b/java/org/apache/catalina/startup/LocalStrings.properties >@@ -83,11 +83,13 @@ 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.deployWar=Deploying web application archive {0} >+hostConfig.deployDir.threaded.error=Error waiting for multi-thread deployment of directories to completehostConfig.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 <V> >- * the type of the returned value >- * @param callable >- * @return the completed result >- */ >- public <V> V execute(final Callable<V> callable) { >- final Future<V> 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 <V> >- * @param callable >- * @return the completed result >- */ >- public static <V> V executeInOwnThread( >- final Callable<V> 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<Long>() { >- @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<Void>() { >- @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<Long>() { >- @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..d1b5709 100644 >--- a/webapps/docs/config/engine.xml >+++ b/webapps/docs/config/engine.xml >@@ -102,6 +102,17 @@ > name.</em></p> > </attribute> > >+ <attribute name="startStopThreads" required="false"> >+ <p>The number of threads this <strong>Engine</strong> will use to start >+ child <a href="host.html">Host</a> elements in parallel. If not >+ specified, the default value of 1 will be used. The special value of 0 >+ will result in the value of >+ <code>Runtime.getRuntime().availableProcessors()</code> being used. >+ Negative values will result in >+ <code>Runtime.getRuntime().availableProcessors() + value</code> being >+ used unless this is less than 1 in which case 1 thread will be used.</p> >+ </attribute> >+ > </attributes> > > </subsection> >diff --git a/webapps/docs/config/host.xml b/webapps/docs/config/host.xml >index 53660f5..f596880 100644 >--- a/webapps/docs/config/host.xml >+++ b/webapps/docs/config/host.xml >@@ -188,6 +188,19 @@ > virtual host.</p> > </attribute> > >+ <attribute name="startStopThreads" required="false"> >+ <p>The number of threads this <strong>Host</strong> will use to start >+ child <a href="context.html">Context</a> elements in parallel. The same >+ thread pool will be used to deploy new >+ <a href="context.html">Context</a>s if automatic deployment is being >+ used. If not specified, the default value of 1 will be used. The special >+ value of 0 will result in the value of >+ <code>Runtime.getRuntime().availableProcessors()</code> being used. >+ Negative values will result in >+ <code>Runtime.getRuntime().availableProcessors() + value</code> being >+ used unless this is less than 1 in which case 1 thread will be used.</p> >+ </attribute> >+ > </attributes> > > </subsection>
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 46264
:
22912
|
27755
|
27758
|
27759
|
27760
|
27761
|
27767
|
27769
|
27772
|
27846