View | Details | Raw Unified | Return to bug 49159
Collapse All | Expand All

(-)conf/server.xml (+1 lines)
Lines 30-35 Link Here
30
  <!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html -->
30
  <!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html -->
31
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
31
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
32
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
32
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
33
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
33
34
34
  <!-- Global JNDI resources
35
  <!-- Global JNDI resources
35
       Documentation at /docs/jndi-resources-howto.html
36
       Documentation at /docs/jndi-resources-howto.html
(-)java/org/apache/catalina/core/StandardContext.java (-37 lines)
Lines 770-784 Link Here
770
    private boolean clearReferencesStopThreads = false;
770
    private boolean clearReferencesStopThreads = false;
771
    
771
    
772
    /**
772
    /**
773
     * Should Tomcat attempt to clear any ThreadLocal objects that are instances
774
     * of classes loaded by this class loader. Failure to remove any such
775
     * objects will result in a memory leak on web application stop, undeploy or
776
     * reload. It is disabled by default since the clearing of the ThreadLocal
777
     * objects is not performed in a thread-safe manner.
778
     */
779
    private boolean clearReferencesThreadLocals = false;
780
    
781
    /**
782
     * Should the effective web.xml be logged when the context starts?
773
     * Should the effective web.xml be logged when the context starts?
783
     */
774
     */
784
    private boolean logEffectiveWebXml = false;
775
    private boolean logEffectiveWebXml = false;
Lines 2320-2353 Link Here
2320
    }
2311
    }
2321
2312
2322
2313
2323
    /**
2324
     * Return the clearReferencesThreadLocals flag for this Context.
2325
     */
2326
    public boolean getClearReferencesThreadLocals() {
2327
2328
        return (this.clearReferencesThreadLocals);
2329
2330
    }
2331
2332
2333
    /**
2334
     * Set the clearReferencesThreadLocals feature for this Context.
2335
     *
2336
     * @param clearReferencesThreadLocals The new flag value
2337
     */
2338
    public void setClearReferencesThreadLocals(
2339
            boolean clearReferencesThreadLocals) {
2340
2341
        boolean oldClearReferencesThreadLocals =
2342
            this.clearReferencesThreadLocals;
2343
        this.clearReferencesThreadLocals = clearReferencesThreadLocals;
2344
        support.firePropertyChange("clearReferencesStopThreads",
2345
                                   oldClearReferencesThreadLocals,
2346
                                   this.clearReferencesThreadLocals);
2347
2348
    }
2349
2350
2351
    // -------------------------------------------------------- Context Methods
2314
    // -------------------------------------------------------- Context Methods
2352
2315
2353
2316
(-)java/org/apache/catalina/core/StandardThreadExecutor.java (-1 / +50 lines)
Lines 25-30 Link Here
25
import org.apache.catalina.LifecycleState;
25
import org.apache.catalina.LifecycleState;
26
import org.apache.catalina.util.LifecycleBase;
26
import org.apache.catalina.util.LifecycleBase;
27
import org.apache.catalina.util.LifecycleMBeanBase;
27
import org.apache.catalina.util.LifecycleMBeanBase;
28
import org.apache.juli.logging.Log;
29
import org.apache.juli.logging.LogFactory;
30
import org.apache.tomcat.util.res.StringManager;
28
import org.apache.tomcat.util.threads.ResizableExecutor;
31
import org.apache.tomcat.util.threads.ResizableExecutor;
29
import org.apache.tomcat.util.threads.TaskQueue;
32
import org.apache.tomcat.util.threads.TaskQueue;
30
import org.apache.tomcat.util.threads.TaskThreadFactory;
33
import org.apache.tomcat.util.threads.TaskThreadFactory;
Lines 32-37 Link Here
32
35
33
public class StandardThreadExecutor extends LifecycleMBeanBase
36
public class StandardThreadExecutor extends LifecycleMBeanBase
34
        implements Executor, ResizableExecutor {
37
        implements Executor, ResizableExecutor {
38
        
39
    private static final Log log =
40
        LogFactory.getLog(StandardThreadExecutor.class);
35
    
41
    
36
    // ---------------------------------------------- Properties
42
    // ---------------------------------------------- Properties
37
    /**
43
    /**
Lines 67-73 Link Here
67
    /**
73
    /**
68
     * The executor we use for this component
74
     * The executor we use for this component
69
     */
75
     */
70
    protected ThreadPoolExecutor executor = null;
76
    protected volatile ThreadPoolExecutor executor = null;
71
    
77
    
72
    /**
78
    /**
73
     * the name of this thread pool
79
     * the name of this thread pool
Lines 85-90 Link Here
85
    protected int maxQueueSize = Integer.MAX_VALUE;
91
    protected int maxQueueSize = Integer.MAX_VALUE;
86
    
92
    
87
    private TaskQueue taskqueue = null;
93
    private TaskQueue taskqueue = null;
94
    
95
    /**
96
     * Lock to synchronize renewal of threads.
97
     */
98
    private Object renewThreadsLock = new Object();
99
    
88
    // ---------------------------------------------- Constructors
100
    // ---------------------------------------------- Constructors
89
    public StandardThreadExecutor() {
101
    public StandardThreadExecutor() {
90
        //empty constructor for the digester
102
        //empty constructor for the digester
Lines 304-307 Link Here
304
        name.append(getName());
316
        name.append(getName());
305
        return name.toString();
317
        return name.toString();
306
    }
318
    }
319
    
320
    	public void renewThreads() {
321
		ThreadPoolExecutor oldExecutor;
322
		synchronized (renewThreadsLock) {
323
			if(log.isDebugEnabled()) {
324
				log.debug("renewing threads for Executor "+getName());
325
			}
326
			oldExecutor = executor;
327
			ThreadPoolExecutor newExecutor = new ThreadPoolExecutor(
328
					getMinSpareThreads(), getMaxThreads(), maxIdleTime,
329
					TimeUnit.MILLISECONDS, taskqueue, oldExecutor
330
							.getThreadFactory());
331
332
			// prestart some core threads, but not all, especially if the
333
			// Executor is not actually used (for instance the AJP connector is
334
			// declared by default but it's not always used...)
335
			int nbCoreThreadsToPrestart = Math.min(getMinSpareThreads(), oldExecutor.getPoolSize());
336
			for(int i=0; i<nbCoreThreadsToPrestart; i++){
337
				newExecutor.prestartCoreThread();
338
			}
339
340
			// now the new pool is ready, let's make it current
341
			// we don't need to synchronize the next 2 lines.
342
			// At worst a call to taskqueue.force() will have the
343
			// task executed by the old pool instead of the new one
344
			// it's not a problem because the old pool is not yet shut down
345
346
			// Note : the changed instance variables are volatile
347
			executor = newExecutor;
348
			taskqueue.setParent(executor);
349
		}
350
351
		oldExecutor.shutdown();
352
		// we don't wait for termination of the old pool, threads will terminate
353
		// when their work is done
354
	}
355
    
307
}
356
}
(-)java/org/apache/catalina/core/ThreadLocalLeakPreventionListener.java (+185 lines)
Line 0 Link Here
1
package org.apache.catalina.core;
2
3
import java.util.concurrent.Executor;
4
5
import org.apache.catalina.Container;
6
import org.apache.catalina.ContainerEvent;
7
import org.apache.catalina.ContainerListener;
8
import org.apache.catalina.Context;
9
import org.apache.catalina.Engine;
10
import org.apache.catalina.Host;
11
import org.apache.catalina.Lifecycle;
12
import org.apache.catalina.LifecycleEvent;
13
import org.apache.catalina.LifecycleListener;
14
import org.apache.catalina.Server;
15
import org.apache.catalina.Service;
16
import org.apache.catalina.connector.Connector;
17
import org.apache.coyote.ProtocolHandler;
18
import org.apache.coyote.ajp.AjpAprProtocol;
19
import org.apache.coyote.ajp.AjpProtocol;
20
import org.apache.coyote.http11.AbstractHttp11Protocol;
21
import org.apache.coyote.http11.Http11AprProtocol;
22
import org.apache.juli.logging.Log;
23
import org.apache.juli.logging.LogFactory;
24
import org.apache.tomcat.util.threads.ResizableExecutor;
25
26
/**
27
 * A {@link LifecycleListener} that triggers
28
 * {@link StandardThreadExecutor#renewThreads()} when a {@link Context} is being
29
 * stopped to avoid thread-local related memory leaks. This listener must be
30
 * declared in server.xml to be active.
31
 * 
32
 * @author slaurent
33
 * 
34
 */
35
public class ThreadLocalLeakPreventionListener implements LifecycleListener,
36
		ContainerListener {
37
	private static final Log log = LogFactory
38
			.getLog(ThreadLocalLeakPreventionListener.class);
39
40
	/**
41
	 * Listens for {@link LifecycleEvent} for the start of the {@link Server} to
42
	 * initialize itself and then for after_stop events of each {@link Context}.
43
	 */
44
	@Override
45
	public void lifecycleEvent(LifecycleEvent event) {
46
		try {
47
			Lifecycle lifecycle = event.getLifecycle();
48
			if (Lifecycle.AFTER_START_EVENT.equals(event.getType())
49
					&& lifecycle instanceof Server) {
50
				// when the server starts, we register ourself as listener for
51
				// all context
52
				// as well as container event listener so that we know when new
53
				// Context are deployed
54
				Server server = (Server) lifecycle;
55
				registerListenersForServer(server);
56
			}
57
58
			if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType())
59
					&& lifecycle instanceof Context) {
60
				renewThreads((Context) lifecycle);
61
			}
62
		} catch (Exception e) {
63
			log.error("Exception processing event " + event, e);
64
		}
65
	}
66
67
	@Override
68
	public void containerEvent(ContainerEvent event) {
69
		try {
70
			String type = event.getType();
71
			if (Container.ADD_CHILD_EVENT.equals(type)) {
72
				processContainerAddChild(event.getContainer(),
73
						(Container) event.getData());
74
			} else if (Container.REMOVE_CHILD_EVENT.equals(type)) {
75
				processContainerRemoveChild(event.getContainer(),
76
						(Container) event.getData());
77
			}
78
		} catch (Exception e) {
79
			log.error("Exception processing event " + event, e);
80
		}
81
82
	}
83
84
	private void registerListenersForServer(Server server) {
85
		for (Service service : server.findServices()) {
86
			Engine engine = (Engine) service.getContainer();
87
			engine.addContainerListener(this);
88
			registerListenersForEngine(engine);
89
		}
90
91
	}
92
93
	private void registerListenersForEngine(Engine engine) {
94
		for (Container hostContainer : engine.findChildren()) {
95
			Host host = (Host) hostContainer;
96
			host.addContainerListener(this);
97
			registerListenersForHost(host);
98
		}
99
	}
100
101
	private void registerListenersForHost(Host host) {
102
		for (Container contextContainer : host.findChildren()) {
103
			Context context = (Context) contextContainer;
104
			registerContextListener(context);
105
		}
106
	}
107
108
	private void registerContextListener(Context context) {
109
		context.addLifecycleListener(this);
110
	}
111
112
	protected void processContainerAddChild(Container parent, Container child) {
113
		if (log.isDebugEnabled())
114
			log.debug("Process addChild[parent=" + parent + ",child=" + child
115
					+ "]");
116
117
		try {
118
			if (child instanceof Context) {
119
				registerContextListener((Context) child);
120
			} else if (child instanceof Engine) {
121
				registerListenersForEngine((Engine) child);
122
			} else if (child instanceof Host) {
123
				registerListenersForHost((Host) child);
124
			}
125
		} catch (Throwable t) {
126
			log.error("processContainerAddChild: Throwable", t);
127
		}
128
129
	}
130
131
	protected void processContainerRemoveChild(Container parent, Container child) {
132
133
		if (log.isDebugEnabled())
134
			log.debug("Process removeChild[parent=" + parent + ",child="
135
					+ child + "]");
136
137
		try {
138
			if (child instanceof Context) {
139
				Context context = (Context) child;
140
				context.removeLifecycleListener(this);
141
			} else if (child instanceof Host) {
142
				Host host = (Host) child;
143
				host.removeContainerListener(this);
144
			} else if (child instanceof Engine) {
145
				Engine engine = (Engine) child;
146
				engine.removeContainerListener(this);
147
			}
148
		} catch (Throwable t) {
149
			log.error("processContainerRemoveChild: Throwable", t);
150
		}
151
152
	}
153
154
	/**
155
	 * Asks each {@link StandardThreadExecutor} to renew its threads.
156
	 * 
157
	 * @param context
158
	 *            the context being stopped, used to discover all the Connectors
159
	 *            of its parent Service.
160
	 */
161
	private void renewThreads(Context context) {
162
		Engine engine = (Engine) context.getParent().getParent();
163
		Service service = engine.getService();
164
		Connector[] connectors = service.findConnectors();
165
		if (connectors != null) {
166
			for (Connector connector : connectors) {
167
				ProtocolHandler handler = connector.getProtocolHandler();
168
				Executor executor = null;
169
				if (handler instanceof AbstractHttp11Protocol) {
170
					executor = ((AbstractHttp11Protocol) handler)
171
							.getExecutor();
172
				} else if (handler instanceof AjpProtocol) {
173
					executor = ((AjpProtocol) handler).getExecutor();
174
				} else if (handler instanceof AjpAprProtocol) {
175
					executor = ((AjpAprProtocol) handler).getExecutor();
176
				} else if (handler instanceof Http11AprProtocol) {
177
					executor = ((Http11AprProtocol) handler).getExecutor();
178
				}
179
				if (executor instanceof ResizableExecutor) {
180
					((ResizableExecutor) executor).renewThreads();
181
				}
182
			}
183
		}
184
	}
185
}
(-)java/org/apache/catalina/core/mbeans-descriptors.xml (+7 lines)
Lines 891-896 Link Here
891
    <attribute name="threadPriority"
891
    <attribute name="threadPriority"
892
               description="The thread priority for threads in this thread pool"
892
               description="The thread priority for threads in this thread pool"
893
               type="int"/>
893
               type="int"/>
894
895
    <operation name="renewThreads"
896
               description="Recreate the thread pool"
897
               impact="ACTION"
898
               returnType="void">
899
    </operation>
900
               
894
  </mbean>
901
  </mbean>
895
902
896
  <mbean name="StandardWrapper"
903
  <mbean name="StandardWrapper"
(-)java/org/apache/catalina/loader/LocalStrings.properties (-2 lines)
Lines 40-47 Link Here
40
webappClassLoader.clearRmiFail=Failed to clear context class loader referenced from sun.rmi.transport.Target for web application [{0}]
40
webappClassLoader.clearRmiFail=Failed to clear context class loader referenced from sun.rmi.transport.Target for web application [{0}]
41
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.
41
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.
42
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.
42
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.
43
webappClassLoader.clearThreadLocalDebugClear=To simplify the process of tracing memory leaks, the key has been forcibly removed.
44
webappClassLoader.clearThreadLocalClear=To prevent a memory leak, the ThreadLocal has been forcibly removed.
45
webappClassLoader.clearThreadLocalFail=Failed to clear ThreadLocal references for web application [{0}]
43
webappClassLoader.clearThreadLocalFail=Failed to clear ThreadLocal references for web application [{0}]
46
webappClassLoader.stopThreadFail=Failed to terminate thread named [{0}] for web application [{1}]
44
webappClassLoader.stopThreadFail=Failed to terminate thread named [{0}] for web application [{1}]
47
webappClassLoader.stopTimerThreadFail=Failed to terminate TimerThread named [{0}] for web application [{1}]
45
webappClassLoader.stopTimerThreadFail=Failed to terminate TimerThread named [{0}] for web application [{1}]
(-)java/org/apache/catalina/loader/WebappClassLoader.java (-71 / +30 lines)
Lines 451-465 Link Here
451
    private boolean clearReferencesStopThreads = false;
451
    private boolean clearReferencesStopThreads = false;
452
452
453
    /**
453
    /**
454
     * Should Tomcat attempt to clear any ThreadLocal objects that are instances
455
     * of classes loaded by this class loader. Failure to remove any such
456
     * objects will result in a memory leak on web application stop, undeploy or
457
     * reload. It is disabled by default since the clearing of the ThreadLocal
458
     * objects is not performed in a thread-safe manner.
459
     */
460
    private boolean clearReferencesThreadLocals = false;
461
    
462
    /**
463
     * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()}
454
     * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()}
464
     * when the class loader is stopped? If not specified, the default value
455
     * when the class loader is stopped? If not specified, the default value
465
     * of <code>true</code> is used. Changing the default setting is likely to
456
     * of <code>true</code> is used. Changing the default setting is likely to
Lines 704-731 Link Here
704
     }
695
     }
705
696
706
697
707
708
709
     /**
698
     /**
710
      * Return the clearReferencesThreadLocals flag for this Context.
711
      */
712
     public boolean getClearReferencesThreadLocals() {
713
         return (this.clearReferencesThreadLocals);
714
     }
715
716
717
     /**
718
      * Set the clearReferencesThreadLocals feature for this Context.
719
      *
720
      * @param clearReferencesThreadLocals The new flag value
721
      */
722
     public void setClearReferencesThreadLocals(
723
             boolean clearReferencesThreadLocals) {
724
         this.clearReferencesThreadLocals = clearReferencesThreadLocals;
725
     }
726
727
728
     /**
729
      * Set the clearReferencesLogFactoryRelease feature for this Context.
699
      * Set the clearReferencesLogFactoryRelease feature for this Context.
730
      *
700
      *
731
      * @param clearReferencesLogFactoryRelease The new flag value
701
      * @param clearReferencesLogFactoryRelease The new flag value
Lines 1891-1897 Link Here
1891
        clearReferencesThreads();
1861
        clearReferencesThreads();
1892
        
1862
        
1893
        // Clear any ThreadLocals loaded by this class loader
1863
        // Clear any ThreadLocals loaded by this class loader
1894
        clearReferencesThreadLocals();
1864
        checkThreadLocalsForLeak();
1895
        
1865
        
1896
        // Clear RMI Targets loaded by this class loader
1866
        // Clear RMI Targets loaded by this class loader
1897
        clearReferencesRmiTargets();
1867
        clearReferencesRmiTargets();
Lines 2202-2208 Link Here
2202
        }
2172
        }
2203
    }
2173
    }
2204
2174
2205
    
2175
    //TODO : vŽrifier que c'est bien dŽsactivŽ par dŽfaut (BZ...)
2206
    private void clearReferencesStopTimerThread(Thread thread) {
2176
    private void clearReferencesStopTimerThread(Thread thread) {
2207
        
2177
        
2208
        // Need to get references to:
2178
        // Need to get references to:
Lines 2250-2256 Link Here
2250
        }
2220
        }
2251
    }
2221
    }
2252
2222
2253
    private void clearReferencesThreadLocals() {
2223
    private void checkThreadLocalsForLeak() {
2254
        Thread[] threads = getThreads();
2224
        Thread[] threads = getThreads();
2255
2225
2256
        try {
2226
        try {
Lines 2274-2284 Link Here
2274
                if (threads[i] != null) {
2244
                if (threads[i] != null) {
2275
                    // Clear the first map
2245
                    // Clear the first map
2276
                    threadLocalMap = threadLocalsField.get(threads[i]);
2246
                    threadLocalMap = threadLocalsField.get(threads[i]);
2277
                    clearThreadLocalMap(threadLocalMap, tableField);
2247
                    checkThreadLocalMapForLeaks(threadLocalMap, tableField);
2278
                    // Clear the second map
2248
                    // Clear the second map
2279
                    threadLocalMap =
2249
                    threadLocalMap =
2280
                        inheritableThreadLocalsField.get(threads[i]);
2250
                        inheritableThreadLocalsField.get(threads[i]);
2281
                    clearThreadLocalMap(threadLocalMap, tableField);
2251
                    checkThreadLocalMapForLeaks(threadLocalMap, tableField);
2282
                }
2252
                }
2283
            }
2253
            }
2284
        } catch (SecurityException e) {
2254
        } catch (SecurityException e) {
Lines 2307-2346 Link Here
2307
2277
2308
2278
2309
    /*
2279
    /*
2310
     * Clears the given thread local map object. Also pass in the field that
2280
     * Analyzes the given thread local map object. Also pass in the field that
2311
     * points to the internal table to save re-calculating it on every
2281
     * points to the internal table to save re-calculating it on every
2312
     * call to this method.
2282
     * call to this method.
2313
     */
2283
     */
2314
    private void clearThreadLocalMap(Object map, Field internalTableField)
2284
    private void checkThreadLocalMapForLeaks(Object map, Field internalTableField)
2315
            throws NoSuchMethodException, IllegalAccessException,
2285
            throws NoSuchMethodException, IllegalAccessException,
2316
            NoSuchFieldException, InvocationTargetException {
2286
            NoSuchFieldException, InvocationTargetException {
2317
        if (map != null) {
2287
        if (map != null) {
2318
            Method mapRemove =
2319
                map.getClass().getDeclaredMethod("remove",
2320
                        ThreadLocal.class);
2321
            mapRemove.setAccessible(true);
2322
            Object[] table = (Object[]) internalTableField.get(map);
2288
            Object[] table = (Object[]) internalTableField.get(map);
2323
            int staleEntriesCount = 0;
2324
            if (table != null) {
2289
            if (table != null) {
2325
                for (int j =0; j < table.length; j++) {
2290
                for (int j =0; j < table.length; j++) {
2326
                    if (table[j] != null) {
2291
                    if (table[j] != null) {
2327
                        boolean remove = false;
2292
                        boolean potentialLeak = false;
2328
                        // Check the key
2293
                        // Check the key
2329
                        Object key = ((Reference<?>) table[j]).get();
2294
                        Object key = ((Reference<?>) table[j]).get();
2330
                        if (this.equals(key) || (key != null &&
2295
                        if (this.equals(key) || objectIsLoadedByThisOrChildClassLoader(key)) {
2331
                                this == key.getClass().getClassLoader())) {
2296
                            potentialLeak = true;
2332
                            remove = true;
2333
                        }
2297
                        }
2334
                        // Check the value
2298
                        // Check the value
2335
                        Field valueField =
2299
                        Field valueField =
2336
                            table[j].getClass().getDeclaredField("value");
2300
                            table[j].getClass().getDeclaredField("value");
2337
                        valueField.setAccessible(true);
2301
                        valueField.setAccessible(true);
2338
                        Object value = valueField.get(table[j]);
2302
                        Object value = valueField.get(table[j]);
2339
                        if (this.equals(value) || (value != null &&
2303
                        if (this.equals(value) || objectIsLoadedByThisOrChildClassLoader(value)) {
2340
                                this == value.getClass().getClassLoader())) {
2304
                            potentialLeak = true;
2341
                            remove = true;
2342
                        }
2305
                        }
2343
                        if (remove) {
2306
                        if (potentialLeak) {
2344
                            Object[] args = new Object[5];
2307
                            Object[] args = new Object[5];
2345
                            args[0] = contextName;
2308
                            args[0] = contextName;
2346
                            if (key != null) {
2309
                            if (key != null) {
Lines 2356-2392 Link Here
2356
                                    log.debug(sm.getString(
2319
                                    log.debug(sm.getString(
2357
                                            "webappClassLoader.clearThreadLocalDebug",
2320
                                            "webappClassLoader.clearThreadLocalDebug",
2358
                                            args));
2321
                                            args));
2359
                                    if (clearReferencesThreadLocals) {
2360
                                        log.debug(sm.getString(
2361
                                                "webappClassLoader.clearThreadLocalDebugClear"));
2362
                                    }
2363
                                }
2322
                                }
2364
                            } else {
2323
                            } else {
2365
                                log.error(sm.getString(
2324
                                log.error(sm.getString(
2366
                                        "webappClassLoader.clearThreadLocal",
2325
                                        "webappClassLoader.clearThreadLocal",
2367
                                        args));
2326
                                        args));
2368
                                if (clearReferencesThreadLocals) {
2369
                                    log.info(sm.getString(
2370
                                            "webappClassLoader.clearThreadLocalClear"));
2371
                                }
2372
                            }
2327
                            }
2373
                            if (clearReferencesThreadLocals) {
2374
                                if (key == null) {
2375
                                  staleEntriesCount++;
2376
                                } else {
2377
                                  mapRemove.invoke(map, key);
2378
                                }
2379
                            }
2380
                        }
2328
                        }
2381
                    }
2329
                    }
2382
                }
2330
                }
2383
            }
2331
            }
2384
            if (staleEntriesCount > 0) {
2385
                Method mapRemoveStale =
2386
                    map.getClass().getDeclaredMethod("expungeStaleEntries");
2387
                mapRemoveStale.setAccessible(true);
2388
                mapRemoveStale.invoke(map);
2389
            }
2390
        }
2332
        }
2391
    }
2333
    }
2392
2334
Lines 2576-2581 Link Here
2576
    }
2518
    }
2577
2519
2578
2520
2521
    private boolean objectIsLoadedByThisOrChildClassLoader(Object o) {
2522
    	if(o == null) {
2523
    		return false;
2524
    	}
2525
2526
    	Class clazz = o.getClass();
2527
    	if(o instanceof Class) {
2528
    		clazz = (Class)clazz;
2529
    	}
2530
    	
2531
    	for(ClassLoader cl = clazz.getClassLoader(); cl != null; cl = cl.getParent()) {
2532
    		if(cl == this) {
2533
    			return true;
2534
    		}
2535
    	}
2536
    	return false;
2537
    }
2579
    /**
2538
    /**
2580
     * Determine whether a class was loaded by this class loader or one of
2539
     * Determine whether a class was loaded by this class loader or one of
2581
     * its child class loaders.
2540
     * its child class loaders.
(-)java/org/apache/catalina/loader/WebappLoader.java (-2 lines)
Lines 576-583 Link Here
576
                        ((StandardContext) container).getClearReferencesStatic());
576
                        ((StandardContext) container).getClearReferencesStatic());
577
                classLoader.setClearReferencesStopThreads(
577
                classLoader.setClearReferencesStopThreads(
578
                        ((StandardContext) container).getClearReferencesStopThreads());
578
                        ((StandardContext) container).getClearReferencesStopThreads());
579
                classLoader.setClearReferencesThreadLocals(
580
                        ((StandardContext) container).getClearReferencesThreadLocals());
581
            }
579
            }
582
580
583
            for (int i = 0; i < repositories.length; i++) {
581
            for (int i = 0; i < repositories.length; i++) {
(-)java/org/apache/tomcat/util/net/AbstractEndpoint.java (-14 / +17 lines)
Lines 21-30 Link Here
21
import java.net.InetSocketAddress;
21
import java.net.InetSocketAddress;
22
import java.util.StringTokenizer;
22
import java.util.StringTokenizer;
23
import java.util.concurrent.Executor;
23
import java.util.concurrent.Executor;
24
import java.util.concurrent.TimeUnit;
25
24
26
import javax.net.ssl.KeyManagerFactory;
25
import javax.net.ssl.KeyManagerFactory;
27
26
27
import org.apache.catalina.LifecycleException;
28
import org.apache.catalina.core.StandardThreadExecutor;
28
import org.apache.juli.logging.Log;
29
import org.apache.juli.logging.Log;
29
import org.apache.juli.logging.LogFactory;
30
import org.apache.juli.logging.LogFactory;
30
import org.apache.tomcat.util.IntrospectionUtils;
31
import org.apache.tomcat.util.IntrospectionUtils;
Lines 32-38 Link Here
32
import org.apache.tomcat.util.res.StringManager;
33
import org.apache.tomcat.util.res.StringManager;
33
import org.apache.tomcat.util.threads.ResizableExecutor;
34
import org.apache.tomcat.util.threads.ResizableExecutor;
34
import org.apache.tomcat.util.threads.TaskQueue;
35
import org.apache.tomcat.util.threads.TaskQueue;
35
import org.apache.tomcat.util.threads.TaskThreadFactory;
36
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
36
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
37
/**
37
/**
38
 * 
38
 * 
Lines 370-391 Link Here
370
    }
370
    }
371
    
371
    
372
372
373
    public void createExecutor() {
373
    public void createExecutor() throws LifecycleException {
374
        internalExecutor = true;
374
        internalExecutor = true;
375
        TaskQueue taskqueue = new TaskQueue();
375
        StandardThreadExecutor stdExecutor = new StandardThreadExecutor();
376
        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
376
        stdExecutor.setMinSpareThreads(getMinSpareThreads());
377
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
377
        stdExecutor.setMaxThreads(getMaxThreads());
378
        taskqueue.setParent( (ThreadPoolExecutor) executor);
378
        stdExecutor.setMaxIdleTime(60*1000);
379
        stdExecutor.setName(getName()+"-exec-");
380
        stdExecutor.setDomain("dummy"); // TODO slaurent : inject correct domain
381
        stdExecutor.setDaemon(daemon);
382
        
383
        stdExecutor.start();
384
        executor = stdExecutor;
379
    }
385
    }
380
    
386
    
381
    public void shutdownExecutor() {
387
    public void shutdownExecutor() throws LifecycleException {
382
        if ( executor!=null && internalExecutor ) {
388
        if ( executor!=null && internalExecutor ) {
383
            if ( executor instanceof ThreadPoolExecutor ) {
389
            if (executor instanceof StandardThreadExecutor) {
384
                //this is our internal one, so we need to shut it down
390
            	//this is our internal one, so we need to shut it down ourselves
385
                ThreadPoolExecutor tpe = (ThreadPoolExecutor) executor;
391
            	((StandardThreadExecutor) executor).stop();
386
                tpe.shutdownNow();
387
                TaskQueue queue = (TaskQueue) tpe.getQueue();
388
                queue.setParent(null);
389
            }
392
            }
390
            executor = null;
393
            executor = null;
391
        }
394
        }
(-)java/org/apache/tomcat/util/net/AprEndpoint.java (-1 / +3 lines)
Lines 21-26 Link Here
21
import java.util.HashMap;
21
import java.util.HashMap;
22
import java.util.concurrent.RejectedExecutionException;
22
import java.util.concurrent.RejectedExecutionException;
23
23
24
import org.apache.catalina.LifecycleException;
24
import org.apache.juli.logging.Log;
25
import org.apache.juli.logging.Log;
25
import org.apache.juli.logging.LogFactory;
26
import org.apache.juli.logging.LogFactory;
26
import org.apache.tomcat.jni.Address;
27
import org.apache.tomcat.jni.Address;
Lines 605-612 Link Here
605
606
606
    /**
607
    /**
607
     * Stop the endpoint. This will cause all processing threads to stop.
608
     * Stop the endpoint. This will cause all processing threads to stop.
609
     * @throws LifecycleException 
608
     */
610
     */
609
    public void stop() {
611
    public void stop() throws LifecycleException {
610
        if (running) {
612
        if (running) {
611
            running = false;
613
            running = false;
612
            unlockAccept();
614
            unlockAccept();
(-)java/org/apache/tomcat/util/net/JIoEndpoint.java (-1 / +2 lines)
Lines 29-34 Link Here
29
import java.util.concurrent.RejectedExecutionException;
29
import java.util.concurrent.RejectedExecutionException;
30
30
31
import org.apache.catalina.Globals;
31
import org.apache.catalina.Globals;
32
import org.apache.catalina.LifecycleException;
32
import org.apache.juli.logging.Log;
33
import org.apache.juli.logging.Log;
33
import org.apache.juli.logging.LogFactory;
34
import org.apache.juli.logging.LogFactory;
34
import org.apache.tomcat.util.IntrospectionUtils;
35
import org.apache.tomcat.util.IntrospectionUtils;
Lines 417-423 Link Here
417
        }
418
        }
418
    }
419
    }
419
420
420
    public void stop() {
421
    public void stop() throws Exception {
421
        if (running) {
422
        if (running) {
422
            running = false;
423
            running = false;
423
            unlockAccept();
424
            unlockAccept();
(-)java/org/apache/tomcat/util/net/NioEndpoint.java (-1 / +3 lines)
Lines 49-54 Link Here
49
import javax.net.ssl.TrustManagerFactory;
49
import javax.net.ssl.TrustManagerFactory;
50
import javax.net.ssl.X509KeyManager;
50
import javax.net.ssl.X509KeyManager;
51
51
52
import org.apache.catalina.LifecycleException;
52
import org.apache.juli.logging.Log;
53
import org.apache.juli.logging.Log;
53
import org.apache.juli.logging.LogFactory;
54
import org.apache.juli.logging.LogFactory;
54
import org.apache.tomcat.util.IntrospectionUtils;
55
import org.apache.tomcat.util.IntrospectionUtils;
Lines 633-640 Link Here
633
634
634
    /**
635
    /**
635
     * Stop the endpoint. This will cause all processing threads to stop.
636
     * Stop the endpoint. This will cause all processing threads to stop.
637
     * @throws LifecycleException 
636
     */
638
     */
637
    public void stop() {
639
    public void stop() throws LifecycleException {
638
        if (running) {
640
        if (running) {
639
            running = false;
641
            running = false;
640
            unlockAccept();
642
            unlockAccept();
(-)java/org/apache/tomcat/util/threads/ResizableExecutor.java (+8 lines)
Lines 37-40 Link Here
37
    
37
    
38
    public boolean resizeQueue(int capacity);
38
    public boolean resizeQueue(int capacity);
39
    
39
    
40
	/**
41
	 * Cleanly stops all threads of the Executor and create new ones. This is
42
	 * useful to work around some types of memory leaks caused by uncleaned
43
	 * ThreadLocal instances.
44
	 * The implemenation of this method must be thread-safe.
45
	 */
46
	public void renewThreads();
47
    
40
}
48
}
(-)java/org/apache/tomcat/util/threads/TaskQueue.java (-1 / +3 lines)
Lines 30-36 Link Here
30
 *
30
 *
31
 */
31
 */
32
public class TaskQueue extends LinkedBlockingQueue<Runnable> {
32
public class TaskQueue extends LinkedBlockingQueue<Runnable> {
33
    private ThreadPoolExecutor parent = null;
33
	//parent executor is marked volatile so that it can be changed by 
34
	//any thread without synchronization
35
    private volatile ThreadPoolExecutor parent = null;
34
36
35
    public TaskQueue() {
37
    public TaskQueue() {
36
        super();
38
        super();
(-)webapps/docs/config/context.xml (-9 lines)
Lines 387-401 Link Here
387
        default value of <code>false</code> will be used.</p>
387
        default value of <code>false</code> will be used.</p>
388
      </attribute>
388
      </attribute>
389
389
390
      <attribute name="clearReferencesThreadLocals" required="false">
391
        <p>If <code>true</code>, Tomcat attempts to clear any ThreadLocal
392
        objects that are instances of classes loaded by this class loader.
393
        Failure to remove any such objects will result in a memory leak on web
394
        application stop, undeploy or reload.  If not specified, the default
395
        value of <code>false</code> will be used since the clearing of the
396
        ThreadLocal objects is not performed in a thread-safe manner.</p>
397
      </attribute>
398
399
      <attribute name="processTlds" required="false">
390
      <attribute name="processTlds" required="false">
400
        <p>Whether the context should process TLDs on startup.  The default
391
        <p>Whether the context should process TLDs on startup.  The default
401
        is true.  The false setting is intended for special cases
392
        is true.  The false setting is intended for special cases

Return to bug 49159