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
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. Étant donné qu'un appel asynchrone renvoie des données immédiatement, l'appelant n'est pas dans la pile d'appels lorsque le code asynchrone est exécuté. 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 à ce qui se fait avec le code synchrone, vous ne pouvez pas utiliser les blocs try/catch/finally dans le code d'appel pour nettoyer les ressources, car le travail initié dans le bloc try peut encore être en cours d'exécution lorsque le bloc finally s'exécute.
L'infrastructure fournit un mécanisme qui rend la gestion des erreurs en code asynchrone distribué semblable à, et presque aussi simple que, l'ensemble de blocs 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) …
Sémantique TryCatchFinally
L'exécution d'unAWS Flow Frameworkpour le programme Java peut être visualisé sous la forme d'une arborescence de branches s'exécutant de façon simultanée. 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 :
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. En cas d'échec d'une tâche (par exemple, à la suite d'un dépassement de délai d'attente ou d'une exception non réglée), l'exception correspondante est levée et l'élément correspondant est levé.doCatch()
sera invoqué pour le gérer. Pour effectuer cette opération, l'infrastructure fonctionne en tandem avec Amazon SWF pour propager les erreurs distantes et les relancer sous forme d'exceptions dans le contexte de l'appelant.
Annulation
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 de l'exemple précédent dépendent les unes des autres, disons :activityB
dépend deactivityA
etactivityC
suractivityB
, puis l'annulation deactivityC
sera immédiat car il n'est pas programmé dans Amazon SWF avantactivityB
complète :
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é
LeAWS Flow Frameworkpour le mécanisme d'annulation coopérative de Java permet d'annuler de façon élégante les tâches d'activité en cours. 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 conseillé d'utiliser trop fréquemment la technique de rapport des pulsations, car Amazon SWF peut limiter les demandes de pulsations. ConsultezGuide du développeur Amazon Simple Workflow Servicepour les limites placés par Amazon SWF.
Annulation explicite d'une tâche
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
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.
TryCatchFinally imbriqué
Vous pouvez imbriquer les blocs TryCatchFinally
en fonction de vos besoins. Étant donné que chaque bloc TryCatchFinally
crée une nouvelle branche dans l'arborescence d'exécution, vous pouvez créer des portées 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é.
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); } };