mikhailshilkov / DurableFunctions.FSharp

F#-friendly API layer for Azure Durable Functions
MIT License
62 stars 8 forks source link

Catch exceptions raised by ActivityFunctions #9

Closed jbeeko closed 5 years ago

jbeeko commented 5 years ago

I am using Durable functions for "fire and forget" callback processing. The DurableFuction is invoked with a collection of callbacks to make. It initiates each one and fails if the response is not 200 OK. A retry policy is used to ensure the failed attempt is retried n times.

This works, but the only way to indicate a failure is by raising an exception in the activity function. But this is then being logged in our azure app-insights. I think what is happening is that we should be catching the exception in the orchestration. Since we are not I think it is terminating the orchestration and logging an exception to AppInsights.

Mark Heath seems to indicate something like the below should be done, but without re-throwing the exception.

I don't know how this could be done, perhaps an parameter to .All like this Exception -> Unit. All would then catch any exception and pass it to the function to handle and possibly rethrow?

https://markheath.net/post/error-handling-durable-functions

[FunctionName("ExceptionHandlingOrchestrator")]
public static async Task<string> ExceptionHandlingOrchestrator(
    [OrchestrationTrigger] DurableOrchestrationContext ctx,
    TraceWriter log)
{
    var inputData = ctx.GetInput<string>();
    try
    {
        var a1 = await ctx.CallActivityAsync<string>("Activity1", inputData);
        var a2 = await ctx.CallActivityAsync<ActivityResult>("Activity2", a1);
        var a3 = await ctx.CallActivityAsync<string>("Activity3", a2);
        return a3;
    }
    catch (Exception)
    {
        await ctx.CallActivityAsync<string>("CleanupActivity", inputData);
        // optionally rethrow the exception to fail the orchestration
        throw;
    }
}
mikhailshilkov commented 5 years ago

You should be able to use try..with in F# orchestrator function for the same effect. Have you found a case where it doesn't work/apply?

jbeeko commented 5 years ago

In the sample Retry.fs I first tried this:

    try let! msg = Activity.callWithRetries policy failUntil3 "Jam"
    with _ -> ()
    return msg

I think this is what should work, however there is a compile time error indicating no TryWith is defined on the builder. image

Based on this: https://stackoverflow.com/questions/21801144/wrangling-trywith-in-computation-expressions I think TryWith should be defined here: https://github.com/mikhailshilkov/DurableFunctions.FSharp/blob/cef984fa2c8703d7a57a41c50f2600b8e781287c/src/DurableFunctions.FSharp/OrchestratorCE.fs#L155-L164

... Getting creative...

I also tried this, but I don't think this is the right thing to do. In any-case it will not compile because the unit being returned from the with is not a task.

    let! msg = 
        try Activity.callWithRetries policy failUntil3 "Jam"
        with _ -> ()
    return msg

Finally I tried this slightly crazy approach. noOp is a new activity function that does nothing. This will not compile because the type signature does not line up.

    let! msg = 
        try Activity.callWithRetries policy failUntil3 "Jam"
        with _ -> Activity.call noOp ()
    return msg
mikhailshilkov commented 5 years ago

Yes, you are right... Trying without ! won't have effect indeed, so I'd need to implement those extra methods of the CE. Interesting!

jbeeko commented 5 years ago

I tried adding the below naive implementation from the post: member inline __.TryWith(r, fn) = try r() with ex -> fn ex

That then let me compile:

    try 
        let! msg = Activity.callWithRetries policy failUntil3 "Jam"
        return msg
    with _ -> 
        return "error"

But the exception is not caught and the with block is never entered.

mikhailshilkov commented 5 years ago

Fixed by #10