'Azure ethernal orchestration - Cancel timer

I'm working with the Azure Ethernal Orchestrations, with a tasks pool. I have many tasks to execute, with some priority. I have a service which give me the top priority task. But sometimes I need to wait until a specific date to execute the next task.

So I've made a kind of watchdog using eternal orchestrations.

My problem is when I need to cancel the orchestrations, I need to cancel the Timer too. But when I'm doing it, the function is still in a Running State and my OutputStatus doen't change.

Here my orchetration function :

    [FunctionName(nameof(RunTaskWatchdog))]
    public async Task RunTaskWatchdog(
        [OrchestrationTrigger] IDurableOrchestrationContext context)
    {
        var poolName = context.GetInput<string>();
        TaskStatus output = new()
        {
            PoolName = poolName,
            Message = "Getting next task"
        };
        context.SetCustomStatus(output);

        // Retrieve the next task to execute
        var task = await context.CallActivityAsync<TaskExecution>(nameof(Activities.GetNextResearchForExecution), poolName);
        if (task == null)
        {
            output.Message = "Unable to get a task";
            context.SetCustomStatus(output);
            return;
        }
        if (string.IsNullOrEmpty(task.TaskName))
        {
            output.Message = task.Error;
            context.SetCustomStatus(output);
            return;
        }

        output.Message = "Got task";
        output.CurrentTask = task;
        if (task.NextExecution.HasValue)
        {
            // If the next task can't be executed before a date, sleep till the good date
            output.Message = "Need to wait to respect task delay";
            output.NextExcecution = task.NextExecution.Value;
            context.SetCustomStatus(output);

            try
            {
                await context.CreateTimer(task.NextExecution.Value, tokens[poolName].Token);
            }
            catch (OperationCanceledException)
            {
                output.Message = "Operation cancelled while sleeping";
                output.NextExcecution = null;
                context.SetCustomStatus(output);

                return;

                // After this line, the CustomStatus has not changed, and the orchestration is still in running state
            }
        }
        else
        {
            context.SetCustomStatus(output);
        }

        var executionSuccess = await context.CallActivityAsync<bool>(nameof(Activities.ExecuteResearchActivity), task.TaskName);

        var doneSuccess = await context.CallActivityAsync<bool>(nameof(Activities.SetExecutingTaskDone), poolName);

        DateTime nextTask = context.CurrentUtcDateTime.AddSeconds(10);
        output.Message = "Waiting next execution";
        output.NextExcecution = nextTask;
        output.CurrentTask = null;
        context.SetCustomStatus(output);

        try
        {
            await context.CreateTimer(nextTask, tokens[poolName].Token);
        }
        catch (OperationCanceledException)
        {
            output.Message = "Operation cancelled";
            output.NextExcecution = null;
            context.SetCustomStatus(output);
            return;
        }

        context.ContinueAsNew(poolName);
    }

As you can see, in the catch section, I enter in this in debug, but I don't see any changes when I retrieve the status with Postman (http://localhost:7071/runtime/webhooks/durabletask/instances)

I've made a sample code available here : https://github.com/Rizov74/TestDurableOrchestrator



Solution 1:[1]

Ok I've found a way by using the external events :

instead of

        try
        {
            await context.CreateTimer(task.NextExecution.Value, tokens[poolName].Token);
        }
        catch (OperationCanceledException)
        {
            output.Message = "Operation cancelled while sleeping";
            output.NextExcecution = null;
            context.SetCustomStatus(output);

            return;

            // After this line, the CustomStatus has not changed, and the orchestration is still in running state
        }

I use :

            using (var cts = new CancellationTokenSource())
            {
                Task sleepingTask = context.CreateTimer(task.NextExecution.Value, output, cts.Token);
                Task timeoutTask = context.WaitForExternalEvent("stop");

                Task winner = await Task.WhenAny(sleepingTask, timeoutTask);
                if (winner == sleepingTask)
                {
                    // Can continue
                }
                else
                {
                    // Cancel timer token
                    cts.Cancel();
                    if (!context.IsReplaying)
                        Console.WriteLine($"{context.InstanceId}: wait cancelled {poolName} : {task.TaskName}");

                    output.Message = "Operation cancelled while sleeping";
                    output.NextExcecution = null;
                    context.SetCustomStatus(output);

                    // If the watchdog has been cancelled, we need to telle the pool the task is cancelled
                    var ok = await context.CallActivityAsync<bool>(nameof(Activities.CancelExecutingTask), poolName);

                    if (!context.IsReplaying)
                        Console.WriteLine($"{context.InstanceId}: task correctly canceled {poolName} : {task.TaskName}");

                    // Stop the watchdog
                    return;
                }
            }

I have updated my repo on github, you can take a look if you want an complex example of durable orchestrator

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1