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

Try-catch clause #525

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open

Conversation

kirsanium
Copy link
Contributor

Feature introducing complex error handling via new StepBuilder's methods Try() and Catch().
This works pretty similar to CompensateWith() and is based off of that, but with no reversion and it allows to catch exceptions by type.
Also introducing WorkflowDefinitionValidator to ensure that there are corresponding catches for every try.

Copy link
Owner

@danielgerlag danielgerlag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a significant and impressive contribution 👍
Left a few comments... I'd also like to pull this branch and play with it a bit before merging.


namespace WorkflowCore.Interface
{
public interface ICatchStepBuilder<TData, TStepBody> : IStepBuilder<TData, TStepBody>, ITryStepBuilder<TData, TStepBody>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need an empty interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface gives a builder full functionality of IStepBuilder and also allows to Catch() if we want to have multiple catches after one Try(). It is used as a return type for builder after the first Catch(), whereas after Try() we want to allow only to Catch().

{
public interface IWorkflowDefinitionValidator
{
bool IsDefinitionValid(WorkflowDefinition definition);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this part of the try/catch feature? or something else?

Copy link
Contributor Author

@kirsanium kirsanium Mar 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now this only checks that every Try() has corresponding Catch().

@@ -45,6 +45,8 @@ public class ExecutionPointer
public object Outcome { get; set; }

public PointerStatus Status { get; set; } = PointerStatus.Legacy;

public Exception CurrentException { get; set; }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class needs to be serializable... not sure how different sub-classes of Exception will behave in that case?

Copy link
Contributor Author

@kirsanium kirsanium Mar 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understood, error handling happens in the same persistence round (not sure if this is how it's called tho) in which the exception occurs. So do we really need to serialize this field?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this field on the ExecutionPointer?

Copy link
Contributor Author

@kirsanium kirsanium May 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielgerlag We need this field to build StepExecutionContext so that we can retrieve a thrown exception in Catch() step. An example:

builder.Try(b => b.StartWith(_ => throw new ArgumentException("I am Exception1")))
            .Catch(new[] {typeof(Exception)}, ctx =>
            {
                Console.WriteLine($"Caught an exception: Type: '{ctx.CurrentException.GetType().Name}', Message: '{ctx.CurrentException.Message}'");
                return ExecutionResult.Next();
            })

Does it really need to be serializable though, or do you have any counter-arguments to what I have said above?

UPD: just used here the same SerializableException class as in WorkflowTerminated


namespace WorkflowCore.Models.LifeCycleEvents
{
public class WorkflowTerminated : LifeCycleEvent
{
public Exception Exception { get; set; }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class also needs to be serializable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be fine if we would put ExecutionError in here instead?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There already is WorkflowError lifecycle event. Also, you could terminate a workflow without an error... not sure this field makes sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielgerlag
When a workflow doesn't catch an exception thrown in Try clause, it seems logical to me that the workflow has to be terminated, because that means something has gone pretty badly. This can be the case if there is no matching exception type in corresponding Catch clauses. It means WorkflowTerminated event has to be published when this happens. I publish WorkflowError event when an exception happens inside of a Try clause, please, correct me, if I'm doing it wrong. If we want to retrieve information about why workflow has been terminated, we have to preserve an exception in some way. If a workflow was terminated without an error, this field just equals to null.
Nevertheless, if this field needs to be serializable, I'm going to make it of a custom class with fields like Type, Message and StackTrace, because I feel like people might make a good use of the additional information on an unexpected workflow termination.

public class StepBuilder<TData, TStepBody> : IStepBuilder<TData, TStepBody>, IContainerStepBuilder<TData, TStepBody, TStepBody>
public class StepBuilder<TData, TStepBody>
: IContainerStepBuilder<TData, TStepBody, TStepBody>,
ICatchStepBuilder<TData, TStepBody>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand why the class would implement ICatchStepBuilder?

Copy link
Contributor Author

@kirsanium kirsanium Mar 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we need to return ICatchStepBuilder after Catch().

public class StepBuilder<TData, TStepBody> 
    : IContainerStepBuilder<TData, TStepBody, TStepBody>,
      ICatchStepBuilder<TData, TStepBody>,
      IStepBuilder<TData, TStepBody>, 
      ITryStepBuilder<TData, TStepBody>

This would be more verbose, but two last lines are redundant in that case.

src/WorkflowCore/Services/WorkflowRegistry.cs Show resolved Hide resolved

namespace WorkflowCore.Services.ErrorHandlers
{
public class CatchHandler : IWorkflowErrorHandler
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you implement a set of unit tests for this class that make it's behavior explicit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will do it soon. By the way I have a question concerning behaviour of CompensateHandler. I don't understand what lines 41-45 of CatchHandler do as I copypasted them from lines 56-60 of CompensateHandler, hence I don't understand if they are needed at all. Could you please elaborate on this?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that was for bubbling up the call stack of nested containers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I clearly have troubles understanding this part of bubbling up the call stack. Could you please go into deeper details? And please tell me if it's needed or not in case of Catchclause.

@danielgerlag
Copy link
Owner

Going to be travelling for a few weeks but I'll try take a deeper look soon.

@kirsanium
Copy link
Contributor Author

Just wanted to say I didn't forget about PR, going to fix the issues you noticed some time soon.

@danielgerlag
Copy link
Owner

@kirsanium I haven't forgotten about this PR. I will try carve out some time in the next week or so to dive deeper into it but on the surface it looks really good!

@knoxi
Copy link
Contributor

knoxi commented Sep 11, 2020

@danielgerlag @kirsanium Any news on this? Would be very handy to have this in the product.

@kirsanium
Copy link
Contributor Author

@knoxi waiting for the review from @danielgerlag

@knoxi
Copy link
Contributor

knoxi commented Mar 8, 2021

@danielgerlag any news on that? It's now open since a year.

@knoxi
Copy link
Contributor

knoxi commented Nov 23, 2021

@kirsanium
Do you see any chance to update the branch and extend the tests?
It would be really great if the feature will be part of the library!

@kirsanium
Copy link
Contributor Author

Sorry I have no time and interest in this one since too much time have passed - I haven't even coded in c# for some time now...but please please finish this one, it was goddamn ready two years ago

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

Successfully merging this pull request may close these issues.

3 participants