ASF Bugzilla – Attachment 25411 Details for
Bug 49159
Improve ThreadLocal memory leak clean-up
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch for tomcat 7 to renew threads
patch tomcat 7 renewThreads.txt (text/plain), 30.08 KB, created by
Sylvain Laurent
on 2010-05-06 17:15:35 UTC
(
hide
)
Description:
Patch for tomcat 7 to renew threads
Filename:
MIME Type:
Creator:
Sylvain Laurent
Created:
2010-05-06 17:15:35 UTC
Size:
30.08 KB
patch
obsolete
>Index: conf/server.xml >=================================================================== >--- conf/server.xml (revision 941722) >+++ conf/server.xml (working copy) >@@ -30,6 +30,7 @@ > <!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html --> > <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" /> > <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> >+ <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> > > <!-- Global JNDI resources > Documentation at /docs/jndi-resources-howto.html >Index: java/org/apache/catalina/core/StandardContext.java >=================================================================== >--- java/org/apache/catalina/core/StandardContext.java (revision 941722) >+++ java/org/apache/catalina/core/StandardContext.java (working copy) >@@ -770,15 +770,6 @@ > private boolean clearReferencesStopThreads = false; > > /** >- * Should Tomcat attempt to clear any ThreadLocal objects that are instances >- * of classes loaded by this class loader. Failure to remove any such >- * objects will result in a memory leak on web application stop, undeploy or >- * reload. It is disabled by default since the clearing of the ThreadLocal >- * objects is not performed in a thread-safe manner. >- */ >- private boolean clearReferencesThreadLocals = false; >- >- /** > * Should the effective web.xml be logged when the context starts? > */ > private boolean logEffectiveWebXml = false; >@@ -2320,34 +2311,6 @@ > } > > >- /** >- * Return the clearReferencesThreadLocals flag for this Context. >- */ >- public boolean getClearReferencesThreadLocals() { >- >- return (this.clearReferencesThreadLocals); >- >- } >- >- >- /** >- * Set the clearReferencesThreadLocals feature for this Context. >- * >- * @param clearReferencesThreadLocals The new flag value >- */ >- public void setClearReferencesThreadLocals( >- boolean clearReferencesThreadLocals) { >- >- boolean oldClearReferencesThreadLocals = >- this.clearReferencesThreadLocals; >- this.clearReferencesThreadLocals = clearReferencesThreadLocals; >- support.firePropertyChange("clearReferencesStopThreads", >- oldClearReferencesThreadLocals, >- this.clearReferencesThreadLocals); >- >- } >- >- > // -------------------------------------------------------- Context Methods > > >Index: java/org/apache/catalina/core/StandardThreadExecutor.java >=================================================================== >--- java/org/apache/catalina/core/StandardThreadExecutor.java (revision 941722) >+++ java/org/apache/catalina/core/StandardThreadExecutor.java (working copy) >@@ -25,6 +25,9 @@ > import org.apache.catalina.LifecycleState; > import org.apache.catalina.util.LifecycleBase; > import org.apache.catalina.util.LifecycleMBeanBase; >+import org.apache.juli.logging.Log; >+import org.apache.juli.logging.LogFactory; >+import org.apache.tomcat.util.res.StringManager; > import org.apache.tomcat.util.threads.ResizableExecutor; > import org.apache.tomcat.util.threads.TaskQueue; > import org.apache.tomcat.util.threads.TaskThreadFactory; >@@ -32,6 +35,9 @@ > > public class StandardThreadExecutor extends LifecycleMBeanBase > implements Executor, ResizableExecutor { >+ >+ private static final Log log = >+ LogFactory.getLog(StandardThreadExecutor.class); > > // ---------------------------------------------- Properties > /** >@@ -67,7 +73,7 @@ > /** > * The executor we use for this component > */ >- protected ThreadPoolExecutor executor = null; >+ protected volatile ThreadPoolExecutor executor = null; > > /** > * the name of this thread pool >@@ -85,6 +91,12 @@ > protected int maxQueueSize = Integer.MAX_VALUE; > > private TaskQueue taskqueue = null; >+ >+ /** >+ * Lock to synchronize renewal of threads. >+ */ >+ private Object renewThreadsLock = new Object(); >+ > // ---------------------------------------------- Constructors > public StandardThreadExecutor() { > //empty constructor for the digester >@@ -304,4 +316,41 @@ > name.append(getName()); > return name.toString(); > } >+ >+ public void renewThreads() { >+ ThreadPoolExecutor oldExecutor; >+ synchronized (renewThreadsLock) { >+ if(log.isDebugEnabled()) { >+ log.debug("renewing threads for Executor "+getName()); >+ } >+ oldExecutor = executor; >+ ThreadPoolExecutor newExecutor = new ThreadPoolExecutor( >+ getMinSpareThreads(), getMaxThreads(), maxIdleTime, >+ TimeUnit.MILLISECONDS, taskqueue, oldExecutor >+ .getThreadFactory()); >+ >+ // prestart some core threads, but not all, especially if the >+ // Executor is not actually used (for instance the AJP connector is >+ // declared by default but it's not always used...) >+ int nbCoreThreadsToPrestart = Math.min(getMinSpareThreads(), oldExecutor.getPoolSize()); >+ for(int i=0; i<nbCoreThreadsToPrestart; i++){ >+ newExecutor.prestartCoreThread(); >+ } >+ >+ // now the new pool is ready, let's make it current >+ // we don't need to synchronize the next 2 lines. >+ // At worst a call to taskqueue.force() will have the >+ // task executed by the old pool instead of the new one >+ // it's not a problem because the old pool is not yet shut down >+ >+ // Note : the changed instance variables are volatile >+ executor = newExecutor; >+ taskqueue.setParent(executor); >+ } >+ >+ oldExecutor.shutdown(); >+ // we don't wait for termination of the old pool, threads will terminate >+ // when their work is done >+ } >+ > } >Index: java/org/apache/catalina/core/ThreadLocalLeakPreventionListener.java >=================================================================== >--- java/org/apache/catalina/core/ThreadLocalLeakPreventionListener.java (revision 0) >+++ java/org/apache/catalina/core/ThreadLocalLeakPreventionListener.java (revision 0) >@@ -0,0 +1,185 @@ >+package org.apache.catalina.core; >+ >+import java.util.concurrent.Executor; >+ >+import org.apache.catalina.Container; >+import org.apache.catalina.ContainerEvent; >+import org.apache.catalina.ContainerListener; >+import org.apache.catalina.Context; >+import org.apache.catalina.Engine; >+import org.apache.catalina.Host; >+import org.apache.catalina.Lifecycle; >+import org.apache.catalina.LifecycleEvent; >+import org.apache.catalina.LifecycleListener; >+import org.apache.catalina.Server; >+import org.apache.catalina.Service; >+import org.apache.catalina.connector.Connector; >+import org.apache.coyote.ProtocolHandler; >+import org.apache.coyote.ajp.AjpAprProtocol; >+import org.apache.coyote.ajp.AjpProtocol; >+import org.apache.coyote.http11.AbstractHttp11Protocol; >+import org.apache.coyote.http11.Http11AprProtocol; >+import org.apache.juli.logging.Log; >+import org.apache.juli.logging.LogFactory; >+import org.apache.tomcat.util.threads.ResizableExecutor; >+ >+/** >+ * A {@link LifecycleListener} that triggers >+ * {@link StandardThreadExecutor#renewThreads()} when a {@link Context} is being >+ * stopped to avoid thread-local related memory leaks. This listener must be >+ * declared in server.xml to be active. >+ * >+ * @author slaurent >+ * >+ */ >+public class ThreadLocalLeakPreventionListener implements LifecycleListener, >+ ContainerListener { >+ private static final Log log = LogFactory >+ .getLog(ThreadLocalLeakPreventionListener.class); >+ >+ /** >+ * Listens for {@link LifecycleEvent} for the start of the {@link Server} to >+ * initialize itself and then for after_stop events of each {@link Context}. >+ */ >+ @Override >+ public void lifecycleEvent(LifecycleEvent event) { >+ try { >+ Lifecycle lifecycle = event.getLifecycle(); >+ if (Lifecycle.AFTER_START_EVENT.equals(event.getType()) >+ && lifecycle instanceof Server) { >+ // when the server starts, we register ourself as listener for >+ // all context >+ // as well as container event listener so that we know when new >+ // Context are deployed >+ Server server = (Server) lifecycle; >+ registerListenersForServer(server); >+ } >+ >+ if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType()) >+ && lifecycle instanceof Context) { >+ renewThreads((Context) lifecycle); >+ } >+ } catch (Exception e) { >+ log.error("Exception processing event " + event, e); >+ } >+ } >+ >+ @Override >+ public void containerEvent(ContainerEvent event) { >+ try { >+ String type = event.getType(); >+ if (Container.ADD_CHILD_EVENT.equals(type)) { >+ processContainerAddChild(event.getContainer(), >+ (Container) event.getData()); >+ } else if (Container.REMOVE_CHILD_EVENT.equals(type)) { >+ processContainerRemoveChild(event.getContainer(), >+ (Container) event.getData()); >+ } >+ } catch (Exception e) { >+ log.error("Exception processing event " + event, e); >+ } >+ >+ } >+ >+ private void registerListenersForServer(Server server) { >+ for (Service service : server.findServices()) { >+ Engine engine = (Engine) service.getContainer(); >+ engine.addContainerListener(this); >+ registerListenersForEngine(engine); >+ } >+ >+ } >+ >+ private void registerListenersForEngine(Engine engine) { >+ for (Container hostContainer : engine.findChildren()) { >+ Host host = (Host) hostContainer; >+ host.addContainerListener(this); >+ registerListenersForHost(host); >+ } >+ } >+ >+ private void registerListenersForHost(Host host) { >+ for (Container contextContainer : host.findChildren()) { >+ Context context = (Context) contextContainer; >+ registerContextListener(context); >+ } >+ } >+ >+ private void registerContextListener(Context context) { >+ context.addLifecycleListener(this); >+ } >+ >+ protected void processContainerAddChild(Container parent, Container child) { >+ if (log.isDebugEnabled()) >+ log.debug("Process addChild[parent=" + parent + ",child=" + child >+ + "]"); >+ >+ try { >+ if (child instanceof Context) { >+ registerContextListener((Context) child); >+ } else if (child instanceof Engine) { >+ registerListenersForEngine((Engine) child); >+ } else if (child instanceof Host) { >+ registerListenersForHost((Host) child); >+ } >+ } catch (Throwable t) { >+ log.error("processContainerAddChild: Throwable", t); >+ } >+ >+ } >+ >+ protected void processContainerRemoveChild(Container parent, Container child) { >+ >+ if (log.isDebugEnabled()) >+ log.debug("Process removeChild[parent=" + parent + ",child=" >+ + child + "]"); >+ >+ try { >+ if (child instanceof Context) { >+ Context context = (Context) child; >+ context.removeLifecycleListener(this); >+ } else if (child instanceof Host) { >+ Host host = (Host) child; >+ host.removeContainerListener(this); >+ } else if (child instanceof Engine) { >+ Engine engine = (Engine) child; >+ engine.removeContainerListener(this); >+ } >+ } catch (Throwable t) { >+ log.error("processContainerRemoveChild: Throwable", t); >+ } >+ >+ } >+ >+ /** >+ * Asks each {@link StandardThreadExecutor} to renew its threads. >+ * >+ * @param context >+ * the context being stopped, used to discover all the Connectors >+ * of its parent Service. >+ */ >+ private void renewThreads(Context context) { >+ Engine engine = (Engine) context.getParent().getParent(); >+ Service service = engine.getService(); >+ Connector[] connectors = service.findConnectors(); >+ if (connectors != null) { >+ for (Connector connector : connectors) { >+ ProtocolHandler handler = connector.getProtocolHandler(); >+ Executor executor = null; >+ if (handler instanceof AbstractHttp11Protocol) { >+ executor = ((AbstractHttp11Protocol) handler) >+ .getExecutor(); >+ } else if (handler instanceof AjpProtocol) { >+ executor = ((AjpProtocol) handler).getExecutor(); >+ } else if (handler instanceof AjpAprProtocol) { >+ executor = ((AjpAprProtocol) handler).getExecutor(); >+ } else if (handler instanceof Http11AprProtocol) { >+ executor = ((Http11AprProtocol) handler).getExecutor(); >+ } >+ if (executor instanceof ResizableExecutor) { >+ ((ResizableExecutor) executor).renewThreads(); >+ } >+ } >+ } >+ } >+} >Index: java/org/apache/catalina/core/mbeans-descriptors.xml >=================================================================== >--- java/org/apache/catalina/core/mbeans-descriptors.xml (revision 941722) >+++ java/org/apache/catalina/core/mbeans-descriptors.xml (working copy) >@@ -891,6 +891,13 @@ > <attribute name="threadPriority" > description="The thread priority for threads in this thread pool" > type="int"/> >+ >+ <operation name="renewThreads" >+ description="Recreate the thread pool" >+ impact="ACTION" >+ returnType="void"> >+ </operation> >+ > </mbean> > > <mbean name="StandardWrapper" >Index: java/org/apache/catalina/loader/LocalStrings.properties >=================================================================== >--- java/org/apache/catalina/loader/LocalStrings.properties (revision 941722) >+++ java/org/apache/catalina/loader/LocalStrings.properties (working copy) >@@ -40,8 +40,6 @@ > webappClassLoader.clearRmiFail=Failed to clear context class loader referenced from sun.rmi.transport.Target for web application [{0}] > webappClassLoader.clearThreadLocalDebug=The web application [{0}] created a ThreadLocal with key of type [{1}] (value [{2}]). The ThreadLocal has been correctly set to null and the key will be removed by GC. > webappClassLoader.clearThreadLocal=The web application [{0}] created a ThreadLocal with key of type [{1}] (value [{2}]) and a value of type [{3}] (value [{4}]) but failed to remove it when the web application was stopped. This is very likely to create a memory leak. >-webappClassLoader.clearThreadLocalDebugClear=To simplify the process of tracing memory leaks, the key has been forcibly removed. >-webappClassLoader.clearThreadLocalClear=To prevent a memory leak, the ThreadLocal has been forcibly removed. > webappClassLoader.clearThreadLocalFail=Failed to clear ThreadLocal references for web application [{0}] > webappClassLoader.stopThreadFail=Failed to terminate thread named [{0}] for web application [{1}] > webappClassLoader.stopTimerThreadFail=Failed to terminate TimerThread named [{0}] for web application [{1}] >Index: java/org/apache/catalina/loader/WebappClassLoader.java >=================================================================== >--- java/org/apache/catalina/loader/WebappClassLoader.java (revision 941722) >+++ java/org/apache/catalina/loader/WebappClassLoader.java (working copy) >@@ -451,15 +451,6 @@ > private boolean clearReferencesStopThreads = false; > > /** >- * Should Tomcat attempt to clear any ThreadLocal objects that are instances >- * of classes loaded by this class loader. Failure to remove any such >- * objects will result in a memory leak on web application stop, undeploy or >- * reload. It is disabled by default since the clearing of the ThreadLocal >- * objects is not performed in a thread-safe manner. >- */ >- private boolean clearReferencesThreadLocals = false; >- >- /** > * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()} > * when the class loader is stopped? If not specified, the default value > * of <code>true</code> is used. Changing the default setting is likely to >@@ -704,28 +695,7 @@ > } > > >- >- > /** >- * Return the clearReferencesThreadLocals flag for this Context. >- */ >- public boolean getClearReferencesThreadLocals() { >- return (this.clearReferencesThreadLocals); >- } >- >- >- /** >- * Set the clearReferencesThreadLocals feature for this Context. >- * >- * @param clearReferencesThreadLocals The new flag value >- */ >- public void setClearReferencesThreadLocals( >- boolean clearReferencesThreadLocals) { >- this.clearReferencesThreadLocals = clearReferencesThreadLocals; >- } >- >- >- /** > * Set the clearReferencesLogFactoryRelease feature for this Context. > * > * @param clearReferencesLogFactoryRelease The new flag value >@@ -1891,7 +1861,7 @@ > clearReferencesThreads(); > > // Clear any ThreadLocals loaded by this class loader >- clearReferencesThreadLocals(); >+ checkThreadLocalsForLeak(); > > // Clear RMI Targets loaded by this class loader > clearReferencesRmiTargets(); >@@ -2202,7 +2172,7 @@ > } > } > >- >+ //TODO : vrifier que c'est bien dsactiv par dfaut (BZ...) > private void clearReferencesStopTimerThread(Thread thread) { > > // Need to get references to: >@@ -2250,7 +2220,7 @@ > } > } > >- private void clearReferencesThreadLocals() { >+ private void checkThreadLocalsForLeak() { > Thread[] threads = getThreads(); > > try { >@@ -2274,11 +2244,11 @@ > if (threads[i] != null) { > // Clear the first map > threadLocalMap = threadLocalsField.get(threads[i]); >- clearThreadLocalMap(threadLocalMap, tableField); >+ checkThreadLocalMapForLeaks(threadLocalMap, tableField); > // Clear the second map > threadLocalMap = > inheritableThreadLocalsField.get(threads[i]); >- clearThreadLocalMap(threadLocalMap, tableField); >+ checkThreadLocalMapForLeaks(threadLocalMap, tableField); > } > } > } catch (SecurityException e) { >@@ -2307,40 +2277,33 @@ > > > /* >- * Clears the given thread local map object. Also pass in the field that >+ * Analyzes the given thread local map object. Also pass in the field that > * points to the internal table to save re-calculating it on every > * call to this method. > */ >- private void clearThreadLocalMap(Object map, Field internalTableField) >+ private void checkThreadLocalMapForLeaks(Object map, Field internalTableField) > throws NoSuchMethodException, IllegalAccessException, > NoSuchFieldException, InvocationTargetException { > if (map != null) { >- Method mapRemove = >- map.getClass().getDeclaredMethod("remove", >- ThreadLocal.class); >- mapRemove.setAccessible(true); > Object[] table = (Object[]) internalTableField.get(map); >- int staleEntriesCount = 0; > if (table != null) { > for (int j =0; j < table.length; j++) { > if (table[j] != null) { >- boolean remove = false; >+ boolean potentialLeak = false; > // Check the key > Object key = ((Reference<?>) table[j]).get(); >- if (this.equals(key) || (key != null && >- this == key.getClass().getClassLoader())) { >- remove = true; >+ if (this.equals(key) || objectIsLoadedByThisOrChildClassLoader(key)) { >+ potentialLeak = true; > } > // Check the value > Field valueField = > table[j].getClass().getDeclaredField("value"); > valueField.setAccessible(true); > Object value = valueField.get(table[j]); >- if (this.equals(value) || (value != null && >- this == value.getClass().getClassLoader())) { >- remove = true; >+ if (this.equals(value) || objectIsLoadedByThisOrChildClassLoader(value)) { >+ potentialLeak = true; > } >- if (remove) { >+ if (potentialLeak) { > Object[] args = new Object[5]; > args[0] = contextName; > if (key != null) { >@@ -2356,37 +2319,16 @@ > log.debug(sm.getString( > "webappClassLoader.clearThreadLocalDebug", > args)); >- if (clearReferencesThreadLocals) { >- log.debug(sm.getString( >- "webappClassLoader.clearThreadLocalDebugClear")); >- } > } > } else { > log.error(sm.getString( > "webappClassLoader.clearThreadLocal", > args)); >- if (clearReferencesThreadLocals) { >- log.info(sm.getString( >- "webappClassLoader.clearThreadLocalClear")); >- } > } >- if (clearReferencesThreadLocals) { >- if (key == null) { >- staleEntriesCount++; >- } else { >- mapRemove.invoke(map, key); >- } >- } > } > } > } > } >- if (staleEntriesCount > 0) { >- Method mapRemoveStale = >- map.getClass().getDeclaredMethod("expungeStaleEntries"); >- mapRemoveStale.setAccessible(true); >- mapRemoveStale.invoke(map); >- } > } > } > >@@ -2576,6 +2518,23 @@ > } > > >+ private boolean objectIsLoadedByThisOrChildClassLoader(Object o) { >+ if(o == null) { >+ return false; >+ } >+ >+ Class clazz = o.getClass(); >+ if(o instanceof Class) { >+ clazz = (Class)clazz; >+ } >+ >+ for(ClassLoader cl = clazz.getClassLoader(); cl != null; cl = cl.getParent()) { >+ if(cl == this) { >+ return true; >+ } >+ } >+ return false; >+ } > /** > * Determine whether a class was loaded by this class loader or one of > * its child class loaders. >Index: java/org/apache/catalina/loader/WebappLoader.java >=================================================================== >--- java/org/apache/catalina/loader/WebappLoader.java (revision 941722) >+++ java/org/apache/catalina/loader/WebappLoader.java (working copy) >@@ -576,8 +576,6 @@ > ((StandardContext) container).getClearReferencesStatic()); > classLoader.setClearReferencesStopThreads( > ((StandardContext) container).getClearReferencesStopThreads()); >- classLoader.setClearReferencesThreadLocals( >- ((StandardContext) container).getClearReferencesThreadLocals()); > } > > for (int i = 0; i < repositories.length; i++) { >Index: java/org/apache/tomcat/util/net/AbstractEndpoint.java >=================================================================== >--- java/org/apache/tomcat/util/net/AbstractEndpoint.java (revision 941722) >+++ java/org/apache/tomcat/util/net/AbstractEndpoint.java (working copy) >@@ -21,10 +21,11 @@ > import java.net.InetSocketAddress; > import java.util.StringTokenizer; > import java.util.concurrent.Executor; >-import java.util.concurrent.TimeUnit; > > import javax.net.ssl.KeyManagerFactory; > >+import org.apache.catalina.LifecycleException; >+import org.apache.catalina.core.StandardThreadExecutor; > import org.apache.juli.logging.Log; > import org.apache.juli.logging.LogFactory; > import org.apache.tomcat.util.IntrospectionUtils; >@@ -32,7 +33,6 @@ > import org.apache.tomcat.util.res.StringManager; > import org.apache.tomcat.util.threads.ResizableExecutor; > import org.apache.tomcat.util.threads.TaskQueue; >-import org.apache.tomcat.util.threads.TaskThreadFactory; > import org.apache.tomcat.util.threads.ThreadPoolExecutor; > /** > * >@@ -370,22 +370,25 @@ > } > > >- public void createExecutor() { >+ public void createExecutor() throws LifecycleException { > internalExecutor = true; >- TaskQueue taskqueue = new TaskQueue(); >- TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority()); >- executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf); >- taskqueue.setParent( (ThreadPoolExecutor) executor); >+ StandardThreadExecutor stdExecutor = new StandardThreadExecutor(); >+ stdExecutor.setMinSpareThreads(getMinSpareThreads()); >+ stdExecutor.setMaxThreads(getMaxThreads()); >+ stdExecutor.setMaxIdleTime(60*1000); >+ stdExecutor.setName(getName()+"-exec-"); >+ stdExecutor.setDomain("dummy"); // TODO slaurent : inject correct domain >+ stdExecutor.setDaemon(daemon); >+ >+ stdExecutor.start(); >+ executor = stdExecutor; > } > >- public void shutdownExecutor() { >+ public void shutdownExecutor() throws LifecycleException { > if ( executor!=null && internalExecutor ) { >- if ( executor instanceof ThreadPoolExecutor ) { >- //this is our internal one, so we need to shut it down >- ThreadPoolExecutor tpe = (ThreadPoolExecutor) executor; >- tpe.shutdownNow(); >- TaskQueue queue = (TaskQueue) tpe.getQueue(); >- queue.setParent(null); >+ if (executor instanceof StandardThreadExecutor) { >+ //this is our internal one, so we need to shut it down ourselves >+ ((StandardThreadExecutor) executor).stop(); > } > executor = null; > } >Index: java/org/apache/tomcat/util/net/AprEndpoint.java >=================================================================== >--- java/org/apache/tomcat/util/net/AprEndpoint.java (revision 941722) >+++ java/org/apache/tomcat/util/net/AprEndpoint.java (working copy) >@@ -21,6 +21,7 @@ > import java.util.HashMap; > import java.util.concurrent.RejectedExecutionException; > >+import org.apache.catalina.LifecycleException; > import org.apache.juli.logging.Log; > import org.apache.juli.logging.LogFactory; > import org.apache.tomcat.jni.Address; >@@ -605,8 +606,9 @@ > > /** > * Stop the endpoint. This will cause all processing threads to stop. >+ * @throws LifecycleException > */ >- public void stop() { >+ public void stop() throws LifecycleException { > if (running) { > running = false; > unlockAccept(); >Index: java/org/apache/tomcat/util/net/JIoEndpoint.java >=================================================================== >--- java/org/apache/tomcat/util/net/JIoEndpoint.java (revision 941722) >+++ java/org/apache/tomcat/util/net/JIoEndpoint.java (working copy) >@@ -29,6 +29,7 @@ > import java.util.concurrent.RejectedExecutionException; > > import org.apache.catalina.Globals; >+import org.apache.catalina.LifecycleException; > import org.apache.juli.logging.Log; > import org.apache.juli.logging.LogFactory; > import org.apache.tomcat.util.IntrospectionUtils; >@@ -417,7 +418,7 @@ > } > } > >- public void stop() { >+ public void stop() throws Exception { > if (running) { > running = false; > unlockAccept(); >Index: java/org/apache/tomcat/util/net/NioEndpoint.java >=================================================================== >--- java/org/apache/tomcat/util/net/NioEndpoint.java (revision 941722) >+++ java/org/apache/tomcat/util/net/NioEndpoint.java (working copy) >@@ -49,6 +49,7 @@ > import javax.net.ssl.TrustManagerFactory; > import javax.net.ssl.X509KeyManager; > >+import org.apache.catalina.LifecycleException; > import org.apache.juli.logging.Log; > import org.apache.juli.logging.LogFactory; > import org.apache.tomcat.util.IntrospectionUtils; >@@ -633,8 +634,9 @@ > > /** > * Stop the endpoint. This will cause all processing threads to stop. >+ * @throws LifecycleException > */ >- public void stop() { >+ public void stop() throws LifecycleException { > if (running) { > running = false; > unlockAccept(); >Index: java/org/apache/tomcat/util/threads/ResizableExecutor.java >=================================================================== >--- java/org/apache/tomcat/util/threads/ResizableExecutor.java (revision 941722) >+++ java/org/apache/tomcat/util/threads/ResizableExecutor.java (working copy) >@@ -37,4 +37,12 @@ > > public boolean resizeQueue(int capacity); > >+ /** >+ * Cleanly stops all threads of the Executor and create new ones. This is >+ * useful to work around some types of memory leaks caused by uncleaned >+ * ThreadLocal instances. >+ * The implemenation of this method must be thread-safe. >+ */ >+ public void renewThreads(); >+ > } >Index: java/org/apache/tomcat/util/threads/TaskQueue.java >=================================================================== >--- java/org/apache/tomcat/util/threads/TaskQueue.java (revision 941722) >+++ java/org/apache/tomcat/util/threads/TaskQueue.java (working copy) >@@ -30,7 +30,9 @@ > * > */ > public class TaskQueue extends LinkedBlockingQueue<Runnable> { >- private ThreadPoolExecutor parent = null; >+ //parent executor is marked volatile so that it can be changed by >+ //any thread without synchronization >+ private volatile ThreadPoolExecutor parent = null; > > public TaskQueue() { > super(); >Index: webapps/docs/config/context.xml >=================================================================== >--- webapps/docs/config/context.xml (revision 941722) >+++ webapps/docs/config/context.xml (working copy) >@@ -387,15 +387,6 @@ > default value of <code>false</code> will be used.</p> > </attribute> > >- <attribute name="clearReferencesThreadLocals" required="false"> >- <p>If <code>true</code>, Tomcat attempts to clear any ThreadLocal >- objects that are instances of classes loaded by this class loader. >- Failure to remove any such objects will result in a memory leak on web >- application stop, undeploy or reload. If not specified, the default >- value of <code>false</code> will be used since the clearing of the >- ThreadLocal objects is not performed in a thread-safe manner.</p> >- </attribute> >- > <attribute name="processTlds" required="false"> > <p>Whether the context should process TLDs on startup. The default > is true. The false setting is intended for special cases
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 49159
:
25411
|
26068
|
26074
|
26097
|
26108
|
26150
|
26156
|
26157