Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

intercept Orchestrator before/after setting the runtimeStatus #1244

Open
thdotnet opened this issue Feb 28, 2020 · 10 comments
Open

intercept Orchestrator before/after setting the runtimeStatus #1244

thdotnet opened this issue Feb 28, 2020 · 10 comments
Labels

Comments

@thdotnet
Copy link

When working with several sub activities on an orchestrator. If some activity exceeds the number of retries, it will set the orchestrator status as "runtimeStatus:Failed". I would like the ability to intercept this moment and perform some action.

@ghost ghost added the Needs: Triage 🔍 label Feb 28, 2020
@ConnorMcMahon
Copy link
Contributor

You actually can already do this. These failed activities will throw a FunctionFailedException, which you can catch yourself and perform code in a try-catch block.

@ConnorMcMahon ConnorMcMahon added Needs: Author Feedback Waiting for the author of the issue to respond to a question and removed Needs: Triage 🔍 labels Feb 28, 2020
@thdotnet
Copy link
Author

The retry logic is implemented by Durable Functions. I would like a point to intercept before / after it set this state.

In summary, some extensibility to enable AOP

@ghost ghost added Needs: Attention 👋 and removed Needs: Author Feedback Waiting for the author of the issue to respond to a question labels Feb 29, 2020
@ConnorMcMahon
Copy link
Contributor

ConnorMcMahon commented Mar 2, 2020

@thdotnet

I'm not quite sure what you mean.

Let's say you have the following orchestration:

public static async Task<string> CatchException([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    try
    {
         return await context.CallActivityWithRetryAsync<string>("PrimaryAction", new RetryOptions(TimeSpan.FromSeconds(3), 3), input);
    } catch (FunctionFailedException)
    {
         return await context.CallActivityAsync<string>("BackupAction", input);
    }
}

Now let's say that PrimaryAction fails 3 times. This replay, context.CallActivityWithRetryAsync will throw a FunctionFailedException, and therefore we will go into the catch block and execute BackupAction. The orchestrator will never go into the Failed state, as it never had an uncaught exception.

@ConnorMcMahon ConnorMcMahon added Needs: Author Feedback Waiting for the author of the issue to respond to a question and removed Needs: Attention 👋 labels Mar 2, 2020
@thdotnet
Copy link
Author

thdotnet commented Mar 3, 2020

You got the idea. But I would like something like this:

public static async Task<string> CatchException([OrchestrationTrigger] IDurableOrchestrationContext context)
{
         context.OnFunctionFailedException("MyFunction"); 

         //or

         context.OnFunctionFailedException(s=> Foo()); 

         await context.CallActivityWithRetryAsync<string>("FunctionA", new RetryOptions(TimeSpan.FromSeconds(3), 3), input);
await context.CallActivityWithRetryAsync<string>("FunctionB", new RetryOptions(TimeSpan.FromSeconds(3), 3), input);
await context.CallActivityWithRetryAsync<string>("FunctionC", new RetryOptions(TimeSpan.FromSeconds(3), 3), input);
}

@ghost ghost added Needs: Attention 👋 and removed Needs: Author Feedback Waiting for the author of the issue to respond to a question labels Mar 3, 2020
@ConnorMcMahon
Copy link
Contributor

I'm not quite sure what utility that adds that catching the exception manually does not add, but I will leave it open for dicussion.

@thdotnet
Copy link
Author

thdotnet commented Mar 3, 2020

the benefit is quite simple. I can have one single orchestrator or multiple sub orchestrators. I want to setup a single entry point to capture failures, but this could be used for other purposes too. For example, in the past there was the Invocation Filters that was removed, but allow Cross Cutting Concerns. I would like the same ability, but in the orchestrator.

@ltouro
Copy link

ltouro commented Mar 3, 2020

I think the "before failure" part is easy to implement in user-land code.

You can provide an extension method to IDurableOrchestrationContext that accepts an additional delegate parameter "OnFailure" and wrap it around a try/catch block as Connor suggested.

public override System.Threading.Tasks.Task<TResult> CallActivityWithRetryAsyncAndFailureHandler<TResult> (string functionName, Microsoft.Azure.WebJobs.RetryOptions retryOptions, object input, Action<Exception> onError);

I think the "after failure" part is harder. But from your example, "just before failure" might be enough.

@thdotnet
Copy link
Author

thdotnet commented Mar 3, 2020

Agreed, in fact it should be:

Before / After -> activity
Before / After -> Orchestrator

@ltouro
Copy link

ltouro commented Mar 3, 2020

Oh, I see now. I think using the extension methods are viable for both before and after for activity functions (using 3 extra params, "before", "after", "onError").

The "before Orchestrator starts" is also viable (adding the extension method on IDurableOrchestrationClient interface, i.e, extending StartNewAsync).

The hard part is the "after orchestration is completed/failed". I suggest you to check WaitForCompletionOrCreateCheckStatusResponseAsync method. It uses some sort of pooling to keep checking the status on the orchestration. Not ideal given the amount of resource usage you would have.

Other option is to add extra activities to all your orchestrations. Altough not very "cross-cutting", these activities would be pretty generic and reusable.

@thdotnet
Copy link
Author

thdotnet commented Mar 3, 2020

as I said, I can implement everything by myself...but I think they would be a nice feature at the framework level

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants