Compartilhar via


Lidando com erros nas Funções Duráveis (Azure Functions)

As orquestrações de Funções Duráveis são implementadas no código e podem usar os recursos internos de tratamento de erros da linguagem de programação. Não há novos conceitos que você precise aprender para adicionar a compensação e o tratamento de erro às suas orquestrações. No entanto, há alguns comportamentos a que você deve estar atento.

Observação

A versão 4 do modelo de programação Node.js para o Azure Functions está disponível para o público geral. O novo modelo v4 foi projetado para oferecer uma experiência mais flexível e intuitiva para desenvolvedores JavaScript e TypeScript. Saiba mais sobre as diferenças entre v3 e v4 no guia de migração.

Nos trechos de código a seguir, o JavaScript (PM4) denota o modelo de programação V4, a nova experiência.

Erros em funções de atividade e suborquestrações

No Durable Functions, exceções sem tratamento geradas dentro de funções de atividade ou suborquestrações são enviadas de volta para a função orquestradora usando tipos de exceção padronizados.

Por exemplo, considere a seguinte função de orquestrador que executa uma transferência de fundo entre duas contas:

Em processo C# do Durable Functions, exceções não tratadas são lançadas como FunctionFailedException.

A mensagem de exceção normalmente identifica quais funções de atividade ou sub-orquestrações causaram a falha. Para acessar informações de erro mais detalhadas, inspecione a InnerException propriedade.

[FunctionName("TransferFunds")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var transferDetails = context.GetInput<TransferOperation>();

    await context.CallActivityAsync("DebitAccount",
        new
        {
            Account = transferDetails.SourceAccount,
            Amount = transferDetails.Amount
        });

    try
    {
        await context.CallActivityAsync("CreditAccount",
            new
            {
                Account = transferDetails.DestinationAccount,
                Amount = transferDetails.Amount
            });
    }
    catch (FunctionFailedException)
    {
        // Refund the source account.
        // Another try/catch could be used here based on the needs of the application.
        await context.CallActivityAsync("CreditAccount",
            new
            {
                Account = transferDetails.SourceAccount,
                Amount = transferDetails.Amount
            });
    }
}

Observação

O exemplo de C# anterior é para o Durable Functions 2.x. No Durable Functions 1.x, use DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, confira o artigo Versões do Durable Functions.

Se a primeira chamada de função CreditAccount falhar, a função de orquestrador compensará creditando os fundos de volta à conta de origem.

Erros em funções de entidade

O comportamento de tratamento de exceção para funções de entidade difere com base no modelo de hospedagem do Durable Functions:

No Durable Functions usando o C# em processo, os tipos de exceção originais gerados por funções de entidade são retornados diretamente ao orquestrador.

[FunctionName("Function1")]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    try
    {
        var entityId = new EntityId(nameof(Counter), "myCounter");
        await context.CallEntityAsync(entityId, "Add", 1);
    }
    catch (Exception ex)
    {
        // The exception type will be InvalidOperationException with the message "this is an entity exception".
    }
    return string.Empty;
}

[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            throw new InvalidOperationException("this is an entity exception");
        case "get":
            ctx.Return(ctx.GetState<int>());
            break;
    }
}

Repetição automática em caso de falha

Quando chama funções de atividade ou funções de suborquestração, você pode especificar uma política de repetição automática. O exemplo a seguir tenta chamar uma função até três vezes e espera cinco segundos entre cada repetição:

[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var retryOptions = new RetryOptions(
        firstRetryInterval: TimeSpan.FromSeconds(5),
        maxNumberOfAttempts: 3);

    await context.CallActivityWithRetryAsync("FlakyFunction", retryOptions, null);

    // ...
}

Observação

O exemplo de C# anterior é para o Durable Functions 2.x. No Durable Functions 1.x, use DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, confira o artigo Versões do Durable Functions.

A chamada de função de atividade no exemplo anterior recebe um parâmetro para configurar uma política de nova tentativa automática. Há várias opções para personalizar a política de repetição automática:

  • Núm. máximo de tentativas: o número máximo de tentativas de repetição. Se definido como 1, não haverá nenhuma repetição.
  • Primeiro intervalo de repetição: o tempo de espera antes de fazer a primeira tentativa.
  • Coeficiente de retirada: o coeficiente usado para determinar a taxa de aumento de retirada. O valor padrão é 1.
  • Intervalo máx. de repetição: o tempo máximo de espera entre tentativas de repetição.
  • Tempo limite de repetição: o tempo máximo a ser dedicado às novas tentativas. O comportamento padrão é repetir indefinidamente.

Manipuladores de repetição personalizados

Ao usar o .NET ou o Java, você também tem a opção de implementar manipuladores de repetição no código. Isso é útil quando políticas declarativas de nova tentativa não são expressivas o suficiente. Para linguagens que não dão suporte a manipuladores de repetição personalizados, você ainda tem a opção de implementar políticas de repetição usando loops, tratamento de exceção e temporizadores para injetar atrasos entre novas tentativas.

RetryOptions retryOptions = new RetryOptions(
    firstRetryInterval: TimeSpan.FromSeconds(5),
    maxNumberOfAttempts: int.MaxValue)
    {
        Handle = exception =>
        {
            // True to handle and try again, false to not handle and throw.
            if (exception is TaskFailedException failure)
            {
                // Exceptions from TaskActivities are always this type. Inspect the
                // inner Exception to get more details.
            }

            return false;
        };
    }

await ctx.CallActivityWithRetryAsync("FlakeyActivity", retryOptions, null);

Tempos limite de função

Talvez você queira abandonar uma chamada de função dentro de uma função de orquestrador se ela estiver demorando muito para ser concluída. No momento, o modo adequado de fazer isso é criar um temporizador durável com “qualquer” seletor de tarefa, conforme mostrado no exemplo a seguir:

[FunctionName("TimerOrchestrator")]
public static async Task<bool> Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    TimeSpan timeout = TimeSpan.FromSeconds(30);
    DateTime deadline = context.CurrentUtcDateTime.Add(timeout);

    using (var cts = new CancellationTokenSource())
    {
        Task activityTask = context.CallActivityAsync("FlakyFunction");
        Task timeoutTask = context.CreateTimer(deadline, cts.Token);

        Task winner = await Task.WhenAny(activityTask, timeoutTask);
        if (winner == activityTask)
        {
            // success case
            cts.Cancel();
            return true;
        }
        else
        {
            // timeout case
            return false;
        }
    }
}

Observação

O exemplo de C# anterior é para o Durable Functions 2.x. No Durable Functions 1.x, use DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, confira o artigo Versões do Durable Functions.

Observação

Esse mecanismo não realmente encerra a execução de uma função de atividade em andamento. Em vez disso, ele simplesmente permite que a função de orquestrador ignore o resultado e prossiga. Para mais informações, confira a documentação dos Medidores de tempo.

Exceções sem tratamento

Se uma função de orquestrador falhar com uma exceção sem tratamento, os detalhes da exceção serão registrados e a instância será concluída com um status Failed.

Incluir propriedades de exceção personalizadas para FailureDetails (.NET Isolated)

Ao executar fluxos de trabalho do `Durable Task` no modelo isolado do .NET, as falhas de tarefa são automaticamente serializadas em um objeto FailureDetails. Por padrão, esse objeto inclui campos padrão, como:

  • ErrorType — o nome do tipo de exceção
  • Mensagem – a mensagem de exceção
  • StackTrace — o rastreamento de pilha serializado
  • InnerFailure – um objeto FailureDetails incorporado para exceções internas recursivas

Começando com Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0, você pode estender esse comportamento implementando um IExceptionPropertiesProvider (definido no pacote Microsoft.DurableTask.Worker a partir da v1.16.1). Esse provedor define quais tipos de exceção e quais de suas propriedades devem ser incluídas no dicionário FailureDetails.Properties.

Observação

  • Esse recurso está disponível somente no .NET Isolado . O suporte para Java será adicionado em uma versão futura.
  • Verifique se você está usando Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0 ou posterior.
  • Verifique se você está usando Microsoft.DurableTask.Worker v1.16.1 ou posterior.

Implementar um provedor de propriedades de exceção

Implemente um IExceptionPropertiesProvider personalizado para extrair e retornar propriedades selecionadas para as exceções relevantes. O dicionário retornado será serializado no campo Propriedades de FailureDetails quando um tipo de exceção correspondente for gerado.

using Microsoft.DurableTask.Worker;

public class CustomExceptionPropertiesProvider : IExceptionPropertiesProvider
{
    public IDictionary<string, object?>? GetExceptionProperties(Exception exception)
    {
        return exception switch
        {
            ArgumentOutOfRangeException e => new Dictionary<string, object?>
            {
                ["ParamName"] = e.ParamName,
                ["ActualValue"] = e.ActualValue
            },
            InvalidOperationException e => new Dictionary<string, object?>
            {
                ["CustomHint"] = "Invalid operation occurred",
                ["TimestampUtc"] = DateTime.UtcNow
            },
            _ => null // Other exception types not handled
        };
    }
}

Registrar o provedor

Registre seu IExceptionPropertiesProvider personalizado no host de trabalho isolado do .NET, normalmente em Program.cs:

using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.DependencyInjection;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(builder =>
    {
        // Register custom exception properties provider
        builder.Services.AddSingleton<IExceptionPropertiesProvider, CustomExceptionPropertiesProvider>();
    })
    .Build();

host.Run();

Depois de registrado, qualquer exceção que corresponda a um dos tipos manipulados incluirá automaticamente as propriedades configuradas em seu FailureDetails.

Exemplo de Saída de FailureDetails

Quando ocorre uma exceção que corresponde à configuração do provedor, a orquestração recebe uma estrutura FailureDetails serializada como esta:

{
  "errorType": "TaskFailedException",
  "message": "Activity failed with an exception.",
  "stackTrace": "...",
  "innerFailure": {
    "errorType": "ArgumentOutOfRangeException",
    "message": "Specified argument was out of range.",
    "properties": {
      "ParamName": "count",
      "ActualValue": 42
    }
  }
}

Próximas etapas