MySQLの Abandoned connection cleanup thread

TomcatのWebアプリでMySQLに接続した後、Tomcatの終了やWebアプリのリロードなどをするとTomcatのログに以下のようなエラーが表示される。

9 18, 2012 12:29:13 午後 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [/xxxxx] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak.

デプロイの激しい運用に使うんでもなし無視してもいいかなーって気もしたけど、
SEVERE とまで言われると、ちょっとほっておくのもよくないかということで調べてみた。


利用したバージョンは以下
Tomcat 7.0.30
MySQL Connecter/J 5.1.22


バグレポート

レポートが上がってた

Bug #65909 referenceThread causes memory leak in Tomcat
http://bugs.mysql.com/bug.php?id=65909


目下議論中の模様
たぶんこんな感じ


NonRegisteringDriverでスレッド作りっぱなしなんだけど

referenceThread.setContextClassLoader(null) してくれたら、Tomcat側でエラーでないよ

いやいや、警告は出ないけど、スレッドが残っててメモリリークになるんじゃ (今ココ)


しばらくしたら、何がしかの解決にいたると思うけど、
仕様的にどこで対処すべき問題だとか、おらー何だかよくわからないので、
中の人がうまいこと決着してくれるのを待つことにします
以下補足メモ


JDBCクライアント側 NonRegisteringDriver

Tomcatに文句を言われているMySQLJDBCクライアントのコードは以下
com.mysql.jdbc.Driverの親クラスの
com.mysql.jdbc.NonRegisteringDriverのstaticイニシャライザ



static {
Thread referenceThread = new Thread("Abandoned connection cleanup thread") {
public void run() {
while (true) {
try {
Reference ref = refQueue.remove();
try {
((ConnectionPhantomReference) ref).cleanup();
} finally {
connectionPhantomRefs.remove(ref);
}
} catch (Exception ex) {
// no where to really log this if we're static
}
}
}
};

referenceThread.setDaemon(true);
referenceThread.start();
}


Class.forNameでロードしたときに呼ばれるコードなので、DBに接続しなくても再現可能
1年前のバージョンのConnector/J(5.1.16)のNonRegisteringDriverには、この処理はなかった


Tomcat側 clearReferencesThreads

Tomcatでエラーを出力してるコードは

org.apache.catalina.loader.WebappClassLoader クラスの
#clearReferences メソッドから呼ばれる
#clearReferencesThreads メソッドの中の以下の部分



@SuppressWarnings("deprecation") // thread.stop()
private void clearReferencesThreads() {
Thread[] threads = getThreads();

// Iterate over the set of threads
for (Thread thread : threads) {
if (thread != null) {
ClassLoader ccl = thread.getContextClassLoader();
if (ccl == this) {
// Don't warn about this thread
if (thread == Thread.currentThread()) {
continue;
}

// JVM controlled threads
ThreadGroup tg = thread.getThreadGroup();
if (tg != null &&
JVM_THREAD_GROUP_NAMES.contains(tg.getName())) {

// HttpClient keep-alive threads
if (clearReferencesHttpClientKeepAliveThread &&
thread.getName().equals("Keep-Alive-Timer")) {
thread.setContextClassLoader(parent);
log.debug(sm.getString(
"webappClassLoader.checkThreadsHttpClient"));
}

// Don't warn about remaining JVM controlled threads
continue;
}

// Skip threads that have already died
if (!thread.isAlive()) {
continue;
}

// TimerThread can be stopped safely so treat separately
// "java.util.TimerThread" in Sun/Oracle JDK
// "java.util.Timer$TimerImpl" in Apache Harmony and in IBM JDK
if (thread.getClass().getName().startsWith("java.util.Timer") &&
clearReferencesStopTimerThreads) {
clearReferencesStopTimerThread(thread);
continue;
}

if (isRequestThread(thread)) {
log.error(sm.getString("webappClassLoader.warnRequestThread",
contextName, thread.getName()));
} else {
log.error(sm.getString("webappClassLoader.warnThread",//※←(1)
contextName, thread.getName()));
}

// Don't try an stop the threads unless explicitly
// configured to do so
if (!clearReferencesStopThreads) {//※←(2)
continue;
}

// If the thread has been started via an executor, try
// shutting down the executor
try {

//...

※(1) 該当のエラーを出力している箇所

ぐぐったら日本語の解説あった。

技術者が知っておきたいTomcat 7の新機能20連発
http://www.atmarkit.co.jp/fjava/rensai4/tomcat7_03/02.html
の「実行中スレッドの停止」の「リクエスト処理スレッド以外のスレッドが実行中の場合」にあたる処理


※(2) context.xmlで  と指定すると
 この後のコードでスレッドをstopに行くが、メッセージの後なので、結局のところSEVEREな警告は出ると思われる