Author: markt
Date: Sun Nov 29 19:27:38 2009
New Revision: 885260

URL: http://svn.apache.org/viewvc?rev=885260&view=rev
Log:
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.

Modified:
    tomcat/trunk/java/org/apache/catalina/loader/LocalStrings.properties
    tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java

Modified: tomcat/trunk/java/org/apache/catalina/loader/LocalStrings.properties
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/LocalStrings.properties?rev=885260&r1=885259&r2=885260&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/loader/LocalStrings.properties 
(original)
+++ tomcat/trunk/java/org/apache/catalina/loader/LocalStrings.properties Sun 
Nov 29 19:27:38 2009
@@ -33,7 +33,8 @@
 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}

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=885260&r1=885259&r2=885260&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java 
(original)
+++ tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java Sun Nov 
29 19:27:38 2009
@@ -112,9 +112,21 @@
     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<String> JVM_THREAD_GROUP_NAMES =
+        new ArrayList<String>();
+
     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<ResourceEntry> {
 
@@ -1555,7 +1567,7 @@
         clearReferences();
 
         started = false;
-
+        
         int length = files.length;
         for (int i = 0; i < length; i++) {
             files[i] = null;
@@ -1636,6 +1648,9 @@
         // 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 @@
     }
 
 
+    /**
+     * 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 @@
             List<String> driverNames = (List<String>) 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 @@
     }
 
 
-    protected void nullInstance(Object instance) {
+    private void nullInstance(Object instance) {
         if (instance == null) {
             return;
         }
@@ -1842,6 +1856,56 @@
     }
 
 
+    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.



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to