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

(-)conf/server.xml (+2 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
  <!-- To use ThreadLocalLeakPreventionListener, the Connectors must be configured with an Executor -->
34
  <!--<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />-->
33
35
34
  <!-- Global JNDI resources
36
  <!-- Global JNDI resources
35
       Documentation at /docs/jndi-resources-howto.html
37
       Documentation at /docs/jndi-resources-howto.html
(-)java/org/apache/catalina/core/StandardContext.java (-29 / +14 lines)
Lines 750-762 Link Here
750
    private boolean clearReferencesStopTimerThreads = false;
750
    private boolean clearReferencesStopTimerThreads = false;
751
    
751
    
752
    /**
752
    /**
753
     * Should Tomcat attempt to clear any ThreadLocal objects that are instances
753
     * Should Tomcat renew the threads of the thread pool when the application
754
     * of classes loaded by this class loader. Failure to remove any such
754
     * is stopped to avoid memory leaks because of uncleaned ThreadLocal
755
     * objects will result in a memory leak on web application stop, undeploy or
755
     * variables. This also requires that the threadRenewalDelay property of the
756
     * reload. It is disabled by default since the clearing of the ThreadLocal
756
     * StandardThreadExecutor of ThreadPoolExecutor be set to a positive value.
757
     * objects is not performed in a thread-safe manner.
758
     */
757
     */
759
    private boolean clearReferencesThreadLocals = false;
758
    private boolean renewThreadsWhenStoppingContext = true;
760
    
759
    
761
    // ----------------------------------------------------- Context Properties
760
    // ----------------------------------------------------- Context Properties
762
761
Lines 2184-2214 Link Here
2184
    }
2183
    }
2185
2184
2186
2185
2187
    /**
2186
    public boolean getRenewThreadsWhenStoppingContext() {
2188
     * Return the clearReferencesThreadLocals flag for this Context.
2187
        return this.renewThreadsWhenStoppingContext;
2189
     */
2190
    public boolean getClearReferencesThreadLocals() {
2191
2192
        return (this.clearReferencesThreadLocals);
2193
2194
    }
2188
    }
2195
2189
2196
2190
    public void setRenewThreadsWhenStoppingContext(boolean renewThreadsWhenStoppingContext) {
2197
    /**
2191
        boolean oldRenewThreadsWhenStoppingContext =
2198
     * Set the clearReferencesStopThreads feature for this Context.
2192
            this.renewThreadsWhenStoppingContext;
2199
     *
2193
        this.renewThreadsWhenStoppingContext = renewThreadsWhenStoppingContext;
2200
     * @param clearReferencesStopThreads The new flag value
2194
        support.firePropertyChange("renewThreadsWhenStoppingContext",
2201
     */
2195
                oldRenewThreadsWhenStoppingContext,
2202
    public void setClearReferencesThreadLocals(
2196
                this.renewThreadsWhenStoppingContext);
2203
            boolean clearReferencesThreadLocals) {
2204
2205
        boolean oldClearReferencesThreadLocals =
2206
            this.clearReferencesThreadLocals;
2207
        this.clearReferencesThreadLocals = clearReferencesThreadLocals;
2208
        support.firePropertyChange("clearReferencesStopThreads",
2209
                                   oldClearReferencesThreadLocals,
2210
                                   this.clearReferencesThreadLocals);
2211
2212
    }
2197
    }
2213
2198
2214
2199
(-)java/org/apache/catalina/core/StandardThreadExecutor.java (-75 / +30 lines)
Lines 17-34 Link Here
17
17
18
package org.apache.catalina.core;
18
package org.apache.catalina.core;
19
19
20
import java.util.Collection;
21
import java.util.concurrent.LinkedBlockingQueue;
22
import java.util.concurrent.ThreadFactory;
23
import java.util.concurrent.ThreadPoolExecutor;
24
import java.util.concurrent.TimeUnit;
20
import java.util.concurrent.TimeUnit;
25
import java.util.concurrent.atomic.AtomicInteger;
26
21
27
import org.apache.catalina.Executor;
22
import org.apache.catalina.Executor;
28
import org.apache.catalina.LifecycleException;
23
import org.apache.catalina.LifecycleException;
29
import org.apache.catalina.LifecycleListener;
24
import org.apache.catalina.LifecycleListener;
30
import org.apache.catalina.util.LifecycleSupport;
25
import org.apache.catalina.util.LifecycleSupport;
31
import java.util.concurrent.RejectedExecutionException;
26
import org.apache.tomcat.util.threads.TaskQueue;
27
import org.apache.tomcat.util.threads.TaskThreadFactory;
28
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
32
29
33
public class StandardThreadExecutor implements Executor {
30
public class StandardThreadExecutor implements Executor {
34
    
31
    
Lines 49-54 Link Here
49
    
46
    
50
    protected String name;
47
    protected String name;
51
    
48
    
49
    /**
50
     * After a context is stopped, threads in the pool are renewed. To avoid
51
     * renewing all threads at the same time, this delay is observed between 2
52
     * threads being renewed.
53
     */
54
    protected long threadRenewalDelay = 1000L;
55
52
    private LifecycleSupport lifecycle = new LifecycleSupport(this);
56
    private LifecycleSupport lifecycle = new LifecycleSupport(this);
53
    // ---------------------------------------------- Constructors
57
    // ---------------------------------------------- Constructors
54
    public StandardThreadExecutor() {
58
    public StandardThreadExecutor() {
Lines 61-67 Link Here
61
    public void start() throws LifecycleException {
65
    public void start() throws LifecycleException {
62
        lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
66
        lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
63
        TaskQueue taskqueue = new TaskQueue();
67
        TaskQueue taskqueue = new TaskQueue();
64
        TaskThreadFactory tf = new TaskThreadFactory(namePrefix);
68
        TaskThreadFactory tf = new TaskThreadFactory(namePrefix, daemon, threadPriority);
65
        lifecycle.fireLifecycleEvent(START_EVENT, null);
69
        lifecycle.fireLifecycleEvent(START_EVENT, null);
66
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
70
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
67
        taskqueue.setParent( (ThreadPoolExecutor) executor);
71
        taskqueue.setParent( (ThreadPoolExecutor) executor);
Lines 77-92 Link Here
77
    }
81
    }
78
    
82
    
79
    public void execute(Runnable command) {
83
    public void execute(Runnable command) {
80
        if ( executor != null ) {
84
        if (executor != null) {
81
            try {
85
            executor.execute(command);
82
                executor.execute(command);
83
            } catch (RejectedExecutionException rx) {
84
                //there could have been contention around the queue
85
                if ( !( (TaskQueue) executor.getQueue()).force(command) ) throw new RejectedExecutionException();
86
            }
87
        } else throw new IllegalStateException("StandardThreadPool not started.");
86
        } else throw new IllegalStateException("StandardThreadPool not started.");
88
    }
87
    }
89
88
89
    public void contextStopping() {
90
        if (executor != null) {
91
            executor.contextStopping();
92
        }
93
    }
94
90
    public int getThreadPriority() {
95
    public int getThreadPriority() {
91
        return threadPriority;
96
        return threadPriority;
92
    }
97
    }
Lines 152-157 Link Here
152
    public void setName(String name) {
157
    public void setName(String name) {
153
        this.name = name;
158
        this.name = name;
154
    }
159
    }
160
161
    public long getThreadRenewalDelay() {
162
        return threadRenewalDelay;
163
    }
164
165
    public void setThreadRenewalDelay(long threadRenewalDelay) {
166
        this.threadRenewalDelay = threadRenewalDelay;
167
        if(executor!=null) {
168
            executor.setThreadRenewalDelay(threadRenewalDelay);
169
        }
170
    }
155
    
171
    
156
    /**
172
    /**
157
     * Add a LifecycleEvent listener to this component.
173
     * Add a LifecycleEvent listener to this component.
Lines 206-270 Link Here
206
        return (executor != null) ? executor.getQueue().size() : -1;
222
        return (executor != null) ? executor.getQueue().size() : -1;
207
    }
223
    }
208
224
209
    // ---------------------------------------------- TaskQueue Inner Class
210
    class TaskQueue extends LinkedBlockingQueue<Runnable> {
211
        ThreadPoolExecutor parent = null;
212
213
        public TaskQueue() {
214
            super();
215
        }
216
217
        public TaskQueue(int initialCapacity) {
218
            super(initialCapacity);
219
        }
220
221
        public TaskQueue(Collection<? extends Runnable> c) {
222
            super(c);
223
        }
224
225
        public void setParent(ThreadPoolExecutor tp) {
226
            parent = tp;
227
        }
228
        
229
        public boolean force(Runnable o) {
230
            if ( parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
231
            return super.offer(o); //forces the item onto the queue, to be used if the task is rejected
232
        }
233
234
        public boolean offer(Runnable o) {
235
            //we can't do any checks
236
            if (parent==null) return super.offer(o);
237
            //we are maxed out on threads, simply queue the object
238
            if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
239
            //we have idle threads, just add it to the queue
240
            //this is an approximation, so it could use some tuning
241
            if (parent.getActiveCount()<(parent.getPoolSize())) return super.offer(o);
242
            //if we have less threads than maximum force creation of a new thread
243
            if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
244
            //if we reached here, we need to add it to the queue
245
            return super.offer(o);
246
        }
247
    }
248
249
    // ---------------------------------------------- ThreadFactory Inner Class
250
    class TaskThreadFactory implements ThreadFactory {
251
        final ThreadGroup group;
252
        final AtomicInteger threadNumber = new AtomicInteger(1);
253
        final String namePrefix;
254
255
        TaskThreadFactory(String namePrefix) {
256
            SecurityManager s = System.getSecurityManager();
257
            group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
258
            this.namePrefix = namePrefix;
259
        }
260
261
        public Thread newThread(Runnable r) {
262
            Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement());
263
            t.setDaemon(daemon);
264
            t.setPriority(getThreadPriority());
265
            return t;
266
        }
267
    }
268
269
270
}
225
}
(-)java/org/apache/catalina/core/ThreadLocalLeakPreventionListener.java (+222 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
package org.apache.catalina.core;
19
20
import java.util.concurrent.Executor;
21
22
import org.apache.catalina.Container;
23
import org.apache.catalina.ContainerEvent;
24
import org.apache.catalina.ContainerListener;
25
import org.apache.catalina.Context;
26
import org.apache.catalina.Engine;
27
import org.apache.catalina.Host;
28
import org.apache.catalina.Lifecycle;
29
import org.apache.catalina.LifecycleEvent;
30
import org.apache.catalina.LifecycleListener;
31
import org.apache.catalina.Server;
32
import org.apache.catalina.Service;
33
import org.apache.catalina.connector.Connector;
34
import org.apache.coyote.ProtocolHandler;
35
import org.apache.coyote.ajp.AjpAprProtocol;
36
import org.apache.coyote.ajp.AjpProtocol;
37
import org.apache.coyote.http11.Http11AprProtocol;
38
import org.apache.coyote.http11.Http11NioProtocol;
39
import org.apache.coyote.http11.Http11Protocol;
40
import org.apache.juli.logging.Log;
41
import org.apache.juli.logging.LogFactory;
42
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
43
44
/**
45
 * A {@link LifecycleListener} that triggers the renewal of threads in Executor
46
 * pools when a {@link Context} is being stopped to avoid thread-local related
47
 * memory leaks.<br/>
48
 * Note : active threads will be renewed one by one when they come back to the
49
 * pool after executing their task, see
50
 * {@link org.apache.tomcat.util.threads.ThreadPoolExecutor}.afterExecute().<br/>
51
 * 
52
 * This listener must be declared in server.xml to be active, and the connectors
53
 * must use an Executor instead of their internal thread pool.
54
 * 
55
 * @author slaurent
56
 * 
57
 */
58
public class ThreadLocalLeakPreventionListener implements LifecycleListener, ContainerListener {
59
    private static final Log log = LogFactory.getLog(ThreadLocalLeakPreventionListener.class);
60
61
    /**
62
     * Listens for {@link LifecycleEvent} for the start of the {@link Server} to
63
     * initialize itself and then for after_stop events of each {@link Context}.
64
     */
65
    @Override
66
    public void lifecycleEvent(LifecycleEvent event) {
67
        try {
68
            Lifecycle lifecycle = event.getLifecycle();
69
            if (Lifecycle.AFTER_START_EVENT.equals(event.getType()) && lifecycle instanceof Server) {
70
                // when the server starts, we register ourself as listener for
71
                // all context
72
                // as well as container event listener so that we know when new
73
                // Context are deployed
74
                Server server = (Server) lifecycle;
75
                registerListenersForServer(server);
76
            }
77
78
            if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType()) && lifecycle instanceof Context) {
79
                stopIdleThreads((Context) lifecycle);
80
            }
81
        } catch (Exception e) {
82
            log.error("Exception processing event " + event, e);
83
        }
84
    }
85
86
    @Override
87
    public void containerEvent(ContainerEvent event) {
88
        try {
89
            String type = event.getType();
90
            if (Container.ADD_CHILD_EVENT.equals(type)) {
91
                processContainerAddChild(event.getContainer(), (Container) event.getData());
92
            } else if (Container.REMOVE_CHILD_EVENT.equals(type)) {
93
                processContainerRemoveChild(event.getContainer(), (Container) event.getData());
94
            }
95
        } catch (Exception e) {
96
            log.error("Exception processing event " + event, e);
97
        }
98
99
    }
100
101
    private void registerListenersForServer(Server server) {
102
        for (Service service : server.findServices()) {
103
            Engine engine = (Engine) service.getContainer();
104
            engine.addContainerListener(this);
105
            registerListenersForEngine(engine);
106
        }
107
108
    }
109
110
    private void registerListenersForEngine(Engine engine) {
111
        for (Container hostContainer : engine.findChildren()) {
112
            Host host = (Host) hostContainer;
113
            host.addContainerListener(this);
114
            registerListenersForHost(host);
115
        }
116
    }
117
118
    private void registerListenersForHost(Host host) {
119
        for (Container contextContainer : host.findChildren()) {
120
            Context context = (Context) contextContainer;
121
            registerContextListener(context);
122
        }
123
    }
124
125
    private void registerContextListener(Context context) {
126
        if (context instanceof Lifecycle) {
127
            ((Lifecycle) context).addLifecycleListener(this);
128
        }
129
    }
130
131
    protected void processContainerAddChild(Container parent, Container child) {
132
        if (log.isDebugEnabled())
133
            log.debug("Process addChild[parent=" + parent + ",child=" + child + "]");
134
135
        try {
136
            if (child instanceof Context) {
137
                registerContextListener((Context) child);
138
            } else if (child instanceof Engine) {
139
                registerListenersForEngine((Engine) child);
140
            } else if (child instanceof Host) {
141
                registerListenersForHost((Host) child);
142
            }
143
        } catch (Throwable t) {
144
            log.error("processContainerAddChild: Throwable", t);
145
        }
146
147
    }
148
149
    protected void processContainerRemoveChild(Container parent, Container child) {
150
151
        if (log.isDebugEnabled())
152
            log.debug("Process removeChild[parent=" + parent + ",child=" + child + "]");
153
154
        try {
155
            if (child instanceof Context) {
156
                Context context = (Context) child;
157
                if (context instanceof Lifecycle) {
158
                    ((Lifecycle) context).removeLifecycleListener(this);
159
                }
160
            } else if (child instanceof Host) {
161
                Host host = (Host) child;
162
                host.removeContainerListener(this);
163
            } else if (child instanceof Engine) {
164
                Engine engine = (Engine) child;
165
                engine.removeContainerListener(this);
166
            }
167
        } catch (Throwable t) {
168
            log.error("processContainerRemoveChild: Throwable", t);
169
        }
170
171
    }
172
173
    /**
174
     * Updates each ThreadPoolExecutor with the current time, which is the time
175
     * when a context is being stopped.
176
     * 
177
     * @param context
178
     *            the context being stopped, used to discover all the Connectors
179
     *            of its parent Service.
180
     */
181
    private void stopIdleThreads(Context context) {
182
        if (context instanceof StandardContext && !((StandardContext) context).getRenewThreadsWhenStoppingContext()) {
183
            log.debug("Not renewing threads when the context is stopping, it is configured not to do it.");
184
            return;
185
        }
186
187
        Engine engine = (Engine) context.getParent().getParent();
188
        Service service = engine.getService();
189
        Connector[] connectors = service.findConnectors();
190
        if (connectors != null) {
191
            for (Connector connector : connectors) {
192
                ProtocolHandler handler = connector.getProtocolHandler();
193
                Executor executor = null;
194
195
                // unfortunately there's no getExecutor() method in
196
                // ProtocolHandler
197
                // we have to check each known implementation
198
199
                if (handler instanceof Http11Protocol) {
200
                    executor = ((Http11Protocol) handler).getExecutor();
201
                } else if (handler instanceof AjpProtocol) {
202
                    executor = ((AjpProtocol) handler).getExecutor();
203
                } else if (handler instanceof AjpAprProtocol) {
204
                    executor = ((AjpAprProtocol) handler).getExecutor();
205
                } else if (handler instanceof Http11AprProtocol) {
206
                    executor = ((Http11AprProtocol) handler).getExecutor();
207
                } else if (handler instanceof Http11NioProtocol) {
208
                    executor = ((Http11NioProtocol) handler).getExecutor();
209
                }
210
211
                if (executor instanceof StandardThreadExecutor) {
212
                    StandardThreadExecutor stdThreadExecutor = (StandardThreadExecutor) executor;
213
                    stdThreadExecutor.contextStopping();
214
                } else if (executor instanceof ThreadPoolExecutor) {
215
                    ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
216
                    threadPoolExecutor.contextStopping();
217
                }
218
219
            }
220
        }
221
    }
222
}
(-)java/org/apache/catalina/core/mbeans-descriptors.xml (+8 lines)
Lines 200-205 Link Here
200
               description="Associated realm."
200
               description="Associated realm."
201
               type="org.apache.catalina.Realm" />
201
               type="org.apache.catalina.Realm" />
202
      
202
      
203
    <attribute name="renewThreadsWhenStoppingContext"
204
               description="Should Tomcat renew the threads of the thread pool when the application is stopped to avoid memory leaks because of uncleaned ThreadLocal variables." 
205
               type="boolean"/>
206
203
    <attribute name="reloadable"
207
    <attribute name="reloadable"
204
               description="The reloadable flag for this web application"
208
               description="The reloadable flag for this web application"
205
               type="boolean"/>
209
               type="boolean"/>
Lines 708-713 Link Here
708
    <attribute name="queueSize"
712
    <attribute name="queueSize"
709
               description="Number of tasks waiting to be processed"
713
               description="Number of tasks waiting to be processed"
710
               type="int"/>
714
               type="int"/>
715
716
    <attribute name="threadRenewalDelay"
717
               description="After a context is stopped, threads in the pool are renewed. To avoid renewing all threads at the same time, this delay is observed between 2 threads being renewed. Value is in ms, default value is 1000ms. If negative, threads are not renewed."
718
               type="long"/>
711
  </mbean>
719
  </mbean>
712
720
713
  <mbean name="StandardWrapper"
721
  <mbean name="StandardWrapper"
(-)java/org/apache/catalina/loader/LocalStrings.properties (-5 / +3 lines)
Lines 44-54 Link Here
44
webappClassLoader.clearReferencesResourceBundlesFail=Failed to clear ResourceBundle references for web application [{0}]
44
webappClassLoader.clearReferencesResourceBundlesFail=Failed to clear ResourceBundle references for web application [{0}]
45
webappClassLoader.clearRmiInfo=Failed to find class sun.rmi.transport.Target to clear context class loader for web application [{0}]. This is expected on non-Sun JVMs.
45
webappClassLoader.clearRmiInfo=Failed to find class sun.rmi.transport.Target to clear context class loader for web application [{0}]. This is expected on non-Sun JVMs.
46
webappClassLoader.clearRmiFail=Failed to clear context class loader referenced from sun.rmi.transport.Target for web application [{0}]
46
webappClassLoader.clearRmiFail=Failed to clear context class loader referenced from sun.rmi.transport.Target for web application [{0}]
47
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.
47
webappClassLoader.checkThreadLocalsForLeaksDebug=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.
48
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.
48
webappClassLoader.checkThreadLocalsForLeaks=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, but threads are going to be renewed over time so that the leak should be mitigated.
49
webappClassLoader.clearThreadLocalDebugClear=To simplify the process of tracing memory leaks, the key has been forcibly removed.
49
webappClassLoader.checkThreadLocalsForLeaksFail=Failed to check for ThreadLocal references for web application [{0}]
50
webappClassLoader.clearThreadLocalClear=To prevent a memory leak, the ThreadLocal has been forcibly removed.
51
webappClassLoader.clearThreadLocalFail=Failed to clear ThreadLocal references for web application [{0}]
52
webappClassLoader.stopThreadFail=Failed to terminate thread named [{0}] for web application [{1}]
50
webappClassLoader.stopThreadFail=Failed to terminate thread named [{0}] for web application [{1}]
53
webappClassLoader.stopTimerThreadFail=Failed to terminate TimerThread named [{0}] for web application [{1}]
51
webappClassLoader.stopTimerThreadFail=Failed to terminate TimerThread named [{0}] for web application [{1}]
54
webappClassLoader.validationErrorJarPath=Unable to validate JAR entry with name {0}
52
webappClassLoader.validationErrorJarPath=Unable to validate JAR entry with name {0}
(-)java/org/apache/catalina/loader/WebappClassLoader.java (-97 / +47 lines)
Lines 464-478 Link Here
464
    private boolean clearReferencesStopTimerThreads = false;
464
    private boolean clearReferencesStopTimerThreads = false;
465
465
466
    /**
466
    /**
467
     * Should Tomcat attempt to clear any ThreadLocal objects that are instances
468
     * of classes loaded by this class loader. Failure to remove any such
469
     * objects will result in a memory leak on web application stop, undeploy or
470
     * reload. It is disabled by default since the clearing of the ThreadLocal
471
     * objects is not performed in a thread-safe manner.
472
     */
473
    private boolean clearReferencesThreadLocals = false;
474
    
475
    /**
476
     * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()}
467
     * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()}
477
     * when the class loader is stopped? If not specified, the default value
468
     * when the class loader is stopped? If not specified, the default value
478
     * of <code>true</code> is used. Changing the default setting is likely to
469
     * of <code>true</code> is used. Changing the default setting is likely to
Lines 740-764 Link Here
740
     }
731
     }
741
732
742
733
743
     /**
744
      * Return the clearReferencesThreadLocals flag for this Context.
745
      */
746
     public boolean getClearReferencesThreadLocals() {
747
         return (this.clearReferencesThreadLocals);
748
     }
749
750
751
     /**
752
      * Set the clearReferencesThreadLocals feature for this Context.
753
      *
754
      * @param clearReferencesThreadLocals The new flag value
755
      */
756
     public void setClearReferencesThreadLocals(
757
             boolean clearReferencesThreadLocals) {
758
         this.clearReferencesThreadLocals = clearReferencesThreadLocals;
759
     }
760
761
762
    // ------------------------------------------------------- Reloader Methods
734
    // ------------------------------------------------------- Reloader Methods
763
735
764
736
Lines 1884-1891 Link Here
1884
        // Stop any threads the web application started
1856
        // Stop any threads the web application started
1885
        clearReferencesThreads();
1857
        clearReferencesThreads();
1886
        
1858
        
1887
        // Clear any ThreadLocals loaded by this class loader
1859
        // Check for leaks triggered by ThreadLocals loaded by this class loader
1888
        clearReferencesThreadLocals();
1860
        checkThreadLocalsForLeaks();
1889
        
1861
        
1890
        // Clear RMI Targets loaded by this class loader
1862
        // Clear RMI Targets loaded by this class loader
1891
        clearReferencesRmiTargets();
1863
        clearReferencesRmiTargets();
Lines 2079-2085 Link Here
2079
                Object value = field.get(instance);
2051
                Object value = field.get(instance);
2080
                if (null != value) {
2052
                if (null != value) {
2081
                    Class<? extends Object> valueClass = value.getClass();
2053
                    Class<? extends Object> valueClass = value.getClass();
2082
                    if (!loadedByThisOrChild(valueClass)) {
2054
                    if (!objectIsLoadedByThisOrChildClassLoader(valueClass)) {
2083
                        if (log.isDebugEnabled()) {
2055
                        if (log.isDebugEnabled()) {
2084
                            log.debug("Not setting field " + field.getName() +
2056
                            log.debug("Not setting field " + field.getName() +
2085
                                    " to null in object of class " + 
2057
                                    " to null in object of class " + 
Lines 2279-2285 Link Here
2279
        }
2251
        }
2280
    }
2252
    }
2281
2253
2282
    private void clearReferencesThreadLocals() {
2254
    private void checkThreadLocalsForLeaks() {
2283
        Thread[] threads = getThreads();
2255
        Thread[] threads = getThreads();
2284
2256
2285
        try {
2257
        try {
Lines 2303-2375 Link Here
2303
                if (threads[i] != null) {
2275
                if (threads[i] != null) {
2304
                    // Clear the first map
2276
                    // Clear the first map
2305
                    threadLocalMap = threadLocalsField.get(threads[i]);
2277
                    threadLocalMap = threadLocalsField.get(threads[i]);
2306
                    clearThreadLocalMap(threadLocalMap, tableField);
2278
                    checkThreadLocalMapForLeaks(threadLocalMap, tableField);
2307
                    // Clear the second map
2279
                    // Clear the second map
2308
                    threadLocalMap =
2280
                    threadLocalMap =
2309
                        inheritableThreadLocalsField.get(threads[i]);
2281
                        inheritableThreadLocalsField.get(threads[i]);
2310
                    clearThreadLocalMap(threadLocalMap, tableField);
2282
                    checkThreadLocalMapForLeaks(threadLocalMap, tableField);
2311
                }
2283
                }
2312
            }
2284
            }
2313
        } catch (SecurityException e) {
2285
        } catch (SecurityException e) {
2314
            log.warn(sm.getString("webappClassLoader.clearThreadLocalFail",
2286
            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
2315
                    contextName), e);
2287
                    contextName), e);
2316
        } catch (NoSuchFieldException e) {
2288
        } catch (NoSuchFieldException e) {
2317
            log.warn(sm.getString("webappClassLoader.clearThreadLocalFail",
2289
            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
2318
                    contextName), e);
2290
                    contextName), e);
2319
        } catch (ClassNotFoundException e) {
2291
        } catch (ClassNotFoundException e) {
2320
            log.warn(sm.getString("webappClassLoader.clearThreadLocalFail",
2292
            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
2321
                    contextName), e);
2293
                    contextName), e);
2322
        } catch (IllegalArgumentException e) {
2294
        } catch (IllegalArgumentException e) {
2323
            log.warn(sm.getString("webappClassLoader.clearThreadLocalFail",
2295
            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
2324
                    contextName), e);
2296
                    contextName), e);
2325
        } catch (IllegalAccessException e) {
2297
        } catch (IllegalAccessException e) {
2326
            log.warn(sm.getString("webappClassLoader.clearThreadLocalFail",
2298
            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
2327
                    contextName), e);
2299
                    contextName), e);
2328
        } catch (NoSuchMethodException e) {
2300
        } catch (NoSuchMethodException e) {
2329
            log.warn(sm.getString("webappClassLoader.clearThreadLocalFail",
2301
            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
2330
                    contextName), e);
2302
                    contextName), e);
2331
        } catch (InvocationTargetException e) {
2303
        } catch (InvocationTargetException e) {
2332
            log.warn(sm.getString("webappClassLoader.clearThreadLocalFail",
2304
            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
2333
                    contextName), e);
2305
                    contextName), e);
2334
        }       
2306
        }       
2335
    }
2307
    }
2336
2308
2337
2309
2338
    /*
2310
    /*
2339
     * Clears the given thread local map object. Also pass in the field that
2311
     * Analyzes the given thread local map object. Also pass in the field that
2340
     * points to the internal table to save re-calculating it on every
2312
     * points to the internal table to save re-calculating it on every
2341
     * call to this method.
2313
     * call to this method.
2342
     */
2314
     */
2343
    private void clearThreadLocalMap(Object map, Field internalTableField)
2315
    private void checkThreadLocalMapForLeaks(Object map, Field internalTableField)
2344
            throws NoSuchMethodException, IllegalAccessException,
2316
            throws NoSuchMethodException, IllegalAccessException,
2345
            NoSuchFieldException, InvocationTargetException {
2317
            NoSuchFieldException, InvocationTargetException {
2346
        if (map != null) {
2318
        if (map != null) {
2347
            Method mapRemove =
2348
                map.getClass().getDeclaredMethod("remove",
2349
                        ThreadLocal.class);
2350
            mapRemove.setAccessible(true);
2351
            Object[] table = (Object[]) internalTableField.get(map);
2319
            Object[] table = (Object[]) internalTableField.get(map);
2352
            int staleEntriesCount = 0;
2353
            if (table != null) {
2320
            if (table != null) {
2354
                for (int j =0; j < table.length; j++) {
2321
                for (int j =0; j < table.length; j++) {
2355
                    if (table[j] != null) {
2322
                    if (table[j] != null) {
2356
                        boolean remove = false;
2323
                        boolean potentialLeak = false;
2357
                        // Check the key
2324
                        // Check the key
2358
                        Object key = ((Reference<?>) table[j]).get();
2325
                        Object key = ((Reference<?>) table[j]).get();
2359
                        if (this.equals(key) || (key != null &&
2326
                        if (this.equals(key) || objectIsLoadedByThisOrChildClassLoader(key)) {
2360
                                this == key.getClass().getClassLoader())) {
2327
                            potentialLeak = true;
2361
                            remove = true;
2362
                        }
2328
                        }
2363
                        // Check the value
2329
                        // Check the value
2364
                        Field valueField =
2330
                        Field valueField =
2365
                            table[j].getClass().getDeclaredField("value");
2331
                            table[j].getClass().getDeclaredField("value");
2366
                        valueField.setAccessible(true);
2332
                        valueField.setAccessible(true);
2367
                        Object value = valueField.get(table[j]);
2333
                        Object value = valueField.get(table[j]);
2368
                        if (this.equals(value) || (value != null &&
2334
                        if (this.equals(value) || objectIsLoadedByThisOrChildClassLoader(value)) {
2369
                                this == value.getClass().getClassLoader())) {
2335
                            potentialLeak = true;
2370
                            remove = true;
2371
                        }
2336
                        }
2372
                        if (remove) {
2337
                        if (potentialLeak) {
2373
                            Object[] args = new Object[5];
2338
                            Object[] args = new Object[5];
2374
                            args[0] = contextName;
2339
                            args[0] = contextName;
2375
                            if (key != null) {
2340
                            if (key != null) {
Lines 2379-2421 Link Here
2379
                            if (value != null) {
2344
                            if (value != null) {
2380
                                args[3] = value.getClass().getCanonicalName();
2345
                                args[3] = value.getClass().getCanonicalName();
2381
                                args[4] = value.toString();
2346
                                args[4] = value.toString();
2382
                            }
2347
                                log.error(sm.getString(
2383
                            if (value == null) {
2348
                                        "webappClassLoader.checkThreadLocalsForLeaks",
2349
                                        args));
2350
                            } else {
2384
                                if (log.isDebugEnabled()) {
2351
                                if (log.isDebugEnabled()) {
2385
                                    log.debug(sm.getString(
2352
                                    log.debug(sm.getString(
2386
                                            "webappClassLoader.clearThreadLocalDebug",
2353
                                            "webappClassLoader.checkThreadLocalsForLeaksDebug",
2387
                                            args));
2354
                                            args));
2388
                                    if (clearReferencesThreadLocals) {
2389
                                        log.debug(sm.getString(
2390
                                                "webappClassLoader.clearThreadLocalDebugClear"));
2391
                                    }
2392
                                }
2355
                                }
2393
                            } else {
2394
                                log.error(sm.getString(
2395
                                        "webappClassLoader.clearThreadLocal",
2396
                                        args));
2397
                                if (clearReferencesThreadLocals) {
2398
                                    log.info(sm.getString(
2399
                                            "webappClassLoader.clearThreadLocalClear"));
2400
                                }
2401
                            }
2356
                            }
2402
                            if (clearReferencesThreadLocals) {
2403
                                if (key == null) {
2404
                                  staleEntriesCount++;
2405
                                } else {
2406
                                  mapRemove.invoke(map, key);
2407
                                }
2408
                            }
2409
                        }
2357
                        }
2410
                    }
2358
                    }
2411
                }
2359
                }
2412
            }
2360
            }
2413
            if (staleEntriesCount > 0) {
2414
                Method mapRemoveStale =
2415
                    map.getClass().getDeclaredMethod("expungeStaleEntries");
2416
                mapRemoveStale.setAccessible(true);
2417
                mapRemoveStale.invoke(map);
2418
            }
2419
        }
2361
        }
2420
    }
2362
    }
2421
2363
Lines 2604-2626 Link Here
2604
        }
2546
        }
2605
    }
2547
    }
2606
2548
2607
2608
    /**
2549
    /**
2609
     * Determine whether a class was loaded by this class loader or one of
2550
     * @param o
2610
     * its child class loaders.
2551
     *            an instance may be null
2552
     * @return true if o os either a Class or an object of a Class that was
2553
     *         loaded by this ClassLoader.
2611
     */
2554
     */
2612
    protected boolean loadedByThisOrChild(Class clazz)
2555
    private boolean objectIsLoadedByThisOrChildClassLoader(Object o) {
2613
    {
2556
        if (o == null) {
2614
        boolean result = false;
2557
            return false;
2615
        for (ClassLoader classLoader = clazz.getClassLoader();
2558
        }
2616
                null != classLoader; classLoader = classLoader.getParent()) {
2559
2617
            if (classLoader.equals(this)) {
2560
        Class<?> clazz;
2618
                result = true;
2561
        if (o instanceof Class) {
2619
                break;
2562
            clazz = (Class<?>) o;
2563
        } else {
2564
            clazz = o.getClass();
2565
        }
2566
2567
        for (ClassLoader cl = clazz.getClassLoader(); cl != null; cl = cl.getParent()) {
2568
            if (cl == this) {
2569
                return true;
2620
            }
2570
            }
2621
        }
2571
        }
2622
        return result;
2572
        return false;
2623
    }    
2573
    }
2624
2574
2625
2575
2626
    /**
2576
    /**
(-)java/org/apache/catalina/loader/WebappLoader.java (-2 lines)
Lines 669-676 Link Here
669
                        ((StandardContext) container).getClearReferencesStopThreads());
669
                        ((StandardContext) container).getClearReferencesStopThreads());
670
                classLoader.setClearReferencesStopTimerThreads(
670
                classLoader.setClearReferencesStopTimerThreads(
671
                        ((StandardContext) container).getClearReferencesStopTimerThreads());
671
                        ((StandardContext) container).getClearReferencesStopTimerThreads());
672
                classLoader.setClearReferencesThreadLocals(
673
                        ((StandardContext) container).getClearReferencesThreadLocals());
674
            }
672
            }
675
673
676
            for (int i = 0; i < repositories.length; i++) {
674
            for (int i = 0; i < repositories.length; i++) {
(-)java/org/apache/tomcat/util/threads/TaskQueue.java (+121 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.tomcat.util.threads;
18
19
import java.util.Collection;
20
import java.util.concurrent.LinkedBlockingQueue;
21
import java.util.concurrent.RejectedExecutionException;
22
import java.util.concurrent.TimeUnit;
23
24
/**
25
 * As task queue specifically designed to run with a thread pool executor.
26
 * The task queue is optimised to properly utilize threads within 
27
 * a thread pool executor. If you use a normal queue, the executor will spawn threads
28
 * when there are idle threads and you wont be able to force items unto the queue itself 
29
 * @author fhanik
30
 *
31
 */
32
public class TaskQueue extends LinkedBlockingQueue<Runnable> {
33
34
    private static final long serialVersionUID = 1L;
35
36
    private ThreadPoolExecutor parent = null;
37
    
38
    // no need to be volatile, the one times when we change and read it occur in
39
    // a single thread (the one that did stop a context and fired listeners)
40
    private Integer forcedRemainingCapacity = null;
41
42
    public TaskQueue() {
43
        super();
44
    }
45
46
    public TaskQueue(int capacity) {
47
        super(capacity);
48
    }
49
50
    public TaskQueue(Collection<? extends Runnable> c) {
51
        super(c);
52
    }
53
54
    public void setParent(ThreadPoolExecutor tp) {
55
        parent = tp;
56
    }
57
    
58
    public boolean force(Runnable o) {
59
        if ( parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
60
        return super.offer(o); //forces the item onto the queue, to be used if the task is rejected
61
    }
62
63
    public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
64
        if ( parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
65
        return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task is rejected
66
    }
67
68
    @Override
69
    public boolean offer(Runnable o) {
70
      //we can't do any checks
71
        if (parent==null) return super.offer(o);
72
        //we are maxed out on threads, simply queue the object
73
        if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
74
        //we have idle threads, just add it to the queue
75
        if (parent.getSubmittedCount()<(parent.getPoolSize())) return super.offer(o);
76
        //if we have less threads than maximum force creation of a new thread
77
        if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
78
        //if we reached here, we need to add it to the queue
79
        return super.offer(o);
80
    }
81
82
83
    @Override
84
    public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException {
85
        Runnable runnable = super.poll(timeout, unit);
86
        if (runnable == null && parent != null) {
87
            // the poll timed out, it gives an opportunity to stop the current
88
            // thread if needed to avoid memory leaks.
89
            parent.stopCurrentThreadIfNeeded();
90
        }
91
        return runnable;
92
    }
93
    
94
95
    @Override
96
    public Runnable take() throws InterruptedException {
97
        if (parent != null && parent.currentThreadShouldBeStopped()) {
98
            return poll(parent.getKeepAliveTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
99
            //yes, this may return null (in case of timeout) which normally does not occur with take()
100
            //but the ThreadPoolExecutor implementation allows this
101
        }
102
        return super.take();
103
    }
104
105
    @Override
106
    public int remainingCapacity() {
107
        if(forcedRemainingCapacity != null) {
108
            // ThreadPoolExecutor.setCorePoolSize checks that
109
            // remainingCapacity==0 to allow to interrupt idle threads
110
            // I don't see why, but this hack allows to conform to this
111
            // "requirement"
112
            return forcedRemainingCapacity.intValue();
113
        }
114
        return super.remainingCapacity();
115
    }
116
117
    public void setForcedRemainingCapacity(Integer forcedRemainingCapacity) {
118
        this.forcedRemainingCapacity = forcedRemainingCapacity;
119
    }
120
121
}
(-)java/org/apache/tomcat/util/threads/TaskThread.java (+47 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.tomcat.util.threads;
18
19
/**
20
 * A Thread implementation that records the time at which it was created.
21
 * 
22
 * @author slaurent
23
 * 
24
 */
25
public class TaskThread extends Thread {
26
27
	private final long creationTime;
28
29
	public TaskThread(ThreadGroup group, Runnable target, String name) {
30
		super(group, target, name);
31
		this.creationTime = System.currentTimeMillis();
32
	}
33
34
	public TaskThread(ThreadGroup group, Runnable target, String name,
35
			long stackSize) {
36
		super(group, target, name, stackSize);
37
		this.creationTime = System.currentTimeMillis();
38
	}
39
40
	/**
41
	 * @return the time (in ms) at which this thread was created
42
	 */
43
	public final long getCreationTime() {
44
		return creationTime;
45
	}
46
47
}
(-)java/org/apache/tomcat/util/threads/TaskThreadFactory.java (+48 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.tomcat.util.threads;
18
19
import java.util.concurrent.ThreadFactory;
20
import java.util.concurrent.atomic.AtomicInteger;
21
/**
22
 * Simple task thread factory to use to create threads for an executor implementation.
23
 * @author fhanik
24
 *
25
 */
26
public class TaskThreadFactory implements ThreadFactory {
27
    private final ThreadGroup group;
28
    private final AtomicInteger threadNumber = new AtomicInteger(1);
29
    private final String namePrefix;
30
    private final boolean daemon;
31
    private final int threadPriority;
32
    public TaskThreadFactory(String namePrefix, boolean daemon, int priority) {
33
        SecurityManager s = System.getSecurityManager();
34
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
35
        this.namePrefix = namePrefix;
36
        this.daemon = daemon;
37
        this.threadPriority = priority;
38
    }
39
40
    @Override
41
    public Thread newThread(Runnable r) {
42
        TaskThread t = new TaskThread(group, r, namePrefix + threadNumber.getAndIncrement());
43
        t.setDaemon(daemon);
44
        t.setPriority(threadPriority);
45
        return t;
46
    }
47
48
}
(-)java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java (+228 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.tomcat.util.threads;
18
19
import java.lang.Thread.UncaughtExceptionHandler;
20
import java.util.concurrent.BlockingQueue;
21
import java.util.concurrent.RejectedExecutionException;
22
import java.util.concurrent.RejectedExecutionHandler;
23
import java.util.concurrent.ThreadFactory;
24
import java.util.concurrent.TimeUnit;
25
import java.util.concurrent.atomic.AtomicInteger;
26
import java.util.concurrent.atomic.AtomicLong;
27
28
import org.apache.juli.logging.Log;
29
import org.apache.juli.logging.LogFactory;
30
31
/**
32
 * Same as a java.util.concurrent.ThreadPoolExecutor but implements a much more efficient
33
 * {@link #getSubmittedCount()} method, to be used to properly handle the work queue.
34
 * If a RejectedExecutionHandler is not specified a default one will be configured
35
 * and that one will always throw a RejectedExecutionException
36
 * @author fhanik
37
 *
38
 */
39
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
40
    private static final Log log = LogFactory.getLog(ThreadPoolExecutor.class);
41
42
    /**
43
     * The number of tasks submitted but not yet finished. This includes tasks
44
     * in the queue and tasks that have been handed to a worker thread but the
45
     * latter did not start executing the task yet.
46
     * This number is always greater or equal to {@link #getActiveCount()}.
47
     */
48
    private final AtomicInteger submittedCount = new AtomicInteger(0);
49
    private final AtomicLong lastContextStoppedTime = new AtomicLong(0L);
50
51
    /**
52
     * Most recent time in ms when a thread decided to kill itself to avoid
53
     * potential memory leaks. Useful to throttle the rate of renewals of
54
     * threads.
55
     */
56
    private final AtomicLong lastTimeThreadKilledItself = new AtomicLong(0L);
57
58
    /**
59
     * Delay in ms between 2 threads being renewed. If negative, do not renew threads.
60
     */
61
    private long threadRenewalDelay = 1000L;
62
63
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
64
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
65
    }
66
67
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
68
            RejectedExecutionHandler handler) {
69
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
70
    }
71
72
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
73
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new RejectHandler());
74
    }
75
76
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
77
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new RejectHandler());
78
    }
79
    
80
    public long getThreadRenewalDelay() {
81
        return threadRenewalDelay;
82
    }
83
84
    public void setThreadRenewalDelay(long threadRenewalDelay) {
85
        this.threadRenewalDelay = threadRenewalDelay;
86
    }
87
88
    @Override
89
    protected void afterExecute(Runnable r, Throwable t) {
90
        submittedCount.decrementAndGet();
91
92
        if (t == null) {
93
            stopCurrentThreadIfNeeded();
94
        }
95
    }
96
97
    /**
98
     * If the current thread was started before the last time when a context was
99
     * stopped, an exception is thrown so that the current thread is stopped.
100
     */
101
    protected void stopCurrentThreadIfNeeded() {
102
        if (currentThreadShouldBeStopped()) {
103
            long lastTime = lastTimeThreadKilledItself.longValue();
104
            if (lastTime + threadRenewalDelay < System.currentTimeMillis()) {
105
                if (lastTimeThreadKilledItself.compareAndSet(lastTime, System.currentTimeMillis() + 1)) {
106
                    // OK, it's really time to dispose of this thread
107
                    
108
                    final String msg = "Stopping thread " + Thread.currentThread().getName()
109
                            + " to avoid potential memory leaks after a context was stopped.";
110
                    Thread.currentThread().setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
111
                        @Override
112
                        public void uncaughtException(Thread t, Throwable e) {
113
                            // yes, swallow the exception
114
                            log.debug(msg);
115
                        }
116
                    });
117
                    throw new RuntimeException(msg);
118
                }
119
            }
120
        }
121
    }
122
    
123
    protected boolean currentThreadShouldBeStopped() {
124
        if (threadRenewalDelay >= 0 && Thread.currentThread() instanceof TaskThread) {
125
            TaskThread currentTaskThread = (TaskThread) Thread.currentThread();
126
            if (currentTaskThread.getCreationTime() < this.lastContextStoppedTime.longValue()) {
127
                return true;
128
            }
129
        }
130
        return false;
131
    }
132
133
    public int getSubmittedCount() {
134
        return submittedCount.get();
135
    }
136
    
137
    /**
138
     * {@inheritDoc}
139
     */
140
    @Override
141
    public void execute(Runnable command) {
142
        execute(command,0,TimeUnit.MILLISECONDS);
143
    }
144
    
145
    /**
146
     * Executes the given command at some time in the future.  The command
147
     * may execute in a new thread, in a pooled thread, or in the calling
148
     * thread, at the discretion of the <tt>Executor</tt> implementation.
149
     * If no threads are available, it will be added to the work queue.
150
     * If the work queue is full, the system will wait for the specified 
151
     * time and it throw a RejectedExecutionException if the queue is still full after that.
152
     *
153
     * @param command the runnable task
154
     * @throws RejectedExecutionException if this task cannot be
155
     * accepted for execution - the queue is full
156
     * @throws NullPointerException if command or unit is null
157
     */
158
    public void execute(Runnable command, long timeout, TimeUnit unit) {
159
        submittedCount.incrementAndGet();
160
        try {
161
            super.execute(command);
162
        } catch (RejectedExecutionException rx) {
163
            if (super.getQueue() instanceof TaskQueue) {
164
                final TaskQueue queue = (TaskQueue)super.getQueue();
165
                try {
166
                    if (!queue.force(command, timeout, unit)) {
167
                        submittedCount.decrementAndGet();
168
                        throw new RejectedExecutionException("Queue capacity is full.");
169
                    }
170
                } catch (InterruptedException x) {
171
                    submittedCount.decrementAndGet();
172
                    Thread.interrupted();
173
                    throw new RejectedExecutionException(x);
174
                }
175
            } else {
176
                submittedCount.decrementAndGet();
177
                throw rx;
178
            }
179
            
180
        }
181
    }
182
183
    public void contextStopping() {
184
        this.lastContextStoppedTime.set(System.currentTimeMillis());
185
186
        // save the current pool parameters to restore them later
187
        int savedCorePoolSize = this.getCorePoolSize();
188
        TaskQueue taskQueue = getQueue() instanceof TaskQueue ? (TaskQueue) getQueue() : null;
189
        if (taskQueue != null) {
190
            // note by slaurent : quite oddly threadPoolExecutor.setCorePoolSize
191
            // checks that queue.remainingCapacity()==0. I did not understand
192
            // why, but to get the intended effect of waking up idle threads, I
193
            // temporarily fake this condition.
194
            taskQueue.setForcedRemainingCapacity(0);
195
        }
196
197
        // setCorePoolSize(0) wakes idle threads
198
        this.setCorePoolSize(0);
199
200
        // wait a little so that idle threads wake and poll the queue again,
201
        // this time always with a timeout (queue.poll() instead of queue.take())
202
        // even if we did not wait enough, TaskQueue.take() takes care of timing out
203
        // so that we are sure that all threads of the pool are renewed in a limited
204
        // time, something like (threadKeepAlive + longest request time)
205
        try {
206
            Thread.sleep(200L);
207
        } catch (InterruptedException e) {
208
            //yes, ignore
209
        }
210
        
211
        if (taskQueue != null) {
212
            // ok, restore the state of the queue and pool
213
            taskQueue.setForcedRemainingCapacity(null);
214
        }
215
        this.setCorePoolSize(savedCorePoolSize);
216
    }
217
    
218
    private static class RejectHandler implements RejectedExecutionHandler {
219
        @Override
220
        public void rejectedExecution(Runnable r,
221
                java.util.concurrent.ThreadPoolExecutor executor) {
222
            throw new RejectedExecutionException();
223
        }
224
        
225
    }
226
227
228
}
(-)webapps/docs/config/context.xml (-9 / +10 lines)
Lines 372-392 Link Here
372
        not specified, the default value of <code>false</code> will be used.</p>
372
        not specified, the default value of <code>false</code> will be used.</p>
373
      </attribute>
373
      </attribute>
374
374
375
      <attribute name="clearReferencesThreadLocals" required="false">
376
        <p>If <code>true</code>, Tomcat attempts to clear any ThreadLocal
377
        objects that are instances of classes loaded by this class loader.
378
        Failure to remove any such objects will result in a memory leak on web
379
        application stop, undeploy or reload.  If not specified, the default
380
        value of <code>false</code> will be used since the clearing of the
381
        ThreadLocal objects is not performed in a thread-safe manner.</p>
382
      </attribute>
383
384
      <attribute name="processTlds" required="false">
375
      <attribute name="processTlds" required="false">
385
        <p>Whether the context should process TLDs on startup.  The default
376
        <p>Whether the context should process TLDs on startup.  The default
386
        is true.  The false setting is intended for special cases
377
        is true.  The false setting is intended for special cases
387
        that know in advance TLDs are not part of the webapp.</p>
378
        that know in advance TLDs are not part of the webapp.</p>
388
      </attribute>
379
      </attribute>
389
380
381
      <attribute name="renewThreadsWhenStoppingContext" required="false">
382
        <p>If <code>true</code>, when this context is stopped, Tomcat renews all
383
        the threads from the thread pool that was used to serve this context.
384
        This also requires that the 
385
        <code>ThreadLocalLeakPreventionListener</code> be configured in 
386
        <code>server.xml</code> and that the <code>threadRenewalDelay</code>
387
        property of the <code>Executor</code> be &gt;=0. If not specified, the 
388
        default value of <code>true</code> will be used.</p>
389
      </attribute>
390
390
      <attribute name="swallowOutput" required="false">
391
      <attribute name="swallowOutput" required="false">
391
        <p>If the value of this flag is <code>true</code>, the bytes output to
392
        <p>If the value of this flag is <code>true</code>, the bytes output to
392
        System.out and System.err by the web application will be redirected to
393
        System.out and System.err by the web application will be redirected to
(-)webapps/docs/config/executor.xml (+5 lines)
Lines 102-107 Link Here
102
      <p>(int) The number of milliseconds before an idle thread shutsdown, unless the number of active threads are less
102
      <p>(int) The number of milliseconds before an idle thread shutsdown, unless the number of active threads are less
103
         or equal to minSpareThreads. Default value is <code>60000</code>(1 minute)</p>
103
         or equal to minSpareThreads. Default value is <code>60000</code>(1 minute)</p>
104
    </attribute>
104
    </attribute>
105
    <attribute name="threadRenewalDelay" required="false">
106
      <p>After a context is stopped, threads in the pool are renewed. To avoid renewing all threads at the same time, 
107
        this delay is observed between 2 threads being renewed. Value is in ms, default value is 1000ms.
108
        If negative, threads are not renewed.</p>
109
    </attribute>
105
  </attributes>
110
  </attributes>
106
111
107
112

Return to bug 49159