You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2012/03/19 20:28:01 UTC
svn commit: r1302610 - in /tomcat/trunk:
java/org/apache/catalina/loader/WebappClassLoader.java
test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java
Author: markt
Date: Mon Mar 19 19:28:01 2012
New Revision: 1302610
URL: http://svn.apache.org/viewvc?rev=1302610&view=rev
Log:
Refactor the ThreadLocal leak tests
Modified:
tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java
tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java
Modified: tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java?rev=1302610&r1=1302609&r2=1302610&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java (original)
+++ tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java Mon Mar 19 19:28:01 2012
@@ -41,6 +41,7 @@ import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
@@ -1089,18 +1090,26 @@ public class WebappClassLoader
// ---------------------------------------------------- ClassLoader Methods
- /**
- * Add the specified URL to the classloader.
- */
+ /**
+ * Add the specified URL to the classloader.
+ */
@Override
- protected void addURL(URL url) {
- super.addURL(url);
- hasExternalRepositories = true;
- repositoryURLs = null;
- }
+ protected void addURL(URL url) {
+ super.addURL(url);
+ hasExternalRepositories = true;
+ repositoryURLs = null;
+ }
/**
+ * Expose this method for use by the unit tests.
+ */
+ protected final Class<?> doDefineClass(String name, byte[] b, int off, int len,
+ ProtectionDomain protectionDomain) {
+ return super.defineClass(name, b, off, len, protectionDomain);
+ }
+
+ /**
* Find the specified class in our local repositories, if possible. If
* not found, throw <code>ClassNotFoundException</code>.
*
@@ -2568,6 +2577,16 @@ public class WebappClassLoader
}
cl = cl.getParent();
}
+
+ if (o instanceof Collection<?>) {
+ Iterator<?> iter = ((Collection<?>) o).iterator();
+ while (iter.hasNext()) {
+ Object entry = iter.next();
+ if (loadedByThisOrChild(entry)) {
+ return true;
+ }
+ }
+ }
return false;
}
Modified: tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java?rev=1302610&r1=1302609&r2=1302610&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java (original)
+++ tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java Mon Mar 19 19:28:01 2012
@@ -17,17 +17,21 @@
package org.apache.catalina.loader;
import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Filter;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Assert;
import org.junit.Test;
import org.apache.catalina.Context;
+import org.apache.catalina.core.JreMemoryLeakPreventionListener;
+import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.tomcat.util.buf.ByteChunk;
@@ -35,119 +39,188 @@ import org.apache.tomcat.util.buf.ByteCh
public class TestWebappClassLoaderThreadLocalMemoryLeak extends TomcatBaseTest {
@Test
- public void testThreadLocalLeak() throws Exception {
+ public void testThreadLocalLeak1() throws Exception {
Tomcat tomcat = getTomcatInstance();
+ // Need to make sure we see a leak for the right reasons
+ tomcat.getServer().addLifecycleListener(
+ new JreMemoryLeakPreventionListener());
// 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, "leakServlet1",
+ "org.apache.tomcat.unittest.TesterLeakingServlet1");
+ ctx.addServletMapping("/leak1", "leakServlet1");
- 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());
+ // Configure logging filter to check leak message appears
+ LogValidationFilter f = new LogValidationFilter(
+ "The web application [] created a ThreadLocal with key of");
+ LogManager.getLogManager().getLogger(
+ "org.apache.catalina.loader.WebappClassLoader").setFilter(f);
+
+ // Need to force loading of all web application classes via the web
+ // application class loader
+ loadClass("TesterCounter",
+ (WebappClassLoader) ctx.getLoader().getClassLoader());
+ loadClass("TesterLeakingServlet1",
+ (WebappClassLoader) ctx.getLoader().getClassLoader());
+
+ // This will trigger the ThreadLocal creation
+ int rc = getUrl("http://localhost:" + getPort() + "/leak1",
+ new ByteChunk(), null);
+
+ // Make sure request is OK
+ Assert.assertEquals(HttpServletResponse.SC_OK, rc);
- // Stop the context
+ // Destroy the context
ctx.stop();
+ tomcat.getHost().removeChild(ctx);
+ ctx = null;
- // If the thread still exists, we have a thread/memory leak
- try {
- Thread.sleep(1000);
- } catch (InterruptedException ie) {
- // ignore
- }
+ // Make sure we have a memory leak
+ String[] leaks = ((StandardHost) tomcat.getHost())
+ .findReloadedContextMemoryLeaks();
+ Assert.assertNotNull(leaks);
+ Assert.assertTrue(leaks.length > 0);
+
+ // Make sure the message was logged
+ Assert.assertEquals(1, f.getMessageCount());
}
- class LeakingServlet extends HttpServlet {
- private static final long serialVersionUID = 1L;
+ @Test
+ public void testThreadLocalLeak2() throws Exception {
- private ThreadLocal<MyCounter> myThreadLocal = new ThreadLocal<MyCounter>();
+ Tomcat tomcat = getTomcatInstance();
+ // Need to make sure we see a leak for the right reasons
+ tomcat.getServer().addLifecycleListener(
+ new JreMemoryLeakPreventionListener());
- @Override
- protected void doGet(HttpServletRequest request,
- HttpServletResponse response) throws ServletException,
- IOException {
-
- MyCounter counter = myThreadLocal.get();
- if (counter == null) {
- counter = new MyCounter();
- myThreadLocal.set(counter);
- }
+ // Must have a real docBase - just use temp
+ Context ctx = tomcat.addContext("",
+ System.getProperty("java.io.tmpdir"));
- response.getWriter().println(
- "The current thread served this servlet "
- + counter.getCount() + " times");
- counter.increment();
- }
+ Tomcat.addServlet(ctx, "leakServlet2",
+ "org.apache.tomcat.unittest.TesterLeakingServlet2");
+ ctx.addServletMapping("/leak2", "leakServlet2");
- @Override
- public void destroy() {
- super.destroy();
- // normally not needed, just to make my point
- myThreadLocal = null;
- }
- }
+ tomcat.start();
- class LeakingServlet2 extends HttpServlet {
+ // Configure logging filter to check leak message appears
+ LogValidationFilter f = new LogValidationFilter(
+ "The web application [] created a ThreadLocal with key of");
+ LogManager.getLogManager().getLogger(
+ "org.apache.catalina.loader.WebappClassLoader").setFilter(f);
+
+ // Need to force loading of all web application classes via the web
+ // application class loader
+ loadClass("TesterCounter",
+ (WebappClassLoader) ctx.getLoader().getClassLoader());
+ loadClass("TesterThreadScopedHolder",
+ (WebappClassLoader) ctx.getLoader().getClassLoader());
+ loadClass("TesterLeakingServlet2",
+ (WebappClassLoader) ctx.getLoader().getClassLoader());
+
+ // This will trigger the ThreadLocal creation
+ int rc = getUrl("http://localhost:" + getPort() + "/leak2",
+ new ByteChunk(), null);
- private static final long serialVersionUID = 1L;
+ // Make sure request is OK
+ Assert.assertEquals(HttpServletResponse.SC_OK, rc);
- @Override
- protected void doGet(HttpServletRequest request,
- HttpServletResponse response) throws ServletException,
- IOException {
-
- List<MyCounter> counterList = ThreadScopedHolder.getFromHolder();
- MyCounter counter;
- if (counterList == null) {
- counter = new MyCounter();
- ThreadScopedHolder.saveInHolder(Arrays.asList(counter));
- } else {
- counter = counterList.get(0);
- }
+ // Destroy the context
+ ctx.stop();
+ tomcat.getHost().removeChild(ctx);
+ ctx = null;
- response.getWriter().println(
- "The current thread served this servlet "
- + counter.getCount() + " times");
- counter.increment();
- }
+ // Make sure we have a memory leak
+ String[] leaks = ((StandardHost) tomcat.getHost())
+ .findReloadedContextMemoryLeaks();
+ Assert.assertNotNull(leaks);
+ Assert.assertTrue(leaks.length > 0);
+
+ // Make sure the message was logged
+ Assert.assertEquals(1, f.getMessageCount());
}
- static class ThreadScopedHolder {
- private static final ThreadLocal<List<MyCounter>> threadLocal =
- new ThreadLocal<List<MyCounter>>();
- public static void saveInHolder(List<MyCounter> o) {
- threadLocal.set(o);
+ /**
+ * Utility method to ensure that classes are loaded by the
+ * WebappClassLoader. We can't just create classes since they will be loaded
+ * by the current class loader rather than the WebappClassLoader. This would
+ * mean that no leak occurred making the test for a leak rather pointless
+ * So, we load the bytes via the current class loader but define the class
+ * with the WebappClassLoader.
+ *
+ * This method assumes that all classes are in the current package.
+ */
+ private void loadClass(String name, WebappClassLoader cl) throws Exception {
+
+ InputStream is = cl.getResourceAsStream(
+ "org/apache/tomcat/unittest/" + name + ".class");
+ // We know roughly how big the class will be (~ 1K) so allow 2k as a
+ // starting point
+ byte[] classBytes = new byte[2048];
+ int offset = 0;
+ try {
+ int read = is.read(classBytes, offset, classBytes.length-offset);
+ while (read > -1) {
+ offset += read;
+ if (offset == classBytes.length) {
+ // Buffer full - double size
+ byte[] tmp = new byte[classBytes.length * 2];
+ System.arraycopy(classBytes, 0, tmp, 0, classBytes.length);
+ classBytes = tmp;
+ }
+ read = is.read(classBytes, offset, classBytes.length-offset);
+ }
+ Class<?> lpClass = cl.doDefineClass(
+ "org.apache.tomcat.unittest." + name, classBytes, 0,
+ offset, cl.getClass().getProtectionDomain());
+ // Make sure we can create an instance
+ Object obj = lpClass.newInstance();
+ obj.toString();
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ // Ignore
+ }
+ }
}
+ }
- public static List<MyCounter> getFromHolder() {
- return threadLocal.get();
+
+ private class LogValidationFilter implements Filter {
+
+ private String targetMessage;
+ private AtomicInteger messageCount = new AtomicInteger(0);
+
+
+ public LogValidationFilter(String targetMessage) {
+ this.targetMessage = targetMessage;
}
- }
- class MyCounter {
- private int count = 0;
- public void increment() {
- count++;
+ public int getMessageCount() {
+ return messageCount.get();
}
- public int getCount() {
- return count;
+
+ @Override
+ public boolean isLoggable(LogRecord record) {
+ String msg = record.getMessage();
+ if (msg != null && msg.contains(targetMessage)) {
+ messageCount.incrementAndGet();
+ }
+
+ return true;
}
}
}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org