--- java/org/apache/catalina/connector/mbeans-descriptors.xml (revision 1000553) +++ java/org/apache/catalina/connector/mbeans-descriptors.xml (working copy) @@ -97,6 +97,10 @@ description="The number of request processing threads that will be created" type="int"/> + + { +public class TaskQueue extends ArrayBlockingQueue { private ThreadPoolExecutor parent = null; - public TaskQueue() { - super(); - } public TaskQueue(int capacity) { - super(capacity); + //use a fair queue to allow more deterministic renewal of threads after a + //context is stopped (memory leak protection) + super(capacity, true); } - public TaskQueue(Collection c) { - super(c); - } - public void setParent(ThreadPoolExecutor tp) { parent = tp; } --- java/org/apache/tomcat/util/threads/TaskThread.java (revision 0) +++ java/org/apache/tomcat/util/threads/TaskThread.java (revision 0) @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.util.threads; + +/** + * A Thread implementation that records the time at which it was created. + * + * @author slaurent + * + */ +public class TaskThread extends Thread { + + private final long creationTime; + + public TaskThread(ThreadGroup group, Runnable target, String name) { + super(group, target, name); + this.creationTime = System.currentTimeMillis(); + } + + public TaskThread(ThreadGroup group, Runnable target, String name, + long stackSize) { + super(group, target, name, stackSize); + this.creationTime = System.currentTimeMillis(); + } + + /** + * @return the time (in ms) at which this thread was created + */ + public final long getCreationTime() { + return creationTime; + } + +} --- java/org/apache/tomcat/util/threads/TaskThreadFactory.java (revision 1000553) +++ java/org/apache/tomcat/util/threads/TaskThreadFactory.java (working copy) @@ -38,7 +38,7 @@ } public Thread newThread(Runnable r) { - Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement()); + TaskThread t = new TaskThread(group, r, namePrefix + threadNumber.getAndIncrement()); t.setDaemon(daemon); t.setPriority(threadPriority); return t; --- java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java (revision 1000553) +++ java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java (working copy) @@ -16,12 +16,18 @@ */ package org.apache.tomcat.util.threads; +import java.lang.Thread.UncaughtExceptionHandler; import java.util.concurrent.BlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.catalina.core.StandardContext; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; /** * Same as a java.util.concurrent.ThreadPoolExecutor but implements a much more efficient * getActiveCount method, to be used to properly handle the work queue @@ -31,9 +37,19 @@ * */ public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor { - + private static final Log log = LogFactory.getLog(ThreadPoolExecutor.class); + private final AtomicInteger activeCount = new AtomicInteger(0); + /** + * Most recent time in ms when a thread decided to kill itself to avoid potential memory leaks. + * Useful to throttle the rate of renewals of threads. + */ + private final AtomicLong lastTimeThreadKilledItself = new AtomicLong(0L); + + //TODO make it configurable + private final long threadRenewalRateMs = 1000L; + public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); } @@ -54,6 +70,31 @@ @Override protected void afterExecute(Runnable r, Throwable t) { activeCount.decrementAndGet(); + + if (t == null) { + if (Thread.currentThread() instanceof TaskThread) { + TaskThread currentTaskThread = (TaskThread) Thread.currentThread(); + if (currentTaskThread.getCreationTime() < StandardContext.lastContextStoppedTime.longValue()) { + long lastTime = lastTimeThreadKilledItself.longValue(); + if (lastTime + threadRenewalRateMs < System.currentTimeMillis()) { + if (lastTimeThreadKilledItself.compareAndSet(lastTime, System.currentTimeMillis())) { + //OK, it's really time to dispose of this thread + + final String msg = "Stopping thread " + currentTaskThread.getName() + + " to avoid potential memory leaks after a context was stopped."; + currentTaskThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + // yes, swallow the exception + log.info(msg); + } + }); + throw new RuntimeException(msg); + } + } + } + } + } } @Override --- webapps/docs/config/executor.xml (revision 1000553) +++ webapps/docs/config/executor.xml (working copy) @@ -98,6 +98,9 @@

(int) The minimum number of threads always kept alive, default is 25

+ +

(int) Maximum number of tasks for the pending task queue, default is Integer.MAX_VALUE

+

(int) The number of milliseconds before an idle thread shutsdown, unless the number of active threads are less or equal to minSpareThreads. Default value is 60000(1 minute)