From 7c9194d78895450a1bb6581c6a0c911ecd278ae7 Mon Sep 17 00:00:00 2001 From: markt Date: Sun, 29 Nov 2009 19:27:38 +0000 Subject: [PATCH] Add code that logs threads started but not stopped by the webapp. I have some highly experimental code to shut those threads down but it a) needs more work and b) needs to be made configurable before I commit it. git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@885260 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/catalina/loader/LocalStrings.properties | 3 +- .../apache/catalina/loader/WebappClassLoader.java | 108 ++++++++++++++++----- 2 files changed, 88 insertions(+), 23 deletions(-) diff --git a/java/org/apache/catalina/loader/LocalStrings.properties b/java/org/apache/catalina/loader/LocalStrings.properties index 297af3118..43b843da5 100644 --- a/java/org/apache/catalina/loader/LocalStrings.properties +++ b/java/org/apache/catalina/loader/LocalStrings.properties @@ -33,7 +33,8 @@ webappClassLoader.jdbcRemoveFailed=JDBC driver de-registration failed webappClassLoader.jdbcRemoveStreamError=Exception closing input stream during JDBC driver de-registration webappClassLoader.stopped=Illegal access: this web application instance has been stopped already. Could not load {0}. The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact. webappClassLoader.readError=Resource read error: Could not load {0}. -webappClassLoader.uncleareredReferenceJbdc=A web application registered the JBDC driver [{0}] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered. +webappClassLoader.clearJbdc=A web application registered the JBDC driver [{0}] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered. +webappClassLoader.warnThread=A web application appears to have started a thread named [{0}] but has failed to stop it. This is very likely to create a memory leak. webappClassLoader.wrongVersion=(unable to load class {0}) webappLoader.addRepository=Adding repository {0} webappLoader.deploy=Deploying class repositories to work directory {0} diff --git a/java/org/apache/catalina/loader/WebappClassLoader.java b/java/org/apache/catalina/loader/WebappClassLoader.java index 07d1a220f..ef891427d 100644 --- a/java/org/apache/catalina/loader/WebappClassLoader.java +++ b/java/org/apache/catalina/loader/WebappClassLoader.java @@ -112,9 +112,21 @@ public class WebappClassLoader private static final org.apache.juli.logging.Log log= org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class ); + /** + * List of ThreadGroup names to ignore when scanning for web application + * started threads that need to be shut down. + */ + private static final List JVM_THREAD_GROUP_NAMES = + new ArrayList(); + public static final boolean ENABLE_CLEAR_REFERENCES = Boolean.valueOf(System.getProperty("org.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES", "true")).booleanValue(); + static { + JVM_THREAD_GROUP_NAMES.add("system"); + JVM_THREAD_GROUP_NAMES.add("RMI Runtime"); + } + protected class PrivilegedFindResourceByName implements PrivilegedAction { @@ -1555,7 +1567,7 @@ public class WebappClassLoader clearReferences(); started = false; - + int length = files.length; for (int i = 0; i < length; i++) { files[i] = null; @@ -1636,6 +1648,9 @@ public class WebappClassLoader // De-register any remaining JDBC drivers clearReferencesJdbc(); + // Stop any threads the web application started + clearReferencesThreads(); + // Null out any static or final fields from loaded classes, // as a workaround for apparent garbage collection bugs if (ENABLE_CLEAR_REFERENCES) { @@ -1654,25 +1669,25 @@ public class WebappClassLoader } + /** + * Deregister any JDBC drivers registered by the webapp that the webapp + * forgot. This is made unnecessary complex because a) DriverManager + * checks the class loader of the calling class (it would be much easier + * if it checked the context class loader) b) using reflection would + * create a dependency on the DriverManager implementation which can, + * and has, changed. + * + * We can't just create an instance of JdbcLeakPrevention as it will be + * loaded by the common class loader (since it's .class file is in the + * $CATALINA_HOME/lib directory). This would fail DriverManager's check + * on the class loader of the calling class. So, we load the bytes via + * our parent class loader but define the class with this class loader + * so the JdbcLeakPrevention looks like a webapp class to the + * DriverManager. + * + * If only apps cleaned up after themselves... + */ private final void clearReferencesJdbc() { - /* - * Deregister any JDBC drivers registered by the webapp that the webapp - * forgot. This is made unnecessary complex because a) DriverManager - * checks the class loader of the calling class (it would be much easier - * if it checked the context class loader) b) using reflection would - * create a dependency on the DriverManager implementation which can, - * and has, changed. - * - * We can't just create an instance of JdbcLeakPrevention as it will be - * loaded by the common class loader (since it's .class file is in the - * $CATALINA_HOME/lib directory). This would fail DriverManager's check - * on the class loader of the calling class. So, we load the bytes via - * our parent class loader but define the class with this class loader - * so the JdbcLeakPrevention looks like a webapp class to the - * DriverManager. - * - * If only apps cleaned up after themselves... - */ InputStream is = getResourceAsStream( "org/apache/catalina/loader/JdbcLeakPrevention.class"); // We know roughly how big the class will be (~ 1K) so allow 2k as a @@ -1699,8 +1714,7 @@ public class WebappClassLoader List driverNames = (List) obj.getClass().getMethod( "clearJdbcDriverRegistrations").invoke(obj); for (String name : driverNames) { - log.error(sm.getString( - "webappClassLoader.uncleareredReferenceJbdc", name)); + log.error(sm.getString("webappClassLoader.clearJbdc", name)); } } catch (Exception e) { // So many things to go wrong above... @@ -1793,7 +1807,7 @@ public class WebappClassLoader } - protected void nullInstance(Object instance) { + private void nullInstance(Object instance) { if (instance == null) { return; } @@ -1842,6 +1856,56 @@ public class WebappClassLoader } + private void clearReferencesThreads() { + // Get the current thread group + ThreadGroup tg = Thread.currentThread( ).getThreadGroup( ); + // Find the root thread group + while (tg.getParent() != null) { + tg = tg.getParent(); + } + + int threadCountGuess = tg.activeCount() + 50; + Thread[] threads = new Thread[threadCountGuess]; + int threadCountActual = tg.enumerate(threads); + // Make sure we don't miss any threads + while (threadCountActual == threadCountGuess) { + threadCountGuess *=2; + threads = new Thread[threadCountGuess]; + // Note tg.enumerate(Thread[]) silently ignores any threads that + // can't fit into the array + threadCountActual = tg.enumerate(threads); + } + + // Iterate over the set of threads + for (Thread thread : threads) { + if (thread != null) { + if (thread.getContextClassLoader() == this) { + // Don't warn about this thread + if (thread == Thread.currentThread()) { + continue; + } + + // Skip threads that have already died + if (!thread.isAlive()) { + continue; + } + + // Don't warn about JVM controlled threads + if (thread.getThreadGroup() != null && + JVM_THREAD_GROUP_NAMES.contains( + thread.getThreadGroup().getName())) { + continue; + } + + log.error(sm.getString("webappClassLoader.warnThread", + thread.getName())); + + } + } + } + } + + /** * Determine whether a class was loaded by this class loader or one of * its child class loaders. -- 2.11.0