Skip to content

Commit

Permalink
Avoid stopping org.apache.tomcat.util.threads.TaskThread currently ex…
Browse files Browse the repository at this point in the history
…ecuting in web app - primarily by waiting, secondarily by setting context classloader rather than stopping. Fixes #29.
  • Loading branch information
Mattias Jiderhamn committed Apr 11, 2015
1 parent 7404bea commit 5e72be3
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 22 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -7,7 +7,7 @@
</parent>
<groupId>se.jiderhamn</groupId>
<artifactId>classloader-leak-prevention</artifactId>
<version>1.12.1-SNAPSHOT</version>
<version>1.13.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ClassLoader leak prevention</name>
<description>ServletContextListener that prevents ClassLoader leaks / java.lang.OutOfMemoryError: PermGen space</description>
Expand Down
Expand Up @@ -170,7 +170,7 @@ public class ClassLoaderLeakPreventor implements ServletContextListener {
/**
* No of milliseconds to wait for threads to finish execution, before stopping them.
*/
protected int threadWaitMs = SHUTDOWN_HOOK_WAIT_MS_DEFAULT;
protected int threadWaitMs = THREAD_WAIT_MS_DEFAULT;

/**
* No of milliseconds to wait for shutdown hooks to finish execution, before stopping them.
Expand Down Expand Up @@ -1142,13 +1142,15 @@ protected void stopThreads() {
final Field oracleTarget = findField(Thread.class, "target"); // Sun/Oracle JRE
final Field ibmRunnable = findField(Thread.class, "runnable"); // IBM JRE

final boolean waitForThreads = threadWaitMs > 0;
for(Thread thread : getAllThreads()) {
final Runnable runnable = (oracleTarget != null) ?
(Runnable) getFieldValue(oracleTarget, thread) : // Sun/Oracle JRE
(Runnable) getFieldValue(ibmRunnable, thread); // IBM JRE


final boolean runnableLoadedInWebApplication = isLoadedInWebApplication(runnable);
if(thread != Thread.currentThread() && // Ignore current thread
(isThreadInWebApplication(thread) || isLoadedInWebApplication(runnable))) {
(isThreadInWebApplication(thread) || runnableLoadedInWebApplication)) {

if (thread.getClass().getName().startsWith(JURT_ASYNCHRONOUS_FINALIZER)) {
// Note, the thread group of this thread may be "system" if it is triggered by the Garbage Collector
Expand Down Expand Up @@ -1200,26 +1202,25 @@ else if(thread.isAlive()) { // Non-system, running in web app
}

final String displayString = "'" + thread + "' of type " + thread.getClass().getName();

if(stopThreads) {
final String waitString = (threadWaitMs > 0) ? "after " + threadWaitMs + " ms " : "";
warn("Stopping Thread " + displayString + " running in web app " + waitString);

if(threadWaitMs > 0) {
try {
thread.interrupt(); // Make Thread stop waiting in sleep(), wait() or join()
}
catch (SecurityException e) {
error(e);
}

try {
thread.join(threadWaitMs); // Wait for thread to run
}
catch (InterruptedException e) {
// Do nothing
}
if(! isLoadedInWebApplication(thread) && ! runnableLoadedInWebApplication) { // Not loaded in web app - just running there
// This would for example be the case with org.apache.tomcat.util.threads.TaskThread
if(waitForThreads) {
warn("Thread " + displayString + " running in web app; waiting " + threadWaitMs);
waitForThread(thread, threadWaitMs);
}

if(thread.isAlive() && isWebAppClassLoaderOrChild(thread.getContextClassLoader())) {
warn("Thread " + displayString + (waitForThreads ? " still" : "") +
" running in web app; changing context classloader to system classloader");
thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
}
}
else if(stopThreads) { // Loaded by web app
final String waitString = waitForThreads ? "after " + threadWaitMs + " ms " : "";
warn("Stopping Thread " + displayString + " running in web app " + waitString);

waitForThread(thread, threadWaitMs);

// Normally threads should not be stopped (method is deprecated), since it may cause an inconsistent state.
// In this case however, the alternative is a classloader leak, which may or may not be considered worse.
Expand Down Expand Up @@ -1270,6 +1271,30 @@ protected void stopTimerThread(Thread thread) {
}
}

/**
* Make the provided Thread stop sleep(), wait() or join() and then give it the provided no of milliseconds to finish
* executing.
* @param thread The thread to wake up and wait for
* @param waitMs The no of milliseconds to wait. If <= 0 this method does nothing.
*/
protected void waitForThread(Thread thread, long waitMs) {
if(waitMs > 0) {
try {
thread.interrupt(); // Make Thread stop waiting in sleep(), wait() or join()
}
catch (SecurityException e) {
error(e);
}

try {
thread.join(waitMs); // Wait for thread to run
}
catch (InterruptedException e) {
// Do nothing
}
}
}

/** Destroy any ThreadGroups that are loaded by the application classloader */
public void destroyThreadGroups() {
try {
Expand Down

0 comments on commit 5e72be3

Please sign in to comment.