

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# エラー処理
<a name="errorhandling"></a>

**Topics**
+ [TryCatchFinally のセマンティクス](#errorhandling.trycatchfinally)
+ [キャンセル](#test.cancellation.resources)
+ [ネストされた TryCatchFinally](#errorhandling.nested)

Java の `try`/`catch`/`finally` コンストラクトは、エラー処理の簡単な方法として広く利用されています。このコンストラクトでは、エラー処理をコードのブロックと関連付けることができます。内部的には、エラー処理に関する追加のメタデータがコールスタックに追加されます。例外がスローされると、ランタイムでは、関連付けられたエラー処理をコールスタックで見つけて呼び出します。適切なエラー処理が見つからない場合は、コールチェーンの上に例外を伝播させます。

この方法は、同期コードには適していますが、非同期プログラムや分散プログラムでのエラー処理には他の問題が伴います。非同期呼び出しはすぐに戻るため、非同期コードの実行時に呼び出し元は呼び出しスタックにありません。つまり、非同期コードの未処理の例外は、呼び出し元が通常の方法で処理できません。一般的に、非同期コードでの例外を処理するには、エラー状態をコールバックに渡し、それを非同期メソッドに渡します。または、`Future<?>` を使用している場合は、これにアクセスしようとするとエラーが報告されます。これは理想的な状態ではありません。例外を受け取るコード (`Future<?>` を使用するコードまたはコールバック) は元の呼び出しのコンテキストを参照できず、例外を適切に処理できない可能性があるためです。さらに、分散非同期システムでは、複数のコンポーネントが同時に実行されるため、複数のエラーが同時に発生する可能性があります。これらのエラーは、タイプと深刻度が異なる可能性があり、適切な処理が必要になります。

非同期呼び出し後のリソースのクリーンアップも、簡単ではありません。同期コードとは異なり、呼び出しコードで try/catch/finally を使用してリソースをクリーンアップすることはできません。これは、最後のブロックの実行時に try ブロックで開始された作業がまだ進行中の可能性があるためです。

フレームワークが提供する機構では、分散非同期コードでのエラー処理が Java の try/catch/finally と類似しており、同じようにシンプルです。

```
ImageProcessingActivitiesClient activitiesClient
     = new ImageProcessingActivitiesClientImpl();

public void createThumbnail(final String webPageUrl) {

  new TryCatchFinally() {

    @Override
    protected void doTry() throws Throwable {
      List<String> images = getImageUrls(webPageUrl);
      for (String image: images) {
        Promise<String> localImage
            = activitiesClient.downloadImage(image);
        Promise<String> thumbnailFile
            = activitiesClient.createThumbnail(localImage);
        activitiesClient.uploadImage(thumbnailFile);
      }
    }

    @Override
    protected void doCatch(Throwable e) throws Throwable {

      // Handle exception and rethrow failures
      LoggingActivitiesClient logClient = new LoggingActivitiesClientImpl();
      logClient.reportError(e);
      throw new RuntimeException("Failed to process images", e);
    }

    @Override
    protected void doFinally() throws Throwable {
      activitiesClient.cleanUp();
    }
  };
}
```

`TryCatchFinally` クラスとそのバリアント (`TryFinally` と `TryCatch`) は、Java の `try`/`catch`/`finally` と同じように動作します。これを使用して、例外ハンドラをワークフローコードのブロックと関連付け、非同期のリモートタスクとして実行できます。`doTry()` メソッドは、論理的に `try` ブロックと同じです。フレームワークでは、`doTry()` のコードを自動的に実行します。`Promise` オブジェクトのリストを `TryCatchFinally` のコンストラクタに渡すことができます。コンストラクタに渡したすべての `Promise ` オブジェクトが準備完了状態になると、`doTry` メソッドが実行されます。`doTry()` 内から非同期的に呼び出されたコードによって例外がスローされると、`doTry()` で保留中の仕事がすべてキャンセルされ、`doCatch()` が呼び出されて例外が処理されます。たとえば、上のリスティングで、`downloadImage` から例外がスローされると、`createThumbnail` と `uploadImage` がキャンセルされます。最後に、すべての非同期の仕事が終了する (完了、失敗、またはキャンセルになる) と、`doFinally()` が呼び出されます。これは、リソースのクリーンアップに使用できます。必要に応じて、これらのクラスをネストすることもできます。

例外が `doCatch()` で報告されると、フレームワークでは、非同期呼び出しやリモート呼び出しを含む完全な論理コールスタックを提供します。これはデバッグするときに便利です。特に、非同期メソッドから他の非同期メソッドを呼び出している場合に役立ちます。たとえば、downloadImage からは、次のような例外が生成されます。

```
RuntimeException: error downloading image
  at downloadImage(Main.java:35)
  at ---continuation---.(repeated:1)
  at errorHandlingAsync$1.doTry(Main.java:24)
  at ---continuation---.(repeated:1)
…
```

## TryCatchFinally のセマンティクス
<a name="errorhandling.trycatchfinally"></a>

 AWS Flow Framework for Java プログラムの実行は、ブランチを同時に実行するツリーとして視覚化できます。非同期メソッド、アクティビティ、および `TryCatchFinally` 自体への呼び出しにより、この実行ツリーに新しいブランチが作成されます。たとえば、イメージ処理ワークフローは、次の図に示すツリーとして表示できます。

![\[非同期実行ツリー\]](http://docs.aws.amazon.com/ja_jp/amazonswf/latest/awsflowguide/images/trycatchfinally.png)


特定の実行ブランチでのエラーに伴って、そのブランチのアンワインドが生じます。例外に伴って、Java プログラムのコールスタックのアンワインドが生じるのと同じです。アンワインドに伴って、エラーが処理されるか、ツリーのルートに達してワークフロー実行が終了するまで、実行ブランチが上に移動し続けます。

フレームワークでは、タスクの処理中に発生したエラーを例外として報告します。`TryCatchFinally` で定義した例外ハンドラ (`doCatch()` メソッド) は、対応する `doTry()` のコードで作成されたすべてのタスクに関連付けられます。タスクが (タイムアウトや未処理の例外など) 失敗すると、該当する例外がスローされ、これを処理するために対応する `doCatch()` が呼び出されます。これを達成するために、フレームワークでは Amazon SWF と連携してリモートエラーを伝播し、呼び出し元のコンテキストで例外を再現します。

## キャンセル
<a name="test.cancellation.resources"></a>

同期コードで例外が発生すると、制御は `catch` ブロックに直接ジャンプし、`try` ブロックの残存コードをスキップします。例: 

```
try {
    a();
    b();
    c();
}
catch (Exception e) {
    e.printStackTrace();
}
```

このコードでは、`b()` で例外がスローされると、`c()` はまったく呼び出されません。これを次のワークフローと比較します。

```
new TryCatch() {

    @Override
    protected void doTry() throws Throwable {
        activityA();
        activityB();
        activityC();
    }

    @Override
    protected void doCatch(Throwable e) throws Throwable {
        e.printStackTrace();
    }
};
```

この場合、`activityA`、`activityB`、および `activityC` に対する呼び出しのすべが正常に戻り、3 つのタスクが作成されて非同期に実行されます。後で、`activityB` のタスクがエラーになったとします。このエラーは Amazon SWF によって履歴に記録されます。これを処理するために、フレームワークでは、まず同じ `doTry()` のスコープに属する他のすべてのタスク (この例では `activityA` と `activityC`) をキャンセルしようとします。すべての該当するタスクが完了 (キャンセル、失敗、または正常に終了) すると、このエラーを処理するために適切な `doCatch()` メソッドが呼び出されます。

同期の例 (`c()` がまったく実行されない) とは異なり、`activityC` が呼び出されてタスクの実行がスケジュールされています。したがって、フレームワークではこれをキャンセルしようとします。ただし、キャンセルされる保証はありません。キャンセルが保証されない理由としては、アクティビティが完了済みであるか、キャンセルリクエストが無視されるか、エラーで失敗することが考えられます。ただし、フレームワークでは、対応する `doTry()` で開始されたすべてのタスクが完了した後でのみ `doCatch()` を呼び出すことを保証します。また、`doTry()` と `doCatch()` で開始されたすべてのタスクが完了した後でのみ `doFinally()` を呼び出すことを保証します。例えば、上の例でアクティビティが相互に依存している場合 (`activityB` が `activityA` に依存し、`activityC` が `activityB` に依存している場合など)、`activityC` のキャンセルは `activityB` が完了するまで Amazon SWF でスケジュールされないため、すぐに行われます。

```
new TryCatch() {

    @Override
    protected void doTry() throws Throwable {
        Promise<Void> a = activityA();
        Promise<Void> b = activityB(a);
        activityC(b);
    }

    @Override
    protected void doCatch(Throwable e) throws Throwable {
        e.printStackTrace();
    }
};
```

### アクティビティのハートビート
<a name="errorhandling.activity.heartbeat"></a>

 AWS Flow Framework for Java の協調キャンセルメカニズムにより、実行中のアクティビティタスクを適切にキャンセルできます。キャンセルがトリガーされると、ワーカーをブロックしたタスクや、ワーカーへの割り当てを待機しているタスクは自動的にキャンセルされます。ただし、タスクがワーカーに割り当て済みである場合、フレームワークはアクティビティにキャンセルをリクエストします。アクティビティ実装では、このようなキャンセルリクエストを明示的に処理する必要があります。これを行うには、アクティビティのハートビートを報告します。

ハートビートを報告することで、アクティビティ実装では、進行中のアクティビティタスクの進行状況を報告できます。これはモニタリングに役立ちます。また、アクティビティでは、キャンセルリクエストを確認できます。`recordActivityHeartbeat` メソッドは、キャンセルがリクエストされると、`CancellationException` をスローします。アクティビティ実装では、この例外をキャッチし、キャンセルリクエストに対応します。または、キャンセルリクエストを無視して、例外を ”飲み込み” ます。キャンセルリクエストに対応する場合、アクティビティでは、必要に応じて目的のクリーンアップを実施し、`CancellationException` を再スローします。この例外がアクティビティ実装からスローされると、アクティビティタスクはキャンセル済みで完了したものとしてフレームワークに記録されます。

次の例は、イメージをダウンロードして処理するアクティビティを示しています。各イメージの処理後にハートビートを送信します。キャンセルがリクエストされると、クリーンアップを行い、例外を再スローしてキャンセルを確認します。

```
@Override
public void processImages(List<String> urls) {
    int imageCounter = 0;
    for (String url: urls) {
        imageCounter++;
        Image image = download(url);
        process(image);
        try {
            ActivityExecutionContext context
                 = contextProvider.getActivityExecutionContext();
            context.recordActivityHeartbeat(Integer.toString(imageCounter));
        } catch(CancellationException ex) {
            cleanDownloadFolder();
            throw ex;
        }
    }
}
```

アクティビティのハートビートを報告することは必須ではありませんが、アクティビティの実行時間が長引いている場合や、高価な操作の実行中にエラーが発生したときにキャンセルする場合には推奨されます。アクティビティ実装から `heartbeatActivityTask` を定期的に呼び出す必要があります。

アクティビティがタイムアウトすると、`ActivityTaskTimedOutException` がスローされ、例外オブジェクトの `getDetails` からデータが返されます。これは、対応するアクティビティタスクの `heartbeatActivityTask` に対する最後の正常な呼び出しに渡されたデータです。ワークフロー実装では、この情報に基づいてアクティビティタスクがタイムアウトするまでの進行状況を判断できます。

**注記**  
ハートビートの送信が多すぎると、Amazon SWF でハートビートリクエストがスロットリングされるため、調整する必要があります。Amazon SWF の制限については、「[Amazon Simple Workflow Service Developer Guide](https://docs.aws.amazon.com/amazonswf/latest/developerguide/)」(Amazon Simple Workflow Service デベロッパーガイド) を参照してください。

### タスクの明示的なキャンセル
<a name="errorhandling.canceltask"></a>

エラー条件とは別に、タスクを明示的にキャンセルする場合もあります。たとえば、クレジットカードで支払いを処理するアクティビティは、ユーザーが注文を取り消した場合に、キャンセルが必要になることがあります。フレームワークでは、`TryCatchFinally` のスコープで作成されたタスクを明示的にキャンセルできます。次の例では、支払いの処理中にシグナルを受信すると、支払いタスクがキャンセルされます。

```
public class OrderProcessorImpl implements OrderProcessor {
    private PaymentProcessorClientFactory factory
        = new PaymentProcessorClientFactoryImpl();
    boolean processingPayment = false;
    private TryCatchFinally paymentTask = null;

    @Override
    public void processOrder(int orderId, final float amount) {
        paymentTask = new TryCatchFinally() {

            @Override
            protected void doTry() throws Throwable {
                processingPayment = true;

                PaymentProcessorClient paymentClient = factory.getClient();
                paymentClient.processPayment(amount);
            }

            @Override
            protected void doCatch(Throwable e) throws Throwable {
                if (e instanceof CancellationException) {
                    paymentClient.log("Payment canceled.");
                } else {
                    throw e;
                }
            }

            @Override
            protected void doFinally() throws Throwable {
                processingPayment = false;
            }
        };

    }

    @Override
    public void cancelPayment() {
        if (processingPayment) {
            paymentTask.cancel(null);
        }
    }
}
```

### キャンセル済みタスクの通知の受信
<a name="errorhandling.canceltask.notification"></a>

タスクがキャンセル済みとして完了すると、フレームワークでは、`CancellationException` をスローしてワークフローロジックに通知します。アクティビティがキャンセル済みとして完了すると、レコードが履歴に作成され、フレームワークでは `CancellationException` を使用して適切な `doCatch()` を呼び出します。前の例で示したように、支払い処理タスクがキャンセルされ、ワークフローは `CancellationException` を受け取ります。

処理されない `CancellationException` は、他の例外と同じように、実行ブランチの上に伝播されます。ただし、`doCatch()` メソッドが `CancellationException` を受け取るのは、スコープに他の例外がない場合に限られます。他の例外があると、キャンセルより優先されます。

## ネストされた TryCatchFinally
<a name="errorhandling.nested"></a>

必要に応じて `TryCatchFinally` をネストできます。それぞれが実行ツリーに新しいブランチ`TryCatchFinally`を作成するため、ネストされたスコープを作成できます。親スコープで例外が発生すると、親スコープ内にネストされた `TryCatchFinally` で開始されたすべてのタスクに対してキャンセルが試行されます。ただし、ネストされた `TryCatchFinally` での例外は自動的に親に伝播されません。ネストされた `TryCatchFinally` の例外を親の `TryCatchFinally` に伝播する場合は、`doCatch()` で例外を再スローする必要があります。つまり、Java の `try`/`catch` と同じように、未処理の例外のみがバブルアップされます。キャンセルメソッドを呼び出してネストされた `TryCatchFinally` をキャンセルすると、ネストされた `TryCatchFinally` はキャンセルされますが、親の `TryCatchFinally` は自動的にキャンセルされません。

![\[ネストされた TryCatchFinally\]](http://docs.aws.amazon.com/ja_jp/amazonswf/latest/awsflowguide/images/nested.png)


```
new TryCatch() {
    @Override
    protected void doTry() throws Throwable {
        activityA();

        new TryCatch() {
            @Override
            protected void doTry() throws Throwable {
                activityB();
            }

            @Override
            protected void doCatch(Throwable e) throws Throwable {
                reportError(e);
            }
        };

        activityC();
    }

    @Override
    protected void doCatch(Throwable e) throws Throwable {
        reportError(e);
    }
};
```