Partager via


Gestion des erreurs dans Fonctions durables (Azure Functions)

Les orchestrations de fonctions durables sont implémentées dans du code et peuvent utiliser les fonctionnalités de gestion des erreurs intégrées dans le langage de programmation. Vous n’avez pas vraiment besoin d’apprendre de nouveaux concepts pour ajouter la gestion des erreurs et les compensations dans vos orchestrations. Toutefois, vous devez tenir compte de certains comportements.

Remarque

La version 4 du modèle de programmation Node.js pour Azure Functions est en disponibilité générale. Le modèle v4 est conçu pour offrir une expérience plus flexible et intuitive pour les développeurs JavaScript et TypeScript. Pour plus d’informations sur les différences entre v3 et v4, consultez le guide de migration.

Dans les extraits de code suivants, JavaScript (PM4) désigne le modèle de programmation v4, la nouvelle expérience.

Erreurs dans les fonctions d’activité et les sous-orchestrations

Dans Durable Functions, les exceptions non gérées levées dans les fonctions d’activité ou les sous-orchestrations sont transmises à la fonction d’orchestrateur en utilisant des types d’exception standardisés.

Par exemple, considérez la fonction d’orchestrateur suivante qui effectue un transfert de fonds entre deux comptes :

Dans Durable Functions C# in-process, les exceptions non gérées sont levées en tant que FunctionFailedException.

Le message d’exception identifie généralement les fonctions d’activité ou les sous-orchestrations qui ont provoqué l’échec. Pour accéder à des informations d’erreur plus détaillées, inspectez la InnerException propriété.

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

Remarque

Les exemples C# précédents portent sur Durable Functions 2.x. Pour Durable Functions 1.x, vous devez utiliser DurableOrchestrationContext au lieu de IDurableOrchestrationContext. Pour en savoir plus sur les différences entre les versions, consultez l’article Versions de Durable Functions.

Si le premier appel à la fonction CreditAccount échoue, la fonction d’orchestrateur compense en recréditant les fonds sur le compte source.

Erreurs dans les fonctions d’entité

Le comportement de gestion des exceptions pour les fonctions d’entité diffère en fonction du modèle d’hébergement Durable Functions :

Dans Durable Functions avec C# in-process, les types d’exceptions d’origine générées par les fonctions d’entité sont directement retournés à l’orchestrateur.

[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;
    }
}

Nouvelle tentative automatique en cas d’échec

Lorsque vous appelez les fonctions d’activité ou les fonctions de sous-orchestration, vous pouvez spécifier une stratégie de nouvelle tentative automatique. L’exemple suivant tente d’appeler une fonction jusqu’à trois fois, et attend 5 secondes avant chaque nouvelle tentative :

[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);

    // ...
}

Remarque

Les exemples C# précédents portent sur Durable Functions 2.x. Pour Durable Functions 1.x, vous devez utiliser DurableOrchestrationContext au lieu de IDurableOrchestrationContext. Pour en savoir plus sur les différences entre les versions, consultez l’article Versions de Durable Functions.

L'appel de fonction de l'activité de l'exemple précédent utilise un paramètre pour configurer une stratégie de nouvelles tentatives automatiques. Il existe plusieurs options de personnalisation de la stratégie de nouvelle tentative automatique :

  • Nombre maximal de tentatives: le nombre maximal de tentatives. Si la valeur est 1, aucune nouvelle tentative ne se produit.
  • Intervalle avant première nouvelle tentative : temps d’attente avant la première nouvelle tentative.
  • Coefficient d’interruption : coefficient permettant de déterminer le taux d’augmentation de l’interruption. La valeur par défaut est de 1.
  • Intervalle maximal entre deux nouvelles tentatives : durée maximale de l’attente entre deux nouvelles tentatives.
  • Délai de nouvelle tentative : temps que passe le système à effectuer de nouvelles tentatives. Par défaut, le système effectue ces nouvelles tentatives indéfiniment.

Descripteurs de nouvelle tentative personnalisés

Quand vous utilisez .NET ou Java, vous avez également la possibilité d’implémenter des descripteurs de nouvelle tentative dans le code. Cela est utile lorsque les politiques de réessai déclaratives ne sont pas suffisamment expressives. Pour les langages qui ne prennent pas en charge les descripteurs de nouvelle tentative personnalisés, vous avez toujours la possibilité d’implémenter des stratégies de nouvelle tentative à l’aide de boucles, de gestion des exceptions et de retardateurs pour injecter des retards entre les nouvelles tentatives.

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

Délais d’expiration des fonctions

Vous souhaiterez peut-être abandonner un appel de fonction au sein d’une fonction d’orchestrateur s’il prend trop de temps. Actuellement, la méthode adéquate pour cela consiste à créer un retardateur durable avec un sélecteur de tâche « any », comme dans l’exemple suivant :

[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;
        }
    }
}

Remarque

Les exemples C# précédents portent sur Durable Functions 2.x. Pour Durable Functions 1.x, vous devez utiliser DurableOrchestrationContext au lieu de IDurableOrchestrationContext. Pour en savoir plus sur les différences entre les versions, consultez l’article Versions de Durable Functions.

Remarque

Ce mécanisme ne termine pas réellement l’exécution de la fonction d’activité en cours. Il permet simplement à la fonction de l’orchestrateur d’ignorer le résultat et de continuer. Pour plus d’informations, voir la documentation Minuteurs.

Exceptions non prises en charge

Si une fonction d’orchestration échoue avec une exception non prise en charge, les détails de cette dernière sont enregistrés et l’instance s’exécute avec un état Failed.

Inclure des propriétés d’exception personnalisées pour FailureDetails (isolé (.NET))

Lors de l’exécution de flux de travail Durable Task dans le modèle isolé .NET, les échecs de tâche sont automatiquement sérialisés dans un objet FailureDetails. Par défaut, cet objet inclut des champs standard tels que :

  • ErrorType : nom du type d’exception
  • Message : message d’exception
  • StackTrace : piste d'exécution sérialisée
  • InnerFailure : objet FailureDetails imbriqué pour les exceptions internes récursives

À compter de Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0, vous pouvez étendre ce comportement en implémentant un IExceptionPropertiesProvider (défini dans le package Microsoft.DurableTask.Worker à partir de la version 1.16.1). Ce fournisseur définit les types d’exception et les propriétés qui doivent être inclus dans le dictionnaire FailureDetails.Properties.

Remarque

  • Cette fonctionnalité est disponible uniquement dans .NET Isolé . La prise en charge de Java sera ajoutée dans une prochaine version.
  • Vérifiez que vous utilisez Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0 ou version ultérieure.
  • Vérifiez que vous utilisez Microsoft.DurableTask.Worker v1.16.1 ou version ultérieure.

Implémenter un fournisseur de propriétés d’exception

Implémentez un IExceptionPropertiesProvider personnalisé pour extraire et retourner les propriétés sélectionnées pour les exceptions dont vous vous souciez. Le dictionnaire retourné sera sérialisé dans le champ Propriétés de FailureDetails lorsqu’un type d’exception correspondant sera levé.

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

Inscrire le fournisseur

Inscrivez votre IExceptionPropertiesProvider personnalisé dans votre hôte de travailleur isolé .NET, généralement dans 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();

Une fois inscrite, toute exception qui correspond à l’un des types gérés inclut automatiquement les propriétés configurées dans ses FailureDetails.

Exemple de sortie FailureDetails

Lorsqu’une exception se produit qui correspond à la configuration de votre fournisseur, l’orchestration reçoit une structure FailureDetails sérialisée comme suit :

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

Étapes suivantes