### Eclipse Workspace Patch 1.0 #P tomcat-8.0.x Index: test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java =================================================================== --- test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java (revision 0) +++ test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java (revision 0) @@ -0,0 +1,142 @@ +/* + * 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.catalina.loader; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.util.buf.ByteChunk; +import org.junit.Test; + +public class TestWebappClassLoaderThreadLocalMemoryLeak extends TomcatBaseTest { + + @Test + public void testThreadLocalLeak() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + + // Must have a real docBase - just use temp + Context ctx = + tomcat.addContext("", System.getProperty("java.io.tmpdir")); + + Tomcat.addServlet(ctx, "leakServlet", new LeakingServlet()); + ctx.addServletMapping("/leak1", "leakServlet"); + + Tomcat.addServlet(ctx, "leakServlet2", new LeakingServlet2()); + ctx.addServletMapping("/leak2", "leakServlet2"); + + + tomcat.start(); + + // This will trigger the timer & thread creation + ByteChunk chunk = getUrl("http://localhost:" + getPort() + "/leak1"); + System.out.print("First Threadlocal test response "+ chunk.toString()); + + chunk = getUrl("http://localhost:" + getPort() + "/leak2"); + System.out.print("Second Threadlocal test response "+ chunk.toString()); + + // Stop the context + ctx.stop(); + + // If the thread still exists, we have a thread/memory leak + try { + Thread.sleep(1000); + } catch(InterruptedException ie) { + // ignore + } + } + + class LeakingServlet extends HttpServlet { + private ThreadLocal myThreadLocal = new ThreadLocal(); + + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException, + IOException { + + MyCounter counter = myThreadLocal.get(); + if (counter == null) { + counter = new MyCounter(); + myThreadLocal.set(counter); + } + + response.getWriter().println( + "The current thread served this servlet " + + counter.getCount() + " times"); + counter.increment(); + } + + @Override + public void destroy() { + super.destroy(); + // normally not needed, just to make my point + myThreadLocal = null; + } + } + + class LeakingServlet2 extends HttpServlet { + + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + + List counterList = (List) ThreadScopedHolder.getFromHolder(); + MyCounter counter; + if (counterList == null) { + counter = new MyCounter(); + ThreadScopedHolder.saveInHolder(Arrays.asList(counter)); + } else { + counter = counterList.get(0); + } + + response.getWriter().println( + "The current thread served this servlet " + counter.getCount()+ " times"); + counter.increment(); + } + } + + static class ThreadScopedHolder { + private final static ThreadLocal threadLocal = new ThreadLocal(); + + public static void saveInHolder(Object o) { + threadLocal.set(o); + } + + public static Object getFromHolder() { + return threadLocal.get(); + } +} + + class MyCounter { + private int count = 0; + + public void increment() { + count++; + } + + public int getCount() { + return count; + } + } +} Index: test/org/apache/catalina/loader/TestWebappClassLoaderExecutorMemoryLeak.java =================================================================== --- test/org/apache/catalina/loader/TestWebappClassLoaderExecutorMemoryLeak.java (revision 0) +++ test/org/apache/catalina/loader/TestWebappClassLoaderExecutorMemoryLeak.java (revision 0) @@ -0,0 +1,120 @@ +/* + * 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.catalina.loader; + +import java.io.IOException; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.Assert; + +import org.apache.catalina.Context; +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.junit.Test; + +public class TestWebappClassLoaderExecutorMemoryLeak extends TomcatBaseTest { + + @Test + public void testTimerThreadLeak() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // Must have a real docBase - just use temp + Context ctx = + tomcat.addContext("", System.getProperty("java.io.tmpdir")); + + if (ctx instanceof StandardContext) { + ((StandardContext) ctx).setClearReferencesStopThreads(true); + } + + Tomcat.addServlet(ctx, "taskServlet", new ExecutorServlet()); + ctx.addServletMapping("/", "taskServlet"); + + tomcat.start(); + + // This will trigger the timer & thread creation + getUrl("http://localhost:" + getPort() + "/"); + + // Stop the context + ctx.stop(); + + // If the thread still exists, we have a thread/memory leak + try { + Thread.sleep(1000); + } catch(InterruptedException ie) { + // ignore + } + + Assert.assertTrue(ExecutorServlet.tpe.isShutdown()); + Assert.assertTrue(ExecutorServlet.tpe.isTerminated()); + } + + static class ExecutorServlet extends HttpServlet { + + + int nTasks = 5; + long n = 1000L; + int tpSize = 10; + + public static ThreadPoolExecutor tpe; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + resp.getWriter().println("The current thread served"+ this + "servlet "); + tpe = new ThreadPoolExecutor(tpSize, tpSize, 50000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); + + Task[] tasks = new Task[nTasks]; + for (int i = 0; i < nTasks; i++) { + tasks[i] = new Task("Task " + i); + tpe.execute(tasks[i]); + } + resp.getWriter().write("Started "+ nTasks +"never ending tasks using the ThreadPoolExecutor \n"); + resp.getWriter().flush(); + } + + + class Task implements Runnable { + + String _id; + public Task(String id) { + this._id = id; + } + + public void run() { + try { + while (!Thread.currentThread().isInterrupted()) { + Thread.sleep(20000); + System.out.println(Thread.currentThread().getClass()+" ["+ Thread.currentThread().getName() + "] executing " + this._id); + } + } catch (InterruptedException e) { + System.out.println(Thread.currentThread().getClass()+" ["+ Thread.currentThread().getName() + "] EXITING"); + } + } + } + } + + +}