1

i see in Microsoft's example of fan-out that there is no concern for 'defending' against replay

but i definitely need if (!orchestrationContext.IsReplaying) here:

[FunctionName(FUNC_NAME_ORCH)]
public static async Task<string> RunPlayerYouTubeOrchestration(
    [OrchestrationTrigger] DurableOrchestrationContext orchestrationContext,
    ILogger log)
{
    log?.LogInformation($"{FUNC_NAME_ORCH}: {nameof(RunPlayerYouTubeOrchestration)} invoked...");
    log?.LogInformation($"{FUNC_NAME_ORCH}: {nameof(DurableOrchestrationContextBase.InstanceId)} {orchestrationContex

    log?.LogInformation($"{FUNC_NAME_ORCH}: calling {FUNC_NAME_ORCH_FUNC0}.c ..");
    var jsonBlobs = await orchestrationContext
        .CallActivityAsync<IEnumerable<string>>(FUNC_NAME_ORCH_FUNC0, null);

    if ((jsonBlobs == null) || !jsonBlobs.Any())
    {
        var errorMessage = $"{FUNC_NAME_ORCH}: The expected JSON blobs from `{FUNC_NAME_ORCH_FUNC0}` are not here.";
        log?.LogError(errorMessage);
        throw new NullReferenceException(errorMessage);
    }

    if (!orchestrationContext.IsReplaying)
    {
        var tasks = jsonBlobs.Select(json =>
            {
                log?.LogInformation($"{FUNC_NAME_ORCH}: calling {FUNC_NAME_ORCH_FUNC1}...");
                return orchestrationContext.CallActivityAsync(FUNC_NAME_ORCH_FUNC1, json);
            });

        log?.LogInformation($"{FUNC_NAME_ORCH}: fan-out starting...");
        await Task.WhenAll(tasks);
    }

    log?.LogInformation($"{FUNC_NAME_ORCH}: fan-out complete.");

    log?.LogInformation($"{FUNC_NAME_ORCH}: calling {FUNC_NAME_ORCH_FUNC2}...");
    await orchestrationContext.CallActivityAsync(FUNC_NAME_ORCH_FUNC2, null);

    return orchestrationContext.InstanceId;
}

without this if gate, FUNC_NAME_ORCH_FUNC1 will be called endlessly

what am i doing wrong here? is there ever a 'right' time to use !orchestrationContext.IsReplaying or is it a hack to use less-than deterministic code?

rasx
  • 5,288
  • 2
  • 45
  • 60
  • i am now convinced that using `await Task.WhenAll(tasks)` in an Azure Function Orchestration is *not* the way to go (it could be ab anti-pattern); look at Microsoft's latest Durable functions sample where they loop through a a bunch of city names and the function awaits *once* per call; my work above is trying to make AF wait for *several* calls that are out of Orchestration context – rasx Feb 21 '22 at 03:50

1 Answers1

1

I think your code should work fine without the IsReplaying. The CallActivityAsync() method will be called multiple times but it will only execute the activity function once for a given input. After that first execution, the result is checkpointed in table storage and successive calls to that activity will return the result from the table and not execute the activity function again.

More info about the checkpointing & replay: https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-orchestrations#reliability

I suggest you move the logging statement for FUNC1 inside the FUNC1 activity function. This way you only log the actual activity function executions instead of logging how may times the CallActivityAsync method is called.

Marc
  • 1,798
  • 8
  • 15
  • in AzFunc Core Tools I can see logging _inside_ `FUNC1` running again and again on replay (when the `if` gate is removed) – rasx Dec 20 '19 at 18:17
  • That sounds very strange and should not happen. Do you have a sample repo that I can look into? – Marc Dec 21 '19 at 11:31
  • i do not have a public repo for this but i have made a gist: https://gist.github.com/BryanWilhite/184501cfaa35cfd0a596535a4179c69f – rasx Dec 22 '19 at 06:50