このエントリは以下のエントリをベースにしています。
This entry is based on the following one written by Nathan Rauh (Software Engineer, IBM).
https://openliberty.io/blog/2019/03/01/microprofile-concurrency.html
(訳注)
MicroProfile Context Propagationはスタンドアロン仕様として現在仕様作成中の段階です。
completion stages と completable futures はいずれもJDK 8で導入されました。これらを使うと、APIの利用方法に応じて、依存アクションのパイプラインを直列もしくは並列に実行できます。依存するステージの完了をもって従属ステージの実行を呼び出します。completion stagesがもたらすこの機能は、アプリケーションがイベントが発生するとそのイベントに反応するモデルをアプリケーションで実装できるようにする上で非常に有用ですが、組み込み実装(completable future)は、スレッドコンテキストの 一貫性(Consistency)と信頼性(Reliability)、そしてLibertyのグローバルスレッドプールで実行される非同期依存ステージアクションのデフォルト化という、重要な部分を無視します。MicroProfile Context Propagationでは、 ManagedExecutor
と ThreadContext
というAPIを導入し、こうした問題に対処しようとしています。MicroProfile Context Propagationの仕様は現在Open Liberty開発ビルドでご利用頂けます。
OpenLiberty development builds
https://openliberty.io/downloads/
Managed executors in MicroProfile Context PropagationのManaged executorを使うと、アクションがどのスレッドで起こるかに関わらず予測可能なスレッドコンテキストで実行されるcompletion stageを利用できます。この機能がない場合、completion stageアクションのスレッドコンテキストは、リクエストスレッド、依存するステージのスレッド、結果をリクエストするスレッド、前のステージを強制的に終了するスレッドのどれでアクションが実行されるかによって決まります。どのコンテキストでアクションが実行されるかの保証はありません。しかしながらMicroProfile Context Propagationを使えば、コンテキストは常にcompletion stageを作成するスレッドからキャプチャされ、アクション実行時に適用されるため、スレッドコンテキストが完全に確定的になります。
その他、コンテキストをキャプチャするのではなく、特定のコンテキストタイプを消去するように設定できます。例えば、セキュリティコンテキストのクリアとは、completion stageのアクションが実行されている間、スレッドに関連付けられているユーザーがいないことを意味します。マネージドexecutorがcompletion stageを作成すると、マネージドexecutorは作成したcompletion stageと関連付けられたままゆえ、スレッドコンテキストの伝播と特定のexecutorを指定しなくても非同期で実行するようにリクエストされる依存stageを決定します。マネージドexecutorは作成された各従属stageと関連付けられたままなので、それらの従属stageから作成された各従属stageなどはパイクラインの全てのステージで予測可能なスレッドコンテキストの伝播が可能になります。
以下の例では、マネージドexecutorを使ってアプリケーションの名前空間を非同期操作で伝播しようとしています。
CompletableFuture<Integer> stage = executor.supplyAsync(supplier1)
.thenApply(function1)
.thenApply(function2)
.thenApply(i -> {
try {
DataSource ds = InitialContext.doLookup("java:module/env/jdbc/ds1");
...
return result;
} catch (Exception x) {
throw new CompletionExeption(x);
}
});
Managed executors in MicroProfile Context PropagationのマネージドexecutorはJava EE Concurrencyのと完全に互換であり、将来プログラミングモデルを統合できる可能性を残しています。特に、org.eclipse.microprofile.concurrent.ManagedExecutorインターフェースはjava.util.concurrent.ExecutorServiceを継承しているため、ManagedExecutorServiceと同じ実行/送信/呼び出し操作が可能です。
MicroProfile Context Propagation では、管理対象でないcompletion stageの利用を簡単にするためのorg.eclipse.microprofile.concurrent.ThreadContextインターフェースを提供しています。マネージドexecutorが作成していないcompletion stageを有している場合でも、MicroProfile Context Propagation ThreadContextの対応するメソッドを使用してexecutorのアクションを事前にコンテキスト化すると、予測可能なスレッドコンテキストで実行できます。
CompletableFuture<Long> stage = CompletableFuture.supplyAsync(supplier1)
.thenApplyAsync(function1)
.thenApply(threadContext.contextualFunction(function2));
How to obtain ManagedExecutor and ThreadContext instances
ManagedExecutor
と ThreadContext
のインスタンスを取得する方法には数種類あります。
- MicroProfile Context Propagationはプログラミングのための fluent builder pattern を提供します。
ManagedExecutor executor = ManagedExecutor.builder()
.maxAsync(10)
.propagated(ThreadContext.APPLICATION, ThreadContext.SECURITY)
.cleared(ThreadContext.ALL_REMAINING)
.build();
- executorインスタンスが不要になったタイミングで、アプリケーションは作成したexecutorインスタンスをシャットダウンする必要があります。
- Java EE Concurrencyを使っている場合、既存のManagedExecutorServiceをManagedExecutorにキャストできます。
- MicroProfile Context Propagationを使えば、CDI(cdi-2.0以上が必要です)を使ってインスタンスの構成や注入も可能です。以下はCDI beanでの使用例です。
// CDI qualifier which is used to identify the executor instance
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface AppContext {}
// Example producer field, defined in a CDI bean,
@Produces @ApplicationScoped @AppContext
ManagedExecutor appContextExecutor = ManagedExecutor.builder()
.propagated(ThreadContext.APPLICATION)
.build();
// Example disposer method, also defined in the CDI bean,
void disposeExecutor(@Disposes @AppContext exec) {
exec.shutdownNow();
}
// Example injection point, defined in a CDI bean,
@Inject @AppContext
ManagedExecutor executor;
...
CompletableFuture<Integer> stage = executor
.supplyAsync(supplier1)
.thenApply(function1)
.thenApplyAsync(value -> {
try {
// access resource reference in application's java:comp namespace,
DataSource ds = InitialContext.doLookup("java:comp/env/jdbc/ds1");
...
return result;
} catch (Exception x) {
throw new CompletionException(x);
}
});
MicroProfile Context PropagationはJava SEのCompletableFutureを使って構築されており、その周りに必要なインフラストラクチャを構築します。これにより、信頼性の高いスレッドコンテキストの下、Libertyのスレッド処理のパフォーマンスに支えられて、イベントに反応する堅牢なアプリケーションを構築できます。