Error Handling and Diagnostics

Logging in orchestrator functions

An orchestrator function creates checkpoints as each of its composite activity functions complete. After each call to CallActivityAsync on the DurableOrchestrationContext instance completes, the orchestrator function will automatically replay to rebuild their in-memory state.

To prevent logging statements from duplicating, use the IsReplaying property on the DurableOrchestrationContext.

[FunctionName("PlaceOrder")]
public static async Task<InvoiceData> OrderOrchestration(
    [OrchestrationTrigger] DurableOrchestrationContext context,
    ILogger log)
{
    OrderRequestData orderData = context.GetInput<OrderRequestData>();

    if (!context.IsReplaying) log.LogInformation("About Checking inventory");
    await context.CallActivityAsync<bool>("CheckAndReserveInventory", orderData);

    if (!context.IsReplaying) log.LogInformation("Processing payment");
    InvoiceData invoice = await context.CallActivityAsync<InvoiceData>("ProcessPayment", orderData);

    return invoice;
}

Takeaways

  • GetStatusAsync method of DurableOrchestrationClient can be used to get status information for a given orchestration.
  • Alternatively, the status endpoint returned from CreateCheckStatusResponse can be used.
  • The execution history and results can be included in the response if showHistory and showHistoryOutput are set to true.
  • GetStatusAsync returns an instance of DurableOrchestrationStatus that contains all the status information.

Read more

Handling activity function exceptions

Unhandled exceptions thrown in activity functions will be rethrown in the calling orchestrator function as a FunctionFailedException. The InnerException property of the FunctionFailedException will contain the original exception from the activity.

[FunctionName("PlaceOrder")]
public static async Task OrderOrchestration(
    [OrchestrationTrigger] DurableOrchestrationContext context,
    TraceWriter log)
{
    try
    {
        await context.CallActivityAsync<bool>("CheckAndReserveInventory", null);
    }
    catch (FunctionFailedException ex)
    {
        log.Error("Inventory check failed", ex);
    }
}


[FunctionName("CheckAndReserveInventory")]
public static bool CheckAndReserveInventory([ActivityTrigger] DurableActivityContext context)
{
    throw new ArgumentException("Oops...");
}

Takeaways

  • FunctionFailedException is thrown in the orchestrator if an activity function throws an unhandled exception
  • FunctionFailedException's InnerException property will contain the source exception from the activity function.

Read more

Calling activity functions with retry

When call activities in your orchestration functions, you may want to apply a retry policy to handle transient errors that may occur. In such cases, the DurableOrchestrationContext provides the CallActivityWithRetryAsync method. The difference between CallActivityAsync and CallActivityWithRetryAsync is that the latter accepts a type of RetryOptions that specifies the retry behavior.

public static async Task OrderOrchestration(
[OrchestrationTrigger] DurableOrchestrationContext context,
ILogger log)
{
    RetryOptions retryPolicy = new RetryOptions(
        firstRetryInterval: TimeSpan.FromSeconds(3),
        maxNumberOfAttempts: 3);

    retryPolicy.Handle = (ex) =>
    {
        TaskFailedException failedEx = ex as TaskFailedException;
        return (failedEx.Name != "CheckAndReserveInventory") ? false : true;
    };

    try
    {
        await context.CallActivityWithRetryAsync<bool>("CheckAndReserveInventory", retryPolicy, null);
    }
    catch (FunctionFailedException ex)
    {
        log.LogError("Inventory check failed", ex);
    }
}

Here is an activity function that throws an exception.

[FunctionName("CheckAndReserveInventory")]
public static async Task<bool> CheckAndReserveInventory([ActivityTrigger] DurableActivityContext context)
{
    throw new Exception("Ooops...");
}

Takeaways

  • CallActivityWithRetryAsync allows orchestrator functions to call activities with retry behavior.
  • A RetryOptions instance is used to define retry behavior.
  • A RetryOptions instance can be reused across multiple calls to CallActivityWithRetryAsync.
  • The Handle property on RetryOptions lets callers define whether retries should proceed or not.

Read more