Hantera fel i Durable Functions (Azure Functions)

Durable Function-orkestreringar implementeras i kod och kan använda programmeringsspråkets inbyggda funktioner för felhantering. Det finns egentligen inga nya begrepp som du behöver lära dig att lägga till felhantering och kompensation i orkestreringarna. Det finns dock några beteenden som du bör känna till.

Kommentar

Version 4 av Node.js programmeringsmodellen för Azure Functions är allmänt tillgänglig. Den nya v4-modellen är utformad för att ha en mer flexibel och intuitiv upplevelse för JavaScript- och TypeScript-utvecklare. Läs mer om skillnaderna mellan v3 och v4 i migreringsguiden.

I följande kodfragment anger JavaScript (PM4) programmeringsmodellen V4, den nya upplevelsen.

Felaktigheter i aktivitetsfunktioner och underorkestreringar

I Durable Functions konverteras ohanterade undantag som genereras i aktivitetsfunktioner eller underorkestreringar tillbaka till orkestreringsfunktionen med hjälp av standardiserade undantagstyper.

Tänk dig till exempel följande orchestrator-funktion som utför en fondöverföring mellan två konton:

I Durable Functions C# in-process genereras ohanterade undantag som FunctionFailedException.

Undantagsmeddelandet identifierar vanligtvis vilka aktivitetsfunktioner eller underorkestreringar som orsakade felet. Om du vill komma åt mer detaljerad felinformation kontrollerar du egenskapen InnerException .

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

Kommentar

De tidigare C#-exemplen är för Durable Functions 2.x. För Durable Functions 1.x måste du använda DurableOrchestrationContext i stället för IDurableOrchestrationContext. Mer information om skillnaderna mellan versioner finns i artikeln Durable Functions-versioner .

Om det första CreditAccount-funktionsanropet misslyckas kompenserar orkestreringsfunktionen genom att kreditera pengarna tillbaka till källkontot.

Fel i entitetsfunktioner

Undantagshanteringsbeteendet för entitetsfunktioner skiljer sig beroende på Durable Functions-värdmodellen.

I Durable Functions med C# i processen returneras de ursprungliga undantagstyper som genereras av entitetsfunktioner direkt till orkestratorn.

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

Automatiskt återförsök vid fel

När du anropar aktivitetsfunktioner eller underorkestreringsfunktioner kan du ange en princip för automatiskt återförsök. Följande exempel försöker anropa en funktion upp till tre gånger och väntar 5 sekunder mellan varje nytt försök:

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

    // ...
}

Kommentar

De tidigare C#-exemplen är för Durable Functions 2.x. För Durable Functions 1.x måste du använda DurableOrchestrationContext i stället för IDurableOrchestrationContext. Mer information om skillnaderna mellan versioner finns i artikeln Durable Functions-versioner .

Aktivitetsfunktionsanropet i föregående exempel tar en parameter för att konfigurera en automatisk återförsöksprincip. Det finns flera alternativ för att anpassa principen för automatiskt återförsök:

  • Maximalt antal försök: Det maximala antalet försök. Om värdet är 1 görs inget nytt försök.
  • Första återförsöksintervallet: Hur lång tid det tar att vänta innan det första återförsöket.
  • Backoff-koefficient: Den koefficient som används för att fastställa ökningshastigheten för backoff. Standardvärdet är 1.
  • Maximalt återförsöksintervall: Den maximala väntetiden mellan återförsök.
  • Tidsgräns för återförsök: Den maximala tid som du behöver ägna åt att göra återförsök. Standardbeteendet är att försöka igen på obestämd tid.

Anpassade återförsökshanterare

När du använder .NET eller Java kan du också implementera återförsökshanterare i kod. Detta är användbart när deklarativa återförsöksprinciper inte är tillräckligt uttrycksfulla. För språk som inte stöder anpassade återförsökshanterare har du fortfarande möjlighet att implementera återförsöksprinciper med hjälp av loopar, undantagshantering och timers för att mata in fördröjningar mellan återförsök.

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

Funktionstimeouter

Du kanske vill avbryta ett funktionsanrop i en orkestreringsfunktion om det tar för lång tid att slutföra. Det rätta sättet att göra detta i dag är genom att skapa en varaktig timer med en "valfri" uppgiftsväljare, som i följande exempel:

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

Kommentar

De tidigare C#-exemplen är för Durable Functions 2.x. För Durable Functions 1.x måste du använda DurableOrchestrationContext i stället för IDurableOrchestrationContext. Mer information om skillnaderna mellan versioner finns i artikeln Durable Functions-versioner .

Kommentar

Den här mekanismen avslutar inte aktivitetens funktionskörning som pågår. I stället tillåter den helt enkelt orkestreringsfunktionen att ignorera resultatet och gå vidare. Mer information finns i dokumentationen om Timers .

Ohanterade undantag

Om en orchestrator-funktion misslyckas med ett ohanterat undantag loggas information om undantaget och instansen slutförs med status Failed .

Inkludera anpassade undantagsegenskaper för FailureDetails (.NET Isolerad)

När du kör durable task-arbetsflöden i .NET Isolated-modellen serialiseras aktivitetsfel automatiskt till ett FailureDetails-objekt. Som standard innehåller det här objektet standardfält som:

  • ErrorType – namnet på undantagstypen
  • Meddelande – undantagsmeddelandet
  • StackTrace – den serialiserade stackspårningen
  • InnerFailure – ett kapslat FailureDetails-objekt för rekursiva inre undantag

Från och med Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0 kan du utöka det här beteendet genom att implementera ett IExceptionPropertiesProvider (definierat i Microsoft.DurableTask.Worker från v1.16.1-paketet). Den här providern definierar vilka undantagstyper och vilka av deras egenskaper som ska ingå i ordlistan FailureDetails.Properties.

Kommentar

  • Den här funktionen är endast tillgänglig i .NET Isolerad . Stöd för Java kommer att läggas till i en framtida version.
  • Kontrollera att du använder Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0 eller senare.
  • Kontrollera att du använder Microsoft.DurableTask.Worker v1.16.1 eller senare.

Implementera en leverantör för undantagsegenskaper

Implementera en anpassad IExceptionPropertiesProvider för att extrahera och returnera valda egenskaper för de undantag som du bryr dig om. Den returnerade ordlistan serialiseras i fältet Egenskaper i FailureDetails när en matchande undantagstyp genereras.

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

Registrera providern

Registrera din anpassade IExceptionPropertiesProvider i din .NET Isolated Worker-värd, vanligtvis i 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();

När ett undantag som matchar en av de hanterade typerna har registrerats inkluderas automatiskt de konfigurerade egenskaperna i dess FailureDetails.

Exempel på FailureDetails-utdata

När ett undantag inträffar som matchar providerns konfiguration får orkestreringen en serialiserad FailureDetails-struktur som den här:

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

Nästa steg