

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

# Gestión de errores
<a name="errorhandling"></a>

**Topics**
+ [TryCatchFinally Semántica](#errorhandling.trycatchfinally)
+ [Cancelación](#test.cancellation.resources)
+ [Anidado TryCatchFinally](#errorhandling.nested)

La construcción `try`/`catch`/`finally` en Java facilita el control de errores y se utiliza de manera generalizada. Le permite asociar controladores de errores a un bloque de código. A nivel interno, esto se produce introduciendo metadatos adicionales sobre los controladores de errores en la pila de llamadas. Cuando se genera una excepción, el tiempo de ejecución busca en la pila de llamadas un controlador de errores asociado para invocarlo y, si no encuentra ninguno adecuado, propaga la excepción hacia arriba en la cadena de llamadas.

Esto funciona correctamente en el caso de código sincrónico, pero controlar los errores en programas asíncronos y distribuidos es más complicado. Como una llamada asíncrona se devuelve inmediatamente, la persona que llama no está en la pila de llamadas cuando se ejecuta el código asíncrono. Esto significa que el intermediario no puede controlar de la manera habitual las excepciones no controladas en el código asíncrono. Normalmente, las excepciones que se generan en el código asíncrono se controlan transfiriendo un estado de error a una devolución de llamada que se transfiere al método asíncrono. Por otro lado, si se utiliza `Future<?>`, notificará un error cuando intente obtener acceso a él. No es la solución idónea, ya que el código que recibe la excepción (la devolución de llamada o el código que utiliza `Future<?>`) no tiene el contexto de la llamada original y quizás no pueda controlar la excepción correctamente. Además, en un sistema asíncrono distribuido, donde hay componentes que se ejecutan simultáneamente, se puede producir más de un error a la vez. Estos errores pueden ser de varios tipos y tener distintos niveles de gravedad, por lo que es preciso controlarlos de manera adecuada.

Tampoco es tarea fácil limpiar recursos después de una llamada asíncrona. A diferencia del código sincrónico, no se puede usar try/catch/finally en el código de llamada para limpiar recursos, ya que el trabajo iniciado en el bloque try puede continuar cuando se ejecute el bloque final.

El marco proporciona un mecanismo que hace que la gestión de errores en el código asíncrono distribuido sea similar y casi tan simple como la 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 clase `TryCatchFinally` y sus variantes, `TryFinally` y `TryCatch`, funcionan de un modo similar a `try`/`catch`/`finally` de Java. Al utilizarla puede asociar controladores de excepciones a bloques de código de flujo de trabajo que se pueden ejecutar como tareas asíncronas y remotas. El método `doTry()` es lógicamente equivalente al bloque `try`. El marco ejecuta automáticamente el código en `doTry()`. Se puede transferir una lista de objetos `Promise` al constructor de `TryCatchFinally`. El método `doTry` se ejecutará cuando estén listos todos los objetos `Promise `que se hayan transferido al constructor. Si genera una excepción mediante código invocado de manera asíncrona desde dentro de `doTry()`, las tareas pendientes que haya en `doTry()` se cancelan y se llama a `doCatch()` para controlar la excepción. Por ejemplo, en la lista anterior, si `downloadImage` genera una excepción, se cancelarán `createThumbnail` y `uploadImage`. Por último, se llama a `doFinally()` cuando se ha llevado a cabo todo el trabajo asíncrono (completado, erróneo o cancelado). Se puede utilizar para limpiar recursos. También es posible anidar estas clases en función de sus necesidades.

Cuando se notifica una excepción en `doCatch()`, el marco ofrece una pila de llamada lógica completa que incluye llamadas asíncronas y remotas. Esto puede resultar útil para el proceso de depuración, especialmente si hay métodos asíncronos que llaman a otros métodos asíncronos. Por ejemplo, una excepción de downloadImage generará una excepción de este tipo:

```
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 Semántica
<a name="errorhandling.trycatchfinally"></a>

La ejecución de un programa AWS Flow Framework para Java se puede visualizar como un árbol de ramas que se ejecutan simultáneamente. Si se llama a un método asíncrono, a una actividad y al propio `TryCatchFinally`, se crea una nueva rama en este árbol de ejecuciones. Por ejemplo, en la siguiente figura se puede ver el flujo de trabajo de procesamiento de imágenes en forma de árbol.

![\[Árbol de ejecución asíncrona\]](http://docs.aws.amazon.com/es_es/amazonswf/latest/awsflowguide/images/trycatchfinally.png)


Si se produce un error en una rama de ejecución, la rama volverá atrás, al igual que una excepción hace que la pila de la llamada vuelva hacia atrás en un programa de Java. El proceso de marcha atrás sigue avanzando por la rama de ejecución hasta que, bien se controla el error, o se llega a la raíz del árbol, en cuyo caso finalizaría la ejecución del flujo de trabajo.

El marco de trabajo notifica los errores que se producen al procesar las tareas como excepciones. Asocia los controladores de excepciones (métodos `doCatch()`) definidos en `TryCatchFinally` con todas las tareas que crea el código en el correspondiente `doTry()`. Si una tarea da error, por ejemplo, porque se agote el tiempo de espera o porque haya una excepción sin gestionar, se generará la excepción pertinente y se invocará al `doCatch()` correspondiente para gestionarla. Para ello, el marco de trabajo funciona de forma conjunta con Amazon SWF para propagar los errores remotos y recuperarlos como excepciones en el contexto de quien realiza la llamada.

## Cancelación
<a name="test.cancellation.resources"></a>

Cuando se produce una excepción en el código sincrónico, el control pasa directamente al bloque `catch`, omitiendo el resto del código en el bloque `try`. Por ejemplo: 

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

En este código, si `b()` genera una excepción, nunca se invocará a `c()`. Comparémoslo con un flujo de trabajo:

```
new TryCatch() {

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

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

En este caso, las llamadas a `activityA`, `activityB` y `activityC` devuelven todas valores correctamente y hacen que se creen tres tareas que se ejecutarán de forma asíncrona. Supongamos que posteriormente la tarea para `activityB` genera un error. Amazon SWF registra este error en el historial. Para controlar el error, en primer lugar el marco intentará cancelar el resto de las tareas que se originaron en el ámbito del mismo `doTry()` (en este caso, `activityA` y `activityC`). Una vez finalizadas todas estas tareas (canceladas, erróneas o completadas correctamente), se invocará al método `doCatch()` adecuado para controlar el error.

Al contrario que en el ejemplo del código sincrónico, donde nunca se ejecutó `c()`, se invocó a `activityC` y se programó una tarea para su ejecución. El marco intentará cancelarla, pero no hay garantías de que se cancele. No se puede garantizar que se cancele, porque puede que la actividad ya se haya completado, no haya tenido en cuenta la solicitud de cancelación o haya dado error. Sin embargo, el marco garantiza que se llame al método `doCatch()` únicamente cuando hayan finalizado las tareas iniciadas desde el correspondiente método `doTry()`. También garantiza que se llame al método `doFinally()` solo cuando hayan finalizado todas las tareas iniciadas desde los métodos `doTry()` y `doCatch()`. Si, por ejemplo, las actividades del ejemplo anterior dependen una de otra, supongamos que `activityB` depende de `activityA` y `activityC` de `activityB`, la cancelación de `activityC` será inmediata porque no se programará en Amazon SWF hasta que finalice `activityB`:

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

### Latido de actividades
<a name="errorhandling.activity.heartbeat"></a>

El mecanismo AWS Flow Framework de cancelación cooperativa de Java permite cancelar sin problemas las tareas que se realizan durante el vuelo. Cuando se pone en marcha una cancelación, se cancelan automáticamente las tareas que estaban bloqueadas o que estaban en espera de ser asignadas a un trabajo. Sin embargo, si ya se ha asignado la tarea a un trabajo, el marco solicitará que se cancele la actividad. La implementación de la actividad debe controlar de forma explícita estas solicitudes de cancelación. Esto se realiza a través de la notificación de los latidos de su actividad.

La notificación de los latidos permite a la implementación de la actividad notificar el progreso de una tarea de actividad en curso, lo que resulta muy útil para monitorizar y permite a la actividad comprobar la existencia de solicitudes de cancelación. El método `recordActivityHeartbeat` generará una excepción `CancellationException` si se ha solicitado una cancelación. La implementación de la actividad puede detectar esta excepción y actuar según la solicitud de cancelación, o bien puede no tener en cuenta la solicitud integrando la excepción. Para respetar la solicitud de cancelación, la actividad debe realizar la limpieza deseada, si la hubiese, y volver a generar la excepción `CancellationException`. Cuando se genera esta excepción desde la implementación de una actividad, el marco registra que la tarea de actividad ha finalizado con el estado de cancelada.

En el ejemplo siguiente se muestra una actividad que descarga y procesa imágenes. Produce latidos después de procesar cada una de las imágenes y, si se solicita una cancelación, limpia y vuelve a generar la excepción para que se confirme la cancelación.

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

Notificar los latidos de las actividades no es obligatorio, pero se recomienda hacerlo si su actividad se ejecuta durante mucho tiempo o si va a realizar operaciones exhaustivas que desearía cancelar en caso de error. Debería llamar a `heartbeatActivityTask` periódicamente desde la implementación de la actividad.

Si se agota el tiempo de espera de la actividad, se generará la excepción `ActivityTaskTimedOutException`, y el método `getDetails` en el objeto de la excepción devolverá los datos transferidos a la última llamada a `heartbeatActivityTask` realizada correctamente para la correspondiente tarea de actividad. La implementación del flujo de trabajo puede utilizar esta información para determinar los avances realizados antes de que se agotase el tiempo de espera de la tarea de actividad.

**nota**  
No es conveniente aplicar latidos con excesiva frecuencia, ya que Amazon SWF podría limitar las solicitudes de latidos. Consulte la [Guía para desarrolladores de Amazon Simple Workflow Service](https://docs.aws.amazon.com/amazonswf/latest/developerguide/) para conocer los límites establecidos en Amazon SWF.

### Cancelación explícita de una tarea
<a name="errorhandling.canceltask"></a>

Además de las condiciones de error, hay otros casos en que es preciso cancelar de forma explícita una tarea. Por ejemplo, es posible que sea necesario cancelar una actividad para procesar pagos mediante tarjeta de crédito si el usuario cancela la orden. El marco le permite cancelar explícitamente las tareas creadas en el ámbito de un método `TryCatchFinally`. En el siguiente ejemplo se cancela una tarea de pago cuando se recibe una señal mientras se procesa el pago.

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

### Recepción de notificaciones de tareas canceladas
<a name="errorhandling.canceltask.notification"></a>

Cuando una tarea finaliza con estado de cancelada, el marco informa a la lógica del flujo de trabajo generando una excepción `CancellationException`. Cuando una actividad se completa con estado de cancelada, se crea un registro en el historial y el marco llama al método `doCatch()` adecuado con una excepción `CancellationException`. Tal como se muestra en el ejemplo anterior, cuando se cancela una tarea de procesamiento de pagos, el flujo de trabajo recibe una excepción `CancellationException`. 

Una excepción `CancellationException` no controlada se propaga hasta la rama de ejecución, como cualquier otra excepción. Sin embargo, el método `doCatch()` recibirá la excepción `CancellationException` únicamente si no hay otra excepción en ese ámbito, dado que hay otras excepciones con mayor prioridad que la cancelación. 

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

Puede anidar el método `TryCatchFinally` para adaptarlo a sus necesidades. Como cada uno de ellos `TryCatchFinally` crea una nueva rama en el árbol de ejecución, puede crear ámbitos anidados. Las excepciones en el ámbito principal producirán intentos de cancelación de todas las tareas iniciadas por el método `TryCatchFinally` anidado en su interior. Sin embargo, las excepciones contenidas en un método `TryCatchFinally` anidado no se propagan automáticamente al principal. Si desea propagar una excepción desde un `TryCatchFinally` anidado al método `TryCatchFinally` que lo contiene, deberá volver a generar la excepción en `doCatch()`. Dicho de otro modo, solo se desarrollan las excepciones no controladas, igual que `try`/`catch` en Java. Si cancela un `TryCatchFinally` anidado llamando al método de cancelación, se cancelará el `TryCatchFinally` anidado, pero el `TryCatchFinally` que lo contiene no se cancelará automáticamente.

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