拡張子がtbz2のファイルが残る件のデバッグをしてみた。

以前に、vfsで、tbz2 を扱うとファイルを open しっぱなしではないか?という疑いがあったので、調べてみた。デバッグといっても、大体こんな風だ。


1. 現象の把握
現象を軽くまとめると次のようになる。

  • hoge.tbz2 というファイルを利用すると、closeしても、ファイルが削除されずに残る。
  • 再現ケースあり(100%再現)

仕事でやっているのと似たような事をやっている気がしないでもないが、現象の雰囲気を察すると、動作は問題ないため、ファイルがcloseされていない可能性が高いとあたりを付ける。違う場合は、また動作から推測して別のシナリオを考えてそれに沿って、デバッグをしていけばいい。


2. だれがOpenしているか?
InputStream.close() が呼びだれていない可能性から、close()をいくら探しても手がかりは遠い可能性が高い。なので、まずは誰が(どのクラスが)どこでInputStreamをOpenしているかを探すのが近道になりそうだ。

Javaでファイルアクセス(しかも、I/Oを使う) のであれば、FileInputStream / FileOutputStream を利用することが考えられる。私がテストしたのは、読み込みなので(というか、書き込みサポートしているか調べてない。)今回は読み込みだけなので、FileInputStream だけを考えればよい。

Eclipseとかで再現しちゃうような、ちっちゃいテストケースだと、ブレークポイントを簡単にしかける事が可能だ。というわけで、プロジェクトに設定されているライブラリの JRE システム・ライブラリ の rt.jar の java.io.FileInputStream のコンストラクタの先頭にブレークポイントを仕掛けておく。

実行するといくつか関係ないファイルが読み出されるが、その辺はどんどんスキップして、目的のファイルが引数に設定されるまで我慢する。

予定したファイルが読み込まれたら、そのスタックをチェックする。Eclipseでやってしまえば、ソースの場所を指定すれば、その場所を表示してくれるので、それの方が早いかも。

で、その場所を見ると、次のメソッドのような個所だった。
(Commons-VFS の org.apache.commons.vfs.provider.tar.TarFileSystem というクラスのソースより引用)

protected TarInputStream createTarFile(final File file) throws FileSystemException
...
else if ("tbz2".equalsIgnoreCase(getRootName().getScheme()))
{
return new TarInputStream(Bzip2FileObject.wrapInputStream(
file.getAbsolutePath(), new FileInputStream(file))); // ここ
}
...

ここで作成されていることがわかった。


3. 調査対象の絞込み

Commons-VFS のクラスの構造からして、特定のファイルでのみ発生するということは、逆にいえば、InputStream.close() は正しく呼ばれている可能性が高い(もし、呼ばれていなければ、他のファイルで軒並み発生する事の方が可能性が高い)。そのことから、ここで関連する

  • TarInputStream
  • Bzip2FileObject.wrapInputStream

で返されるオブジェクトの InputStream.close() が、うまく引数にあるFileInputStream.close() に伝わっていないと考えられる。
この事から、先ほどの2つのどちらかで close() の動作が怪しいのではないか?と考えソースを調査する。

TarInputStream を見ると、正しくcloseしている(ただ、普通の tar では発生しなかったので、これは正しく動作している可能性が高い。理由は先ほどと同じで、ここで問題がでるようであれば、普通の tar でも同じ問題が出る事が考えられる)。次の候補の、Bzip2FileObject.wrapInputStream で返される InputStream の動作だ。
これは、InputStream を引数にして、CBZip2InputStream を作成し返している。その為、次は、CBZip2InputStream を調べることにする。


4. ということで多分結論

この辺のクラスは、commons compress (sandboxというところにある) にあって、次のリンクから参照できた。

CBZip2InputStream

これをみると InputStream を直接継承し、かつ close() をオーバーライドしていない。ということは、使ってる人が close() を呼び出しても、引数で渡された InputStream はそのまま放置されちゃいそうだ。犯人は、このクラスで、ちゃんとコンストラクタで渡された、InputStream の close() を呼び出すようにする必要がありそうだ。


5. そ、その後?

と、この辺までは、環境をセットアップしてあって、最小限の再現ケースがあれば、すぐだ。次は、修正をいれて見て、ほんとに推測があっているか検証するテストケースを書いて、レポート?なのかな。やったことないけど。。時間が取れたら、考えてみよう。