読者です 読者をやめる 読者になる 読者になる
無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

呼び出し元のスタックトレースを引き継ぐFuture

Java

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;
    }
}