Skip to content

Inconsistent behavior #273

Closed
Closed
@ssougnez

Description

@ssougnez

Hello,

I'm currently experiencing an inconsistent behavior with wfc and I'm starting to run out of idea. I keep telling myself I did something wrong but I can figure out what... First of all, I'm using

  • WorkflowCore 1.9.0
  • WorkflowCore.Persistence.SqlServer 1.8.1

Here is the definition of my workflow:

builder
    .StartWith<NopStep>()
    .Parallel()
        .Do(then => then
            .StartWith<NopStep>()
            .Then<CreateTaskForUserStep>()
                .Input((step, data) =>
                {
                    step.Concerned = data.Concerned.Value;
                    step.Type = CreateTaskForUserStep.UserType.Manager;
                })
                .Output((step, data) => data.TaskOne = step.Output)
            .WaitFor(Constants.WorkflowEvent.WaitForApproval, (data, context) => data.TaskOne.Id.ToString())
                .Output((step, data) => data.TaskOne = step.EventData as TaskInfo)
        )
        .Do(then => then
            .StartWith<NopStep>()
            .Then<CreateTaskForUserStep>()
                .Input((step, data) =>
                {
                    step.Concerned = data.Concerned.Value;
                    step.Type = CreateTaskForUserStep.UserType.Manager;
                    step.Level = 2;
                })
                .Output(data => data.TaskTwo, step => step.Output)
            .WaitFor(Constants.WorkflowEvent.WaitForApproval, (data, context) => data.TaskTwo.Id.ToString())
                .Output((step, data) => data.TaskTwo = step.EventData as TaskInfo)
        )
    .Join()
        .CancelCondition(data => data.TaskOne.IsRejected || data.TaskTwo.IsRejected, true)
    .Then(_ =>
    {
        return ExecutionResult.Next();
    });

This workflow is not complete because of the issue I'm experiencing. It is pretty simple. When it is started by someone, two parallel tasks are started and each of them call the step "CreateTaskForUserStep" to create a task in the database. The step is registered as transient and use a regular "DbContext" to create the task. Once the task are created, I'm just waiting for them to get approved. If one of the two task gets rejected, the workflow must continue and cancel the other parallel branch (which is why I'm using the "CancelCondition").

Like this, this workflow works correctly. If I set a breakpoint at the last line of code, it gets hit correctly. However, if I modify the two parallel branches by adding:

.Then<NopStep>()

Just before the "WaitFor", then the breakpoint I set at the last line gets hit twice... Three remarks:

  1. The NopStep is a simple step that returns ExecutionResult.Next()
  2. The behavior is deterministic. The way I'm testing it is that I trigger the workflow, the two tasks get created, I accept the first one but reject the second one. If I accept both tasks or reject the first one, the breakpoint gets it only once.
  3. All my step (even the "NopStep") is registered as transient. I made an extension method that analyze my assembly to register all the classes that inherit from "StepBody" and "StepBodyAsync" as transient. This way, I can easily inject dependency without having to manually register the step as transient.

Something even weirder is that if I add another

.Then<NopStep>()

Just after the previous one, then the breakpoint gets hits only once... Unfortunately, I can't upload the project somewhere as it is a pretty big project interconnected with other systems.

The more I think about it, the more I think there is an issue with "CancelCondition". Indeed, I tried to follow the same steps (so accept the first task and reject the other one) with the "CancelCondition" line commented and in this case, the breakpoint only gets hit once. It looks like when both parallel branches terminate by themselves and that the "CancelCondition" evaluates to true, the rest of the workflow is executed twice (once by the "CancelCondition" and once by the normal flow of the workflow). To validate this supposition, I replaced the "CancelCondition" line by this:

.CancelCondition(data => (data.TaskOne.IsRejected && data.TaskTwo.State == TaskApprovalState.WaitingForApproval) || (data.TaskTwo.IsRejected && data.TaskOne.State == TaskApprovalState.WaitingForApproval), true)

This way, if the two parallel branches terminate by themselves, the condition of the "CancelCondition" does not evaluate to "true". With this, the breakpoint only gets hit once, which tend to validate my theory. The only thing I don't understand is the fact that the number of .Then<NopStep>() leverage the workflow behavior.

I can use the aforementioned workaround for now, but I fear that there is an underlying issue that could arise in other situations that this one (when using "CancelCondition" I guess). Don't hesitate if you have any question, I really think that library is awesome and I hope that you'll find a solution to this issue ;-)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions