https://issues.apache.org/jira/browse/SOLR-1977
Mavenでダウンロードしようとしたらなかった…。階層がおかしいみたい。
Challange IT For Future
https://issues.apache.org/jira/browse/SOLR-1977
Mavenでダウンロードしようとしたらなかった…。階層がおかしいみたい。
話してきました。やっぱり 30 分だと時間が足りない感があるな…(もっとポイントをまとめた方がよかったんだろうけど…)。まぁ、Fessをざっくりと知ってもらう意味ではよしとしておくか。別なところで not 検索はできないのと言われたことがあったけど、今回はor検索やファセット検索ができないのという意見をいただきました。範囲検索、ファジーやクエリーブーストとかは投げられるのだけど、notやorは今のところ対応していない。これはこれで次のバージョンでどうするか考えよう(QueryHelperImplあたりをいじればよいだけだと思うけど)。あとはロールを聞かれたけど、聞かれた人に説明できてない…(ごめんなさい、来月初めあたりまでにはドキュメントを書きます…)。それ以外には、ビジネス的な話もよくいただくのですが、FessはN2 Searchとしてコンサルやサポートなどを含めて扱っています。来週後半に無料セミナをしますので、ビジネス的なお話がありましたら、そちらもよろしくお願いします。
@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 を使い始めて文句が多発で変更が入りそうな気がする動きだな…。