

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

# Gestion des erreurs
<a name="errorhandling"></a>

**Topics**
+ [TryCatchFinally Sémantique](#errorhandling.trycatchfinally)
+ [Annulation](#test.cancellation.resources)
+ [Imbriqué TryCatchFinally](#errorhandling.nested)

Les blocs `try`/`catch`/`finally` intégrés à Java simplifient la gestion des erreurs et sont abondamment utilisés. Ils vous permettent d'associer des gestionnaires d'erreurs à un bloc de code. En interne, cela se concrétise par l'ajout de métadonnées supplémentaires sur les gestionnaires d'erreurs dans la pile d'appel. Lorsqu'une exception est levée, l'environnement d'exécution recherche dans la pile d'appels un gestionnaire d'erreurs associé et l'appelle. S'il ne trouve aucun gestionnaire d'erreurs approprié, il propage l'exception dans la chaîne d'appel.

Cela fonctionne bien pour le code synchrone, mais la gestion des erreurs est asynchrone et les programmes distribués posent des problèmes supplémentaires. Comme un appel asynchrone est renvoyé immédiatement, l'appelant n'est pas dans la pile d'appels lorsque le code asynchrone s'exécute. Cela signifie que les exceptions non gérées dans le code asynchrone ne peuvent pas être gérées par l'appelant de façon classique. Généralement, les exceptions provenant du code asynchrone sont gérées en transmettant l'état d'erreur à un rappel qui est transmis à la méthode asynchrone. Sinon, si un élément `Future<?>` est utilisé, il signale une erreur lorsque vous tentez d'y accéder. Ce processus n'est pas idéal, car le code qui reçoit l'exception (le rappel ou le code qui utilise l'élément `Future<?>`) ne dispose pas du contexte de l'appel initial et peut ne pas être capable de gérer correctement l'exception. En outre, dans un système asynchrone distribué dont les composants s'exécutent simultanément, plusieurs erreurs peuvent se produire simultanément. Ces erreurs peuvent être de différents types et niveaux de gravité ; elles doivent donc être gérées de façon appropriée.

Le nettoyage de la ressource après un appel asynchrone est également difficile. Contrairement au code synchrone, vous ne pouvez pas utiliser try/catch/finally le code d'appel pour nettoyer les ressources, car le travail initié dans le bloc try peut toujours être en cours lorsque le bloc final s'exécute.

Le framework fournit un mécanisme qui rend la gestion des erreurs dans le code asynchrone distribué similaire et presque aussi simple que celle de 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();
    }
  };
}
```

La classe `TryCatchFinally` et ses variantes, `TryFinally` et `TryCatch`, fonctionnent de façon similaire à l'ensemble de blocs Java `try`/`catch`/`finally`. Elle vous permet d'associer des gestionnaires d'exceptions à des blocs de code de flux de travail qui peuvent s'exécuter sous forme de tâches asynchrones et distantes. La méthode `doTry()` est logiquement équivalente au bloc `try`. L'infrastructure exécute automatiquement le code dans `doTry()`. Une liste d'objets `Promise` peut être transmise au constructeur de `TryCatchFinally`. La méthode `doTry` est exécutée lorsque tous les objets `Promise `transmis au constructeur sont prêts. Si une exception est levée par le code qui a été appelé de façon asynchrone à partir de `doTry()`, tout travail en attente dans `doTry()` est annulé et `doCatch()` est appelé pour gérer l'exception. Par exemple, dans la liste ci-dessus, si `downloadImage` lève une exception, `createThumbnail` et `uploadImage` sont annulés. Enfin, `doFinally()` est appelé lorsque tous les travaux asynchrones sont terminés (terminés avec succès, en échec ou annulés). Il peut être utilisé pour le nettoyage des ressources. Vous pouvez également imbriquer ces classes en fonction de vos besoins.

Lorsqu'une exception est signalée dans `doCatch()`, l'infrastructure fournit une pile d'appels logique complète qui inclut les appels asynchrones et les appels distants. Cela peut être utile pour le débogage, en particulier si des méthodes asynchrones appellent d'autres méthodes asynchrones. Par exemple, une exception provenant de downloadImage générera une exception similaire à la suivante :

```
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 Sémantique
<a name="errorhandling.trycatchfinally"></a>

L'exécution d'un programme AWS Flow Framework pour Java peut être visualisée sous la forme d'un arbre de branches s'exécutant simultanément. Un appel à une méthode asynchrone, une activité et l'élément `TryCatchFinally` lui-même créent une nouvelle branche dans cette arborescence d'exécution. Par exemple, le flux de travail de traitement d'image peut être représenté sous la forme de l'arborescence présentée dans le schéma suivant :

![\[Arborescence d'exécution asynchrone\]](http://docs.aws.amazon.com/fr_fr/amazonswf/latest/awsflowguide/images/trycatchfinally.png)


Une erreur dans une branche d'exécution provoque le déroulement de cette branche, tout comme une exception provoque le déroulement de la pile d'appels dans un programme Java. Le déroulement poursuit sa remontée dans la branche d'exécution jusqu'à ce que l'erreur soit résolue ou que la racine de l'arborescence soit atteinte, auquel cas l'exécution du flux de travail est terminée.

L'infrastructure signale les erreurs qui se produisent tout en procédant au traitement des tâches sous la forme d'exceptions. Elle associe les gestionnaires d'exceptions (méthodes `doCatch()`) définis dans `TryCatchFinally` à toutes les tâches qui sont créées par le code dans l'élément `doTry()` correspondant. Si une tâche échoue, par exemple en raison d'un délai d'attente ou d'une exception non gérée, l'exception appropriée sera levée et la correspondante `doCatch()` sera invoquée pour la gérer. Pour ce faire, le framework fonctionne en tandem avec Amazon SWF pour propager les erreurs distantes et les ressusciter sous forme d'exceptions dans le contexte de l'appelant.

## Annulation
<a name="test.cancellation.resources"></a>

Lorsqu'une exception se produit dans du code synchrone, le contrôle est directement passé au bloc `catch`, en omettant tout code restant dans le bloc `try`. Par exemple : 

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

Dans ce code, si `b()` lève une exception, `c()` n'est jamais appelé. Comparons cela à un flux de travail :

```
new TryCatch() {

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

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

Dans ce cas, les appels à `activityA`, `activityB` et `activityC` renvoient tous des données avec succès et entraînent la création de trois tâches qui seront exécutées de manière asynchrone. Supposons qu'ultérieurement, la tâche associée à `activityB` engendre une erreur. Cette erreur est enregistrée dans l'historique par Amazon SWF. Pour gérer cela, l'infrastructure tente tout d'abord d'annuler toutes les autres tâches qui ont pour origine le même élément `doTry()` ; dans le cas présent, `activityA` et `activityC`. Lorsque toutes ces tâches sont terminées (annulées, en échec ou exécutées avec succès), la méthode `doCatch()` appropriée est invoquée pour gérer l'erreur.

Contrairement à l'exemple du code synchrone, où `c()` n'a jamais été exécuté, `activityC` a été appelé et une tâche a été programmée pour être exécutée ; l'infrastructure va donc tenter de l'annuler, mais rien ne garantit qu'elle sera annulée. Cette annulation ne peut pas être garantie car l'activité peut être déjà exécutée et terminée, peut ignorer la demande d'annulation ou peut échouer en raison d'une erreur. Toutefois, l'infrastructure garantit que `doCatch()` n'est appelé qu'une fois que toutes les tâches qui ont démarré à partir de l'élément `doTry()` correspondant sont terminées. Elle garantit également que `doFinally()` n'est appelé qu'une fois que toutes les tâches démarrées à partir de `doTry()` et `doCatch()` sont terminées. Si, par exemple, les activités décrites dans l'exemple ci-dessus dépendent les unes des autres, disons `activityB` dépendent de `activityA` et `activityC` de`activityB`, l'annulation `activityC` sera immédiate car elle n'est planifiée dans Amazon SWF qu'`activityB`une fois terminée :

```
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();
    }
};
```

### Pulsations de l'activité
<a name="errorhandling.activity.heartbeat"></a>

Le mécanisme d'annulation coopératif de AWS Flow Framework for Java permet d'annuler facilement les tâches liées aux activités en vol. Lorsque l'annulation est déclenchée, les tâches qui sont bloquées ou qui attendent d'être affectées à un exécuteur sont automatiquement annulées. Toutefois, si une tâche est déjà affectée à un exécuteur, l'infrastructure demandera à l'activité de l'annuler. L'implémentation de votre activité doit gérer explicitement ce type de demandes d'annulation. Pour cela, un rapport sur les pulsations de votre activité est émis.

Le fait d'émettre un rapport sur les pulsations permet à l'implémentation d'activité de signaler la progression d'une tâche d'activité en cours, ce qui est utile pour la surveillance et permet à l'activité de détecter les demandes d'annulation. La méthode `recordActivityHeartbeat` lève une exception `CancellationException` si une annulation a été demandée. L'implémentation d'activité peut intercepter cette exception et agir sur la demande d'annulation ou ignorer la demande en digérant l'exception. Pour honorer la demande d'annulation, l'activité doit effectuer le nettoyage souhaité, le cas échéant, puis renvoyer une `CancellationException`. Lorsque cette exception est levée à partir de l'implémentation d'une activité, l'infrastructure enregistre que cette tâche d'activité s'est terminée à l'état annulé.

L'exemple suivant montre une activité qui télécharge et traite des images. Les pulsations varient après le traitement de chaque image et, si l'annulation est demandée, l'activité supprime puis lève à nouveau l'exception pour accuser réception de l'annulation.

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

L'émission d'un rapport sur les pulsations de l'activité n'est pas obligatoire, mais elle est recommandée si votre activité s'exécute sur une longue durée ou exécute des opérations onéreuses que vous souhaitez annuler en cas d'erreur. Vous devez appeler `heartbeatActivityTask` périodiquement à partir de l'implémentation de l'activité.

Si l'activité dépasse le délai d'attente qui lui est imparti, l'exception `ActivityTaskTimedOutException` est levée et l'élément `getDetails` lancé sur l'objet d'exception renvoie les données transmises au dernier appel à `heartbeatActivityTask` ayant abouti pour la tâche d'activité correspondante. L'implémentation de flux de travail peut utiliser ces informations pour déterminer le niveau de progression atteint au moment où la tâche d'activité a dépassé le délai qui lui était imparti.

**Note**  
Il n'est pas recommandé de battre trop fréquemment, car Amazon SWF peut ralentir les demandes de pulsation. Consultez le [guide du développeur Amazon Simple Workflow Service](https://docs.aws.amazon.com/amazonswf/latest/developerguide/) pour connaître les limites fixées par Amazon SWF.

### Annulation explicite d'une tâche
<a name="errorhandling.canceltask"></a>

Outre les conditions d'erreur, il existe d'autres cas où vous pouvez être amené à annuler explicitement une tâche. Par exemple, une activité de traitement des règlements à l'aide d'une carte de crédit peut nécessiter une annulation si l'utilisateur annule sa demande. L'infrastructure vous permet d'annuler explicitement des tâches créées dans un bloc `TryCatchFinally`. Dans l'exemple suivant, la tâche de règlement est annulée si un signal est reçu pendant le traitement du règlement.

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

### Réception d'une notification des tâches annulées
<a name="errorhandling.canceltask.notification"></a>

Lorsqu'une tâche se termine à l'état annulé, l'infrastructure informe la logique de flux de travail en levant une exception `CancellationException`. Lorsqu'une activité se termine à l'état annulé, un enregistrement est créé dans l'historique et l'infrastructure appelle la méthode `doCatch()` appropriée avec une exception `CancellationException`. Comme décrit dans l'exemple précédent, lorsque la tâche de traitement du règlement est annulée, le flux de travail reçoit une exception `CancellationException`. 

Une exception `CancellationException` non résolue est propagée dans la branche d'exécution, comme c'est le cas pour toute autre exception. Toutefois, la méthode `doCatch()` ne reçoit l'exception `CancellationException` que s'il n'y a aucune autre exception dans la portée ; les autres exceptions ont une priorité supérieure à celle de l'annulation. 

## Imbriqué TryCatchFinally
<a name="errorhandling.nested"></a>

Vous pouvez imbriquer les blocs `TryCatchFinally` en fonction de vos besoins. Comme chacune `TryCatchFinally` crée une nouvelle branche dans l'arbre d'exécution, vous pouvez créer des étendues imbriquées. Les exceptions de la portée parent provoquent des tentatives d'annulation de toutes les tâches initiées par les blocs `TryCatchFinally` imbriqués qu'elle contient. Toutefois, les exceptions présentes dans un bloc `TryCatchFinally` imbriqué ne se propagent pas automatiquement vers le parent. Si vous souhaitez propager une exception d'un bloc `TryCatchFinally` imbriqué vers le bloc `TryCatchFinally` dans lequel il est imbriqué, vous devez lever à nouveau l'exception dans `doCatch()`. En d'autres termes, seules les exceptions non résolues sont remontées, tout comme avec les éléments Java `try`/`catch`. Si vous annulez un bloc `TryCatchFinally` imbriqué en appelant la méthode cancel, le bloc `TryCatchFinally` imbriqué est annulé, mais le bloc `TryCatchFinally` dans lequel il est imbriqué n'est pas automatiquement annulé.

![\[Imbriqué TryCatchFinally\]](http://docs.aws.amazon.com/fr_fr/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);
    }
};
```