I am trying to build a simple orchestrator func + additional call with sub-orchestrator.
The problem is in main orchestrator and call to await ctx.CallSubOrchestratorAsync("sub-orchestration", someInput) - the task is never resolved/completed... If I change the code in sub orchestrator from await Send_SomeData(); to Send_SomeData().Wait(); then it works, but that is not what I want. As far as I know async/await is supported in orchestration funcs
Thank you!
The code is below:
Main Orchestrator
[FunctionName("main-orchestration")]
public static async Task MainOrchestrationAsync(
[OrchestrationTrigger] IDurableOrchestrationContext ctx,
ILogger log)
{
var someInput = "my msg";
await ctx.CallSubOrchestratorAsync("sub-orchestration", someInput);
log.LogWarning("finish");
}
Sub Orchestrator
[FunctionName("sub-orchestration")]
public static async Task SubOrchestrationAsync(
[OrchestrationTrigger] IDurableOrchestrationContext ctx,
ILogger log)
{
log.LogWarning("Sub Start");
await Send_SomeData(some_url, some_message);
log.LogWarning("Sub End");
}
Send_SomeData
public static async Task<string> ConnectAsync(string url, string message)
{
using var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Post,
Content = new FormUrlEncodedContent(new[]
{
...message...
})
};
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
UPDATE
I've decided to create an example from scratch, to not miss something and get something that can be copy/paste for debugging.
Below is another example. Main End log message never appeared, so Task from .CallSubOrchestratorAsync never resolved.
What am I missing?
Thanks again!
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
namespace _Await_Issue
{
public static class Function1
{
[FunctionName("Main")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger log)
{
log.LogWarning("Main Start");
try
{
await context.CallSubOrchestratorAsync("Sub", null);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
log.LogWarning("Main End");
}
[FunctionName("Sub")]
public static async Task RunSubOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger log)
{
log.LogWarning("Sub Start");
var data = await GetDataAsync("https://www.google.com");
log.LogWarning("Sub End");
}
[FunctionName("Start")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]
HttpRequestMessage req,
[DurableClient] IDurableOrchestrationClient starter,
ILogger log)
{
// Function input comes from the request content.
string instanceId = await starter.StartNewAsync("Main", null);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
public static async Task<string> GetDataAsync(string url)
{
var httpClient = new HttpClient();
using var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Get,
};
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}
I found the light here ))
Answering on my own question as it might help others.
As Peter Bons pointed Send_SomeData method was under suspicion ))
The problem in general is in "Deterministic Apis" as Azure team call in the docs
Deterministic Apis Explanation
In my case Send_SomeData inside uses HttpClient to communication with external endpoint.
It's not recommended as it might result in an unexpected behavior as in my case with await...
If there are such portions of code (e.g. Send_SomeData in my case) put it in Activity and call this with context.CallActivityAsync() - (be aware that Activity funcs has limited time to execute)
Related
I need to have access to FunctionAppDirectory in Azure Function
Here is a simplified version of the function
[Function("Test")]
public static HttpResponseData Test([HttpTrigger(AuthorizationLevel.Function, "post", Route = "Test")] HttpRequestData req,
ExecutionContext context, FunctionContext fContext)
{
var log = fContext.GetLogger(nameof(TestOperations));
log.LogInformation(context?.FunctionAppDirectory?.ToString());
return req.CreateResponse(HttpStatusCode.OK);
}
ExecutionContext here is null.
My Program.cs file
class Program
{
static Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureAppConfiguration(configurationBuilder =>
{
configurationBuilder.AddCommandLine(args);
})
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
// Add Logging
services.AddLogging();
})
.Build();
return host.RunAsync();
}
}
Azure Function running in .NET 5
How I can configure binding for ExecutionContext or get FunctionAppDirectory in other ways?
As Alex mentioned in the comment, azure function .net 5 is not support 'context.FunctionAppDirectory' to get the directory of function app now.
In function app 3.0, the 'ExecutionContext' is be designed in the package 'Microsoft.Azure.WebJobs'. But in your code, the ExecutionContext is from 'System.Threading'. So these are different classes.
You can use below code to get the azure function directory in .NET 5.0 azure function:
using System;
using System.Collections.Generic;
using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
namespace FunctionApp6
{
public static class Function1
{
[Function("Function1")]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("Function1");
logger.LogInformation("C# HTTP trigger function processed a request.");
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
var local_root = Environment.GetEnvironmentVariable("AzureWebJobsScriptRoot");
var azure_root = $"{Environment.GetEnvironmentVariable("HOME")}/site/wwwroot";
var actual_root = local_root ?? azure_root;
response.WriteString(actual_root);
return response;
}
}
}
There are couple of durable functions that call each other.
Main orchestration -> Sub orchestration -> Activity -> Helper async method
Each func has ILogger dependency and log on function start and on function end.
Both orchestrators duplicates "on start" message for some reason. (See pic)
Activity does not have this effect. (See pic)
Ran example below many times - same story.
I am also sure that the whole process has been triggered once.
Is this a bug in orchestrators or expected behavior?
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
namespace Issues
{
public static class Log_Issue
{
[FunctionName("Main")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger log)
{
try
{
log.LogWarning("Main Start");
await context.CallSubOrchestratorAsync("Sub", null);
log.LogWarning("Main End");
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
[FunctionName("Sub")]
public static async Task RunSubOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger log)
{
log.LogWarning("Sub Start");
var data = await context.CallActivityAsync<string>("Activity", null);
log.LogWarning("Sub End");
}
[FunctionName("Activity")]
public static async Task<string> GetDataActivity([ActivityTrigger] string name, ILogger log)
{
log.LogWarning("Activity Start");
var data = await GetDataAsync("https://www.google.com");
log.LogWarning("Activity End");
return data;
}
[FunctionName("Start")]
public static async Task<IActionResult> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]
HttpRequestMessage req,
[DurableClient] IDurableOrchestrationClient starter,
ILogger log)
{
var instanceId = await starter.StartNewAsync("Main", null);
log.LogWarning($"Started orchestration with ID = '{instanceId}'.");
return new OkResult();
}
private static async Task<string> GetDataAsync(string url)
{
var httpClient = new HttpClient();
using var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Get,
};
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}
This is expected. For example on await context.CallActivityAsync("Activity", null); The code pauses itself and may even be loaded out of memory (in order to save on cost).
Then the orchestrator waits for an event to be placed in another Azure Storage Table which the activity creates, this may occur many days later. For activities they are usually very instant but it still waits for this event to occur.
When that happens the code needs to start from where it last stopped but there is no way to do that. Therefor the code reruns from the beginning but instead of waiting for the activity to finish again it first looks in the table and sees that we already have done this activity and can continue running. If the activity function returned some value it would be returned from the await call. During both runs of the orchestrator it would log but since we only go inte the activity ones that would only be logged ones.
This is why orchestrators have to be deterministic since e.g. a random value on the first run would not be the same as during the second run. Instead we would put the random.Next() into an activity funtion so that the value is saved to Azure Table Storage to be used on subsequent reruns. The orchestrator could also be waiting for some external events which normal functions create. E.g someone has to verify their email account which could take some number of days and this is why durable functions can unload themself and restart when they are triggered by the event.
All that #FilipB said is true. It is just missing the actual code to solve it ;)
[FunctionName("Main")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger log)
{
log = context.CreateReplaySafeLogger(log); // this is what you should use at the start of every Orchestrator
I want to call another (not timer triggered) azure function from my timer triggered azure function.
It compiles but during runtime I get the error:
System.ArgumentException: 'The function 'HelloWorld' doesn't exist, is disabled, or is not an orchestrator function. Additional info: No orchestrator functions are currently registered!'
I reduced it to this tiny code snippet.
[FunctionName("HelloWorld")]
public static string HelloWorld([ActivityTrigger] string name, ILogger log)
{
return $"Hello {name}!";
}
[FunctionName("DownloadLiveList")]
public async void DownloadLiveList([DurableClient] IDurableOrchestrationClient client, [TimerTrigger("0 0 0 * * *", RunOnStartup = true)]TimerInfo myTimer, ILogger log)
{
await client.StartNewAsync<string>("HelloWorld", "Magdeburg");
}
As I took the idea from the official Microsoft example for that kind of azure function cascading, I've no clue, why the function "HelloWorld" is not registered. After uploading into azure, the function is visible in the azure portal as all other functions from the class.
Your time trigger function needs to invoke the start function written with Durable Function Framework. Here's a sample:
[FunctionName("Function1")]
public async Task Run([TimerTrigger("0 */1 * * * *")]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
var url = "http://localhost:7071/api/Durable_Starter";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.GZip;
using (HttpWebResponse response = (HttpWebResponse) await request.GetResponseAsync())
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream))
{
var html = reader.ReadToEnd();
log.LogInformation(html);
}
}
[FunctionName("Durable_Starter")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]HttpRequest req, [DurableClient] IDurableClient starter, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string instanceId = await starter.StartNewAsync("Durable_Orchestrator");
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
var checkStatusResponse = starter.CreateCheckStatusResponse(req, instanceId);
return checkStatusResponse;
}
[FunctionName("Durable_Orchestrator")]
public async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
{
var message = await context.CallActivityAsync<string>("HelloWorld", "Thiago");
log.LogInformation(message);
}
[FunctionName("HelloWorld")]
public string HelloWorldActivity([ActivityTrigger] string name)
{
return $"Hello {name}!";
}
I want to use Azure Durable Functions to orchestrate my functions. This is my code (auto-generated by VS Code when you create a Durable Function) :
[FunctionName("testorchestration")]
public static async Task<List<string>> RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context)
{
var outputs = new List<string>();
// Replace "hello" with the name of your Durable Activity Function.
outputs.Add(await context.CallActivityAsync<string>("testorchestration_Hello", "Tokyo"));
outputs.Add(await context.CallActivityAsync<string>("testorchestration_Hello", "Seattle"));
outputs.Add(await context.CallActivityAsync<string>("testorchestration_Hello", "London"));
// returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
return outputs;
}
[FunctionName("testorchestration_Hello")]
public static string SayHello([ActivityTrigger] string name, ILogger log)
{
log.LogInformation($"Saying hello to {name}.");
return $"Hello {name}!";
}
[FunctionName("testorchestration_HttpStart")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]HttpRequestMessage req,
[OrchestrationClient]DurableOrchestrationClient starter,
ILogger log)
{
// Function input comes from the request content.
string instanceId = await starter.StartNewAsync("testorchestration", null);
return await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(req,instanceId);
}
I add await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(req,instanceId); because I need to wait for completion , and this is my problem.
If I wait for completion, it takes about ~2 seconds and sometimes 20 secondes (test on localhost) and I want to understand why because each function takes ~50ms to execute.
Maybe because this method calls await starter.GetStatusAsync(instanceId) everytime until the task is completed ?
I am trying to locally step through (using Visual Studio 2019) a Durable fuction, but when I invoke a call to context.CallActivityWithRetryAsync("MyActivity_Activity", retryOptions, myActivityParameters) from the OrchestrationTrigger, that invocation always returns the below:
Id = [xxxx], Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
My first guess is there might be a deadlock somewhere, but I have rebooted my machine and still seeing the same result. Also, I tried setting a breakpoint at the first line of the activity (MyActivity_Activity), but that wasn't hit. Any help would be greatly appreciated!
Using the following:
VS 2019 Pro
.Net Core 2.2
Microsoft.Azure.DurableTask.AzureStorage v1.6.2
Microsoft.Azure.WebJobs.Extensions.DurableTask v1.8.2
Microsoft.NET.Sdk.Functions v1.0.29
...et al.
Below are some relevant snippets of code:
[FunctionName("MyOrchestrator_HttpStart")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Function, "post")]HttpRequestMessage req,
[OrchestrationClient]DurableOrchestrationClient starter, ExecutionContext executionContext)
{
string instanceId = string.Empty;
MyOrchestratorParameters myOrchestratorParameters = await req.Content.ReadAsAsync<MyOrchestratorParameters>();
instanceId = await starter.StartNewAsync("MyOrchestrator", myOrchestratorParameters);
return starter.CreateCheckStatusResponse(req, instanceId);
}
[FunctionName("MyOrchestrator")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context,
ExecutionContext executionContext)
{
MyOrchestratorParameters myOrchestratorParameters =
context.GetInput<MyOrchestratorParameters>();
ValidateParameters(myOrchestratorParameters);
var delay = 3000;
var retry = 3;
var retryOptions = new
RetryOptions(TimeSpan.FromSeconds(delay), retry);
MyActivityParameters myActivityParameters = new
MyActivityParameters()
{
JobNumber = myOrchestratorParameters.JobNumber,
Stage = myOrchestratorParameters.Stage,
TemplateId = myOrchestratorParameters.TemplateId
};
myActivityResults =
context.CallActivityWithRetryAsync<MyActivityResult>
("MyActivity_Activity", retryOptions, myActivityParameters);
...
}
[FunctionName("MyActivity_Activity")]
public static async Task<MyActivityResult>
RunActivity([ActivityTrigger] DurableActivityContext
activityContext, ExecutionContext executionContext)
{
var _myActivityParameters =
activityContext.GetInput<MyActivityParameters>();
//do some stuff & return MyActivityResult object
}
I think I found the answer. In my calls to CallActivityWithRetryAsync, I needed to predicate that with an await. Once I did this, it started working!