Managing Durable Function Orchestrations - Azure and C#

Overview

When working with Azure Durable Functions, it can be useful to terminate past orchestrations that are still running but no longer needed. This article provides a Azure function with an HTTP trigger that provides this functionality.

IT IS IMPORTANT NOT TO INCLUDE THIS CODE IN A PRODUCTION SITUATION, as security on it is insufficient. However, it does provide details around how this is accomplished.

Usage

1. Querying for running orchestrations

When you run the function app locally, the Command Window will display the URL for calling this function. It will likely be simliar to the following. Calling this URL — e.g., from Postman — will return a list of orchestrations that are currently running.

http://localhost:7071/api/OrchestrationManager

2. Terminating all running orchestrations

Adding the query string parameter "term=1" to the above URL will cause the code to terminate all running orchestrations, and return a list of these orchestrations that were terminated.

Code

public static class OrchestrationManagerFunctions
{
    [FunctionName(nameof(OrchestrationManager))]
    public static async Task<IActionResult> OrchestrationManager(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        [DurableClient] IDurableOrchestrationClient starter,
        ILogger log)
    {
        /*--- Inits ---*/
        log.LogInformation("C# HTTP trigger function processed a request.");

        var result = new List<string>();

        var token = new CancellationToken();

        /*--- Get/Display Orchestrations Currently Running ---*/
        var instances = await starter.ListInstancesAsync(new OrchestrationStatusQueryCondition { }, token);

        Debug.Print("=========================================================");
        Debug.Print("  Orchestrations Currently Running");
        Debug.Print("---------------------------------------------------------");

        foreach (var item in instances.DurableOrchestrationState)
        {
            if (item.RuntimeStatus != OrchestrationRuntimeStatus.Running)
            {
                continue;
            }

            var mgr = starter.CreateHttpManagementPayload(item.InstanceId);

            var url = mgr.TerminatePostUri;

            var msg = $"Instance('{item.InstanceId}') - {item.Name} - Since {item.CreatedTime}";

            result.Add(msg);

            Debug.Print(msg);
        }
        Debug.Print("=========================================================");

        /*--- Terminate All Running Instances ---*/
        var foo = req.Query["term"];

        if (Debugger.IsAttached && foo == "1")
        {
            foreach (var item in instances.DurableOrchestrationState)
            {
                if (item.RuntimeStatus != OrchestrationRuntimeStatus.Running)
                {
                    continue;
                }

                var mgr = starter.CreateHttpManagementPayload(item.InstanceId);

                var uri = new Uri(mgr.TerminatePostUri);

                var httpRequest = (HttpWebRequest)WebRequest.Create(uri);

                httpRequest.Method = "POST";

                var httpResponse = (HttpWebResponse)(await httpRequest.GetResponseAsync());

                var httpResult = await new StreamReader(httpResponse.GetResponseStream()).ReadToEndAsync();

                Debug.Print($"Instance('{item.InstanceId}') - {item.Name} - TERMINATED");
            }

            Debug.Print("=========================================================");
        }

        return new OkObjectResult(result);
    }
}