HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

Jakarta Slideの問題

Jakarta Slide。WebDAVをフルサポートしたコンテンツリポジトリ

http://jakarta.apache.org/slide/

もう、プロジェクト自体がリタイヤしてしまったので、時代遅れといわれればそのとおりなのだが、未だに使われている&置き換える気力が無い(ぉ

で、このSlideのServletを含むWebアプリに関して、以下のケースで問題となっている。

  • EclipseからTomcatプラグインで起動したTomcatがちゃんと停止しない。
  • WASの管理コンソールで当該アプリを停止してもちゃんと停止しない。

このWebアプリの起動&停止だけならSlideのServletが動いていなくても問題ないので、試しに、SlideのServletを起動しないようにweb.xmlの関連箇所をコメントアウトして試したところ、今度はきちんと停止する。ということは、こいつが原因であることは間違いない。
さぁ、トラブルシューティングだっ!!(意味不明なテンション(謎))

まずはSlideのServletを単体で起動

上記のサイトから辿れるアーカイブサイトで、以下のファイルをダウンロードする。

このファイルの中に含まれる「jakarta-slide-server-bin-2.1/slide/webapp/slide.war」を展開し、そのままTomcatにデプロイする。
このTomcatEclipseから起動して停止してみる。やはりちゃんと停止しないことを確認。

大まかな原因の特定

可能性として一番疑われるのは、自前でスレッドを起動していながら終了処理をちゃんとやっていないこと。
そこで、以下のようなServletを作ってみる。

package hoge.foo.bar;

import org.apache.slide.webdav.WebdavServlet;

public class MyWebdavServlet extends WebdavServlet {

    public void destroy() {
        super.destroy();

        dumpThreads();
    }

    public static void dumpThreads() {
        Thread[] thread = new Thread[Thread.activeCount()];
        Thread.enumerate(thread);
        for (int i = 0; i < thread.length; ++i) {
            if (thread[i] != null) {
                System.out.println("thread[" + i + "] (" + thread[i].isDaemon() + ") " + thread[i].getName());
            } else {
                System.out.println("thread[" + i + "] (null) " + thread[i]);
            }
        }
    }
}

そして、web.xmlの73行目を以下のように書き換える。

        <servlet-class>hoge.foo.bar.MyWebdavServlet</servlet-class>

そして、起動&停止してみると、コンソールに以下のような出力がされる。

thread[0] (false) main
thread[1] (true) StandardManager[/manager]
thread[2] (false) Thread-2
thread[3] (true) StandardManager[]
thread[4] (null) null
thread[5] (null) null

"(null)"になっているのはセキュリティの関係で見えないか何かだろうから放置。最初の"main"は大元のスレッドだろう。
この中で明らかに怪しいのは3番目の

thread[2] (false) Thread-2

非デーモンスレッドである上に、名前に何も指定しなかった場合のスレッド名になっている。
さて、こいつを起動したのは誰なのか、という話か‥

スレッドダンプを取ってみる

詳細は以下のサイトに詳しく書いてあるので省略。
Java - スレッドダンプの取り方 その3 / Windows サービスとして登録している場合は? - #侍ズム
Windowsのサービスに限らず、今回のようにEclipseからTomcatプラグインを利用して起動している場合にももちろん有効。
上記のスレッド一覧が表示された状態(まだプロセスは生きている)でスレッドダンプを取ってみると、以下のような結果が得られた。

Full thread dump Java HotSpot(TM) Client VM (1.4.2_15-b02 mixed mode):

"DestroyJavaVM" prio=5 tid=0x06e6acb8 nid=0x1954 waiting on condition [0x00000000..0x0006faa4]

"Thread-2" prio=5 tid=0x07212000 nid=0x14f4 in Object.wait() [0x075ef000..0x075efd68]
    at java.lang.Object.wait(Native Method)
    - waiting on <0x10c93330> (a java.util.TaskQueue)
    at java.lang.Object.wait(Object.java:429)
    at java.util.TimerThread.mainLoop(Timer.java:403)
    - locked <0x10c93330> (a java.util.TaskQueue)
    at java.util.TimerThread.run(Timer.java:382)

"Signal Dispatcher" daemon prio=10 tid=0x009754c8 nid=0x2230 waiting on condition [0x00000000..0x00000000]

"JDWP Command Reader" daemon prio=5 tid=0x009744c0 nid=0x12e8 runnable [0x00000000..0x00000000]

"JDWP Event Helper Thread" daemon prio=5 tid=0x00995d68 nid=0x22d0 runnable [0x00000000..0x00000000]

"JDWP Transport Listener: dt_socket" daemon prio=5 tid=0x00995788 nid=0x2788 runnable [0x00000000..0x06cbfb34]

"Finalizer" daemon prio=9 tid=0x00971d48 nid=0x6c4 in Object.wait() [0x02bbf000..0x02bbfd68]
    at java.lang.Object.wait(Native Method)
    - waiting on <0x104fd460> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:111)
    - locked <0x104fd460> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:127)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)

"Reference Handler" daemon prio=10 tid=0x00970938 nid=0xb3c in Object.wait() [0x02b7f000..0x02b7fd68]
    at java.lang.Object.wait(Native Method)
    - waiting on <0x104fd480> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:429)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:115)
    - locked <0x104fd480> (a java.lang.ref.Reference$Lock)

"VM Thread" prio=5 tid=0x0093f4d8 nid=0x1b1c runnable 

"VM Periodic Task Thread" prio=10 tid=0x00978a10 nid=0x17c8 waiting on condition 
"Suspend Checker Thread" prio=10 tid=0x00975248 nid=0x6fc runnable 

destroy()メソッド内で一覧を出力した以上にいろいろいるみたいだが、今回注目するのは"Thread-2"。

:
"Thread-2" prio=5 tid=0x07212000 nid=0x14f4 in Object.wait() [0x075ef000..0x075efd68]
    at java.lang.Object.wait(Native Method)
    - waiting on <0x10c93330> (a java.util.TaskQueue)
    at java.lang.Object.wait(Object.java:429)
    at java.util.TimerThread.mainLoop(Timer.java:403)
    - locked <0x10c93330> (a java.util.TaskQueue)
    at java.util.TimerThread.run(Timer.java:382)
:

いたいた。java.util.Timerオブジェクトが生成するスレッドのようだ。

犯人探し

今度は、上記のサイトから辿れるアーカイブサイトで、以下のファイルをダウンロードする。

このファイルを展開し、

cd /D C:\jakarta-slide-server-src-2.1
dir /s /b src | findstr /F:/ Timer

を実行する。(UNIX/Linuxでの"grep -r 'Timer[(]' src"を実行したいだけなのにえらい苦労した‥)
すると、結果は以下。

C:\jakarta-slide-server-src-2.1\src\webdav\server\org\apache\slide\webdav\event\NotificationTrigger.java:    protected static final Timer timer = new Timer();

このソースを見てみると、確かにjava.util.Timerオブジェクトを非デーモンモードで生成している。
また、このTimerオブジェクトに対する操作はscheduleメソッドの呼び出しのみであるし、このクラスを継承しているクラスもなさそう。
なにより、ふと思い立ったようにアプリケーションルートにあるDomain.xmlを見てみると‥いたいた。

        <listener classname="org.apache.slide.webdav.event.NotificationTrigger">
            <configuration>
                <notification include-events="false" />
                <persist-subscriptions filename="subscriptions.xml" />
            </configuration>
        </listener>

試しにこいつをコメントアウトして起動&停止してみると、ちゃんと終了する。
ということは、「分からない設定はいじらない」かつ「稼動中の動作を変えない」ようにするためには、このソースファイルを修正して再ビルド、ということになるか‥

ということで再ビルド

ここまでくれば話は簡単。
NotificationTrigger.javaの89行目を以下のように修正する。

    protected static final Timer timer = new Timer(true);

そして、Antでビルド。もちろん、環境が整っていることは前提として。

cd /D C:\jakarta-slide-server-src-2.1
ant clean dist

これで生成されたdist\slide\lib\slide-webdavservlet-2.1.jarに置き換えて、またまた起動&停止。
今度はちゃんと停止した。

あー、疲れた‥