6

I am looking at this example to run a durable function Activity after a set timeout.

https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-eternal-orchestrations

This will allow my function activity to perform processing of data, then wait exactly 1 hour before it attempts to load again. This will continue to run forever. Perfect.

However, when publishing the Function to Azure, I don't want to have to manually invoke/start the function via the associated HTTP Trigger. I just want the durable function to kickoff automatically and start processing.

Is this possible? If not, what is a suggested work around?

Thanks!

aherrick
  • 19,799
  • 33
  • 112
  • 188
  • 1
    Would it do the job if you have a simple Client Function with a Timer Trigger that would kick off your orchestrator? – Kzryzstof Feb 09 '19 at 16:34
  • 1
    Otherwise, assuming you are using Azure DevOps, this could be a part of your Release pipeline. Once the Function App is deployed, have a Task that would just poke your HTTP Triggered Function... ? – Kzryzstof Feb 09 '19 at 16:36
  • are you talking about just a timer trigger function that auto started and essentially just ran once to kickoff they orchestrator? Is that possible? – aherrick Feb 09 '19 at 16:36
  • Ideally the orchestrator would just auto kick off though. What’s the limitation on this? – aherrick Feb 09 '19 at 16:42
  • 1
    If I am understanding this correctly, the orchestrator needs to be kicked off by a Client Function. In your case, I think the Client Function could be an HTTP Triggered Client Function (aka The Kicker). The Kicker Function could be called by the release pipeline. Every time you deploy a new version of your Function App, a simple bash script could be called and would call your Kicker, which would start your Orchestrator... Does that make any sense? – Kzryzstof Feb 09 '19 at 16:45
  • 1
    Yep it does. Haven’t added this App to pipelines yet but I get what you’re saying. Thanks for the thoughts! – aherrick Feb 09 '19 at 16:47
  • My point is I don’t want a kicker functional at all. Just kick yourself off up deployment – aherrick Feb 09 '19 at 16:48

3 Answers3

2

As discussed in the comments, one way of doing this would be to add a new Task in your Release pipeline.

Here is what I understood of your setup from your question:

[FunctionName("ClientFunction")]
public static async Task<HttpResponseMessage> OnHttpTriggerAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post")]
            HttpRequestMessage request, [OrchestrationClient] DurableOrchestrationClient starter, ILogger logger)
{
    // Triggers the orchestrator.
    string instanceId = await starter.StartNewAsync("OrchestratorFunction", null);

    return new HttpResponseMessage(HttpStatusCode.OK);
}


[FunctionName("OrchestratorFunction")]
public static async Task DoOrchestrationThingsAsync([OrchestrationTrigger] DurableOrchestrationContext context, ILogger logger)
{
    DateTime deadline = context.CurrentUtcDateTime.Add(TimeSpan.FromHours(1));
    await context.CreateTimer(deadline, CancellationToken.None);

    // Triggers some yout activity.
    await context.CallActivityAsync("ActivityFunction", null);
}

[FunctionName("ActivityFunction")]
public static Task DoAnAwesomeActivity([ActivityTrigger] DurableActivityContext context)
{
}

Now, every time you deploy a new version of the Function App, you need the orchestrator to be run. However, I do not think it can be started by itself.

What I propose is to have a simple bash script (using curl or something else) that would call the ClientFunction at the appropriate URL.

Bash script

On top of that, one of the nice things of this solution is that you could make the deployment fail if the Azure Function does not respond.

Kzryzstof
  • 7,688
  • 10
  • 61
  • 108
2

This seems to be working too.

[FunctionName("AutoStart")]
public static async Task Run([TimerTrigger("*/5 * * * * *", RunOnStartup = true, UseMonitor = false)]TimerInfo myStartTimer, 
    [DurableClient] IDurableClient orchestrationClient, ILogger log)
    {
        string instanceId = await orchestrationClient.StartNewAsync("Start_Orchestrator", null);
    }
  • Will it not get triggered every 5 minutes? What if I want to trigger my [Eternal Orchestration in Durable Functions][1] only once. [1]:https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-eternal-orchestrations – krishg Aug 05 '20 at 19:09
  • @KrishnenduGhosh-MSFT Every 5 seconds. ;) Normal Azure Function can run only one TimerTriggers instance at the same time. This will work only if it waits for StartNewAsync to finish. But durable functions can be suspended, so I don't think that will work fine (through I don't know). – klenium Sep 14 '20 at 13:59
1

I don't know if there are hidden problems with this, but I'm experimenting now with having a TimerTrigger that runs on startup and also once a day at midnight (or whatever schedule you want). That TimerTrigger will search the list of instances for any running instances of this orchestration, terminate them, then start a new one.

private const string MyOrchestrationName = "MyOrchestration";

[FunctionName("MyOrchestration_Trigger")]
public async Task MyOrchestrationr_Trigger(
    [TimerTrigger("0 0 0 * * *", RunOnStartup = true)] TimerInfo timer,
    [DurableClient] IDurableOrchestrationClient starter,
    ILogger log,
    CancellationToken cancellationToken)
{
    // Get all the instances currently running that have a status of Pending, Running, ContinuedAsNew
    var instances = await starter.ListInstancesAsync(new OrchestrationStatusQueryCondition()
    {
        ShowInput = false,
        RuntimeStatus = new List<OrchestrationRuntimeStatus>() { OrchestrationRuntimeStatus.Suspended, OrchestrationRuntimeStatus.Pending, OrchestrationRuntimeStatus.Running, OrchestrationRuntimeStatus.ContinuedAsNew }
    }, cancellationToken);

    // Find any instances of the current orchestration that are running.
    var myInstances = instances.DurableOrchestrationState.Where(inst => inst.Name == MyOrchestrationName);
    List<Task> terminateTasks = new List<Task>();
    foreach (var instance in myInstances )
    {
        // Delete any instances that are currently running.
        terminateTasks.Add(starter.TerminateAsync(instance.InstanceId, $"Restarting eternal orchestration"));
    }
    await Task.WhenAll(terminateTasks);

    // Start the new task now that other instances have been terminated.
    string instanceId = await starter.StartNewAsync(MyOrchestrationName, null);

    log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
}

I think at least for my purposes this will be safe. Any activities that are running when you terminate will still run to completion (which is what I want in my case), so you would just kill it and restart it on a schedule.

Mike
  • 827
  • 7
  • 21