Wednesday, September 30, 2020

RetryOptions in Azure Durable Functions

In this post, let's see how we can handle Retries on Activity Functions or Orchestration Functions in the Durable Functions Framework.

When you are calling an Activity Functions or another Orchestration Function from IDurableOrchestrationContext, there 2 methods to use CallActivityWithRetryAsync and CallSubOrchestratorWithRetryAsync which you can use that accepts a parameter of type RetryOptions.

So basically when you are calling an Activity Function or another Orchestration Function you can do something like below.

var retryOptions = new RetryOptions(TimeSpan.FromSeconds(10), 3)
    Handle = exception => exception.InnerException is MyException myException
var someReturn = await context.CallActivityWithRetryAsync<SomeReturn>("MyActivity", retryOptions, someInput);

So here, if MyActvity throws an exception of type MyException, we will be retrying again in 10 seconds and maxNumberOfAttempts will be 3. One of the important things to note for the Handle delegate is, you need access exceptions' InnerException if you want to get the actual exception. The exception would always be FunctionFailedException.

There is another interesting property that you can use in RetryOptions which is BackoffCoefficientBackoffCoefficient lets you increment the waiting time in subsequent retries. Default is 1, for an example if you do something like below,

var retryOptions = new RetryOptions(TimeSpan.FromSeconds(10), 3)
    BackoffCoefficient = 2,
    Handle = exception => exception.InnerException is MyException myException
var someReturnValue= await context.CallActivityWithRetryAsync<SomeReturn>("MyActivity", retryOptions, someInput);

The first retry will be in 10 seconds, the second retry will be in 20 seconds (after the first retry fail) and the final retry will be in 30 seconds (after the second retry fail). 

These are the different options available in RetryOptions that you can use to get the best retry experience.

  • BackoffCoefficient: Gets or sets the backoff coefficient.
  • FirstRetryInterval: Gets or sets the first retry interval.
  • Handle: Gets or sets a delegate to call on an exception to determine if retries should proceed.
  • MaxNumberOfAttempts: Gets or sets the max number of attempts.
  • MaxRetryInterval: Gets or sets the max retry interval.
  • RetryTimeout: Gets or sets the timeout for retries.

So hope this helps.

Happy Coding.


Wednesday, September 23, 2020

Azure Durable Functions for Monitoring

In this post, let's see how Azure Durable Functions can be used to Monitor a long-running task. It's actually pretty easy. But there are some important things to Note.

Consider the below code. This is a basic implementation of such a monitoring functionality using an Orchestration Function. Here basically we are triggering an Activity function that will invoke a long-running task (here it is returning the taskId so we can poll for status using that). And then we are polling every 15 seconds for 60 seconds by triggering another Activity Function. If we are able to get a successful result within that time frame, we are returning that, else we throw a TimeoutException or you can do whatever you want.

public static async Task<TaskResult> Run([OrchestrationTriggerIDurableOrchestrationContext context)
    TaskInput taskInput = context.GetInput<TaskInput>();

    ProcessingResult processingResult = await context.CallActivityAsync<ProcessingResult>("InvokeLongRunningTask", taskInput);

    var pollingIntervalInSeconds = 15;
    DateTime expiryTime = context.CurrentUtcDateTime.AddSeconds(60);

    while (context.CurrentUtcDateTime < expiryTime)
        TaskResult taskResult = await context.CallActivityAsync<TaskResult>("GetTaskResult", processingResult.TaskId);

        if (taskResult.Status == "Success")
            return taskResult;

        // Orchestration sleeps until this time.
        DateTime nextCheck = context.CurrentUtcDateTime.AddSeconds(pollingIntervalInSeconds);
        await context.CreateTimer(nextCheck, CancellationToken.None);

    throw new TimeoutException("Timeout Occurred");

The most important thing here is, we are getting CurrentUtcDateTime from the IDurableOrchestrationContext. The reason for that is, Durable Functions should be deterministic. It should be deterministic because Durable Functions use Event Sourcing pattern to determine it's current state (and that is rather than keeping the current state, it tracks how/what events made the function to get into the current state). If we use DateTime.UtcDateTime, for every time it replays the message, the DateTime will be different and will end up with a infinite wait. CurrentUtcDateTime will be the same for every replay of this specific instance.

So hope this helps!

Happy Coding.


Thursday, September 3, 2020

Consume Secrets in an Azure Key Vault from an Azure Function App

In a development environment, you most likely will be using plain text values in App Settings, but in higher environments, you might not have the luxury to do that. Instead, you might have to read secrets from an Azure Key Vault. In this post, let's see how we can consume a secret in an Azure Key Vault from an Azure Function App. It's actually pretty easy. I am assuming you already have an Azure Function App and an Azure Key Vault created.

First, go to your Function App, click on the Identity tab, and then turn it on.

Identity On

Then, go to your Azure Key Vault where you have your secrets, Click on Access Policies.

Access Policies

And from there click on + Add Access Policy. So here since I am only giving access to read Secrets, I am selecting only Get from Secret permissions multi select dropdown. And then click on Select principal and search for your function application name, select and Add. 

Add access policy

Now from the Key Vault, go to the secret where you want to expose and copy it's Secret Identifier.

Secret Identifier

Now go back to the Function App configuration settings, set the value for your key like below.


And you can see it's getting resolved.

Add/Edit application setting

So that's it and no code changes required.

Happy Coding.