クローズ忘れの検出
InputStreamなどのclose忘れを検出できるようにしてみます。
戦略
- クローズしないといけないモノを生成したら、タイマーを起動。一定時間内にクローズされなかった場合、エラーを報告する。
- ファイナライズされた場合も同様。
- どこで作られたかわかるようにするため、例外を利用する。
実装:
import java.io.Closeable; import java.io.IOException; import java.util.Timer; import java.util.TimerTask; /** * {@link java.io.Closeable} にclose忘れ検出機能を付加する。 */ public class ForgottenDetector implements Closeable { /** 猶予期間 (秒) */ private static final int MORATORIUM = 10; /**移譲先{@link Closeable}*/ private final Closeable delegate; /**作成された場所を特定するための例外。*/ private final RuntimeException ex; /**タイマー*/ private Timer timer; /** * コンストラクタ * @param delegate 移譲先{@link Closeable} */ public ForgottenDetector ( Closeable delegate ){ this.delegate = delegate; this.ex = new RuntimeException("resource not closed."); this.timer = new Timer("ForgottenDetector", true); this.timer.schedule( new TimerTask() { public void run() { report(); } }, 1000 * MORATORIUM ); } /* 継承元クラスのJavaDocを参照 */ public void close () throws IOException { try { delegate.close(); } finally { if ( this.timer != null ) { this.timer .cancel(); this.timer = null; } } } /* 継承元クラスのJavaDocを参照 */ protected void finalize () throws Throwable { try { super.finalize(); } finally { report(); } } /** * close忘れを報告する。 */ private final void report() { if ( this.timer != null ) { this.timer.cancel(); ex.printStackTrace(); // 標準エラーに出力。 this.timer = null; } } }
使い方:
Closeable closeable = new ByteArrayInputStream( new byte[0] ); closeable = new ForgottenDetector(closeable ); // 検知器を設定する。 Thread.sleep(1000*12); // 猶予期間を超えるまで待つ。
出力:
java.lang.RuntimeException: resource not closed. at ForgottenDetector.<init>(ForgottenDetector.java:31) ....
ただし、これだと検知器適用後はclose()しか実行できません。InputStreamであればread()などのAPIも委譲しないと使えない・・。orz。汎用的にするのであれば、Aspectで実装するのがスマートかも。