呼び出し元のスタックトレースを引き継ぐFuture
JavaのConcurrentフレームワークで非同期例外が発生した時に、非同期スレッド内のトレースに加えて呼び出し元のスタックトレースを返すFutureのデコレータを作ってみました。
例えば、次のようなコードを実行した場合、
// 非同期処理 Callable<Void> c = new Callable<Void>() { public Void call() throws Exception { throw new IOException(); // 例外をスロー } }; // 非同期処理を実行 Future<Void> future = Executors.newSingleThreadExecutor().submit(c); try { // 結果を取得 future.get(); } catch ( ExecutionException e ) { e.getCause().printStackTrace(); }
普通のFutureだと↓のように非同期スレッド内のトレースしか出力されないため、非同期処理がどこで開始されたかさっぱりわかりません。
java.io.IOException at StackTraceInheritedFutureTest$1.call(StackTraceInheritedFutureTest.java:27) at StackTraceInheritedFutureTest$1.call(StackTraceInheritedFutureTest.java:1) at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source) at java.util.concurrent.FutureTask.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source)
このデコレータを使うと、
java.io.IOException at StackTraceInheritedFutureTest$1.call(StackTraceInheritedFutureTest.java:27) at StackTraceInheritedFutureTest$1.call(StackTraceInheritedFutureTest.java:1) at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source) at java.util.concurrent.FutureTask.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source) at StackTraceInheritedFuture.<init>(StackTraceInheritedFuture.java:28) at StackTraceInheritedFutureTest.main(StackTraceInheritedFutureTest.java:39) ←このへんが非同期処理を開始した場所
という感じで非同期処理の呼び出し元のトレースが付加されるので、問題の箇所を速やかに特定できます。
使い方
デコレータなので、Futureを取得したタイミングでラップしてやればOK。
Future<Void> future = Executors.newSingleThreadExecutor().submit(callable);
future = new StackTraceInheritedFuture<Void>(future);
実装
次の通り。デコレータ作成時にスタックトレースを取得しておき、例外が発生した場合、トレースをマージして返すだけ。
import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * 非同期例外が発生した場合、非同期処理の呼び出しもとのスタックトレースを例外に付加して返す * {@link Future}のデコレータ。 * * @param <V> 値 */ public class StackTraceInheritedFuture<V> implements Future<V> { /**処理の委譲先*/ private final Future<V> delegate; /**呼び出しもとスタックトレース*/ private final StackTraceElement[] trace; /** * コンストラクタ * @param delegate 委譲先 */ public StackTraceInheritedFuture( Future<V> delegate ) { this.delegate = delegate; this.trace = new Exception().getStackTrace(); } /* 継承元クラスのJavaDocを参照 */ public boolean cancel(boolean mayInterruptIfRunning) { return delegate.cancel(mayInterruptIfRunning); } /* 継承元クラスのJavaDocを参照 */ public V get() throws InterruptedException, ExecutionException { try { return delegate.get(); } catch ( ExecutionException e ) { throw marge(e); } } /* 継承元クラスのJavaDocを参照 */ public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { try { return delegate.get(timeout, unit); } catch ( ExecutionException e ) { throw marge(e); } } /* 継承元クラスのJavaDocを参照 */ public boolean isCancelled() { return delegate.isCancelled(); } /* 継承元クラスのJavaDocを参照 */ public boolean isDone() { return delegate.isDone(); } /** * 発生した非同期例外に、元のスタックトレースをマージする。 * @param e 非同期例外 */ private final ExecutionException marge( ExecutionException e ) { Throwable t = e.getCause(); if ( t == null ) return e; StackTraceElement[] org = t.getStackTrace(); StackTraceElement[] newTrace = new StackTraceElement[trace.length+org.length]; System.arraycopy(org, 0, newTrace, 0, org.length); System.arraycopy(trace, 0, newTrace, org.length, trace.length); t.setStackTrace(newTrace); return e; } }