人類みんなごくつぶし

2012-09-18

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に文句を言われているMySQLのJDBCクライアントのコードは以下

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で <Context clearReferencesStopThreads="true"...> と指定すると

 この後のコードでスレッドをstopに行くが、メッセージの後なので、結局のところSEVEREな警告は出ると思われる

トラックバック - http://d.hatena.ne.jp/muimy/20120918/p1