clearReferences系のエラーログの対応方法

調査を進めた。clearReferencesThreadsとclearThreadLocalMapのエラーを黙らせるには以下のようなコードをサーブレットのdestroyに書けば良い(サーブレットの定義順には注意)。

@Override
public void destroy() {
stopAllThreads();
}
private void stopAllThreads() {
Thread[] threads = getThreads();
ClassLoader cl = this.getClass().getClassLoader();
List<String> jvmThreadGroupList = new ArrayList<String>();
jvmThreadGroupList.add("system");
jvmThreadGroupList.add("RMI Runtime");
// Iterate over the set of threads
for (Thread thread : threads) {
if (thread != null) {
ClassLoader ccl = thread.getContextClassLoader();
if (ccl != null && ccl == cl) {
// Don't warn about this thread
if (thread == Thread.currentThread()) {
continue;
}
// Don't warn about JVM controlled threads
ThreadGroup tg = thread.getThreadGroup();
if (tg != null && jvmThreadGroupList.contains(tg.getName())) {
continue;
}
waitThread(thread);
// Skip threads that have already died
if (!thread.isAlive()) {
continue;
}
if (logger.isInfoEnabled()) {
logger.info("Interrupting a thread ["
+ thread.getName() + "]...");
}
thread.interrupt();
waitThread(thread);
// Skip threads that have already died
if (!thread.isAlive()) {
continue;
}
if (logger.isInfoEnabled()) {
logger.info("Stopping a thread [" + thread.getName()
+ "]...");
}
thread.stop();
}
}
}
Field threadLocalsField = null;
Field inheritableThreadLocalsField = null;
Field tableField = null;
try {
threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
inheritableThreadLocalsField = Thread.class
.getDeclaredField("inheritableThreadLocals");
inheritableThreadLocalsField.setAccessible(true);
// Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects
// accessible
Class<?> tlmClass = Class
.forName("java.lang.ThreadLocal$ThreadLocalMap");
tableField = tlmClass.getDeclaredField("table");
tableField.setAccessible(true);
} catch (Exception e) {
// ignore
}
for (Thread thread : threads) {
if (thread != null) {
Object threadLocalMap;
try {
// Clear the first map
threadLocalMap = threadLocalsField.get(thread);
clearThreadLocalMap(cl, threadLocalMap, tableField);
} catch (Exception e) {
// ignore
}
try { // Clear the second map
threadLocalMap = inheritableThreadLocalsField.get(thread);
clearThreadLocalMap(cl, threadLocalMap, tableField);
} catch (Exception e) {
// ignore
}
}
}
}
private void waitThread(Thread thread) {
int count = 0;
while (thread.isAlive() && count < 5) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
count++;
}
}
/*
* Get the set of current threads as an array.
*/
private Thread[] getThreads() {
// 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);
}
return threads;
}
private void clearThreadLocalMap(ClassLoader cl, Object map,
Field internalTableField) throws NoSuchMethodException,
IllegalAccessException, NoSuchFieldException,
InvocationTargetException {
if (map != null) {
Method mapRemove = map.getClass().getDeclaredMethod("remove",
ThreadLocal.class);
mapRemove.setAccessible(true);
Object[] table = (Object[]) internalTableField.get(map);
if (table != null) {
for (int j = 0; j < table.length; j++) {
if (table[j] != null) {
boolean remove = false;
// Check the key
Field keyField = Reference.class
.getDeclaredField("referent");
keyField.setAccessible(true);
Object key = keyField.get(table[j]);
if (cl.equals(key)
|| (key != null && cl == key.getClass()
.getClassLoader())) {
remove = true;
}
// Check the value
Field valueField = table[j].getClass()
.getDeclaredField("value");
valueField.setAccessible(true);
Object value = valueField.get(table[j]);
if (cl.equals(value)
|| (value != null && cl == value.getClass()
.getClassLoader())) {
remove = true;
}
if (remove) {
Object entry = ((Reference<?>) table[j]).get();
if (logger.isInfoEnabled()) {
logger.info("Removing " + key.toString()
+ " from a thread local...");
}
mapRemove.invoke(map, entry);
}
}
}
}
}
}

上記のコードで、スレッドが終了するのを待ち、Tomcatが処理する前にスレッド系のものたちをクリーンするのでエラーが出なくなる(TimerThreadを使っているものがあれば、上記のコードは未対応なので処理が必要)。Tomcatがやりたい、メモリリークをしないようにというのは分かるのだけど、これを 6.0.24 からエラーレベルでログは吐くのは間違っているんじゃないかね…。WARNくらいなレベルな気がするのだけど。しかも、シャットダウンする時なので、そもそも全部捨てるから吐かれてもな、という気がする(再配備するようなケースではこの処理があると有効かも)。
今回はアプリ側で直したけど、S2とかのライブラリ側で対応するとなると、ThreadLocalをextendedしないとか、ThreadLocalは使い終わりにremove()を呼んでおくとか、そういう対応になるような気が。たとえば、S2のHttpServletExternalContextとか、スレッドローカルがあるけど、removeな状態にしておくのは問題がある気がするし。しかも、スレッドローカルだから消すのがめんどい…。そんな感じで、そもそもの解決策は、どうせ値を消しているのだから、Tomcatがエラーレベルでログを吐かなければ良いだけじゃないかね。
そのうち、みんなが 6.0.24 を使い始めて文句が多発で変更が入りそうな気がする動きだな…。

clearReferences系のエラーログ、その2

clearReferencesJdbcは Seasar2 の DriverManagerUtil#deregisterAllDrivers() で無事に削除できた。でも、まだ、clearReferencesThreadsとclearThreadLocalMapがでますね…。clearReferencesThreadsの方は終了するときにsleepをしたりすると、でなくなったりするので、スレッドの終了待ちをすれば良いのかも。clearThreadLocalMapは自分でnullにしないといかんのか? この問題って、ぐぐっても Jackrabbit くらいしか引っかからんのだけど、まだ Tomcat 6.0.24 ってあまりつかわれていないのだろうか…。

10/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
致命的: A web application appears to have started a thread named [Seasar2-TimeoutManager]
but has failed to stop it. This is very likely to create a memory leak.
2010/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
致命的: A web application appears to have started a thread named [H2 Log Writer FESS]
but has failed to stop it. This is very likely to create a memory leak.
2010/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
致命的: A web application appears to have started a thread named [chronos-pool-1-daemonthread-1]
but has failed to stop it. This is very likely to create a memory leak.
2010/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap
致命的: A web application created a ThreadLocal with key of type [org.seasar.framework.
container.factory.S2ContainerFactory$1] (value [org.seasar.framework.container.factory.
S2ContainerFactory$1@62b21d14]) and a value of type [java.util.LinkedHashSet] (value )
but failed to remove it when the web application was stopped. To prevent a memory leak, the ThreadLocal has been forcibly removed.
2010/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap
致命的: A web application created a ThreadLocal with key of type [org.seasar.framework.container.external.servlet.HttpServletExternalContext.ImmutableMapThreadLocal] (value [org.seasar.framework.container.external.servlet.HttpServletExternalContext$ImmutableMapThreadLocal@68634baf])
and a value of type [java.util.Collections.EmptyMap] (value [{}]) but failed to remove it when the web
application was stopped. To prevent a memory leak, the ThreadLocal has been forcibly removed.
2010/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap
致命的: A web application created a ThreadLocal with key of type [org.seasar.framework.container.external.servlet.HttpServletExternalContext.ImmutableMapThreadLocal] (value [org.seasar.framework.container.external.servlet.HttpServletExternalContext$ImmutableMapThreadLocal@29ba92bb])
and a value of type [java.util.Collections.EmptyMap] (value [{}]) but failed to remove it when the web
application was stopped. To prevent a memory leak, the ThreadLocal has been forcibly removed.
2010/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap
致命的: A web application created a ThreadLocal with key of type [org.seasar.framework.container.external.servlet.HttpServletExternalContext.MutableMapThreadLocal]
(value [org.seasar.framework.container.external.servlet.HttpServletExternalContext$MutableMapThreadLocal@17f74864])
and a value of type [java.util.HashMap] (value [{}]) but failed to remove it when the web application was stopped.
To prevent a memory leak, the ThreadLocal has been forcibly removed.
2010/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap
致命的: A web application created a ThreadLocal with key of type [org.seasar.framework.container.external.servlet.HttpServletExternalContext.ImmutableMapThreadLocal] (value [org.seasar.framework.container.external.servlet.HttpServletExternalContext$ImmutableMapThreadLocal@6bb1a986])
and a value of type [java.util.Collections.EmptyMap] (value [{}]) but failed to remove it when the web application
was stopped. To prevent a memory leak, the ThreadLocal has been forcibly removed.
2010/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap
致命的: A web application created a ThreadLocal with key of type [org.seasar.framework.container.external.servlet.HttpServletExternalContext.ImmutableMapThreadLocal] (value [org.seasar.framework.container.external.servlet.HttpServletExternalContext$ImmutableMapThreadLocal@5cf0d3a6])
and a value of type [java.util.Collections.EmptyMap] (value [{}]) but failed to remove it when the web application
was stopped. To prevent a memory leak, the ThreadLocal has been forcibly removed.
2010/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap
致命的: A web application created a ThreadLocal with key of type [org.seasar.framework.container.external.servlet.HttpServletExternalContext.MutableMapThreadLocal] (value [org.seasar.framework.container.external.servlet.HttpServletExternalContext$MutableMapThreadLocal@46d766f5])
and a value of type [java.util.HashMap] (value [{}]) but failed to remove it when the web application was stopped.
To prevent a memory leak, the ThreadLocal has been forcibly removed.
2010/02/10 6:10:18 org.apache.catalina.loader.WebappClassLoader clearThreadLocalMap
致命的: A web application created a ThreadLocal with key of type [org.seasar.framework.container.external.servlet.HttpServletExternalContext.ImmutableMapThreadLocal] (value [org.seasar.framework.container.external.servlet.HttpServletExternalContext$ImmutableMapThreadLocal@67a88328])
and a value of type [java.util.Collections.EmptyMap] (value [{}]) but failed to remove it when the web application
was stopped. To prevent a memory leak, the ThreadLocal has been forcibly removed.

第二回Solr勉強会

3/11(木)に第二回Solr勉強会が開催されます。そこで Fess について話す予定です。

http://atnd.org/events/3142

何を話すか、細かいことは決めてないのだけど、Fess のデモベースに何かできればな~と思っています。タイトルも何かキャッチーなものにしたかったのだけど、考える時間がなかった。という感じなのですが、興味がありましたらどうぞ、お越しください。