elsa-core icon indicating copy to clipboard operation
elsa-core copied to clipboard

[ENH] Consider joining "active" branches

Open Nokecy opened this issue 2 years ago • 14 comments

public class FlowJoinConditionWaitAllWorkflow : IWorkflow
    {
        public ValueTask BuildAsync(IWorkflowBuilder builder, CancellationToken cancellationToken = default)
        {
            var switchFlow = new FlowSwitch
            {
                CanStartWorkflow = true,

                Cases = new List<FlowSwitchCase>()
                {
                     new FlowSwitchCase() { Label="Case1" ,Condition = new JavaScriptExpression("1 == 1") },

                     new FlowSwitchCase() { Label="Case2" ,Condition = new JavaScriptExpression("1 != 1")  },
                },

                Mode = new(SwitchMode.MatchAny)
            };

            var userTask1 = new AdvancedUserTask()
            {
                Name = "userTask1",

                Cases = new(context => new List<string>() { "下一步userTask2" }),

                AssignUser = new(c => "admin123")
            };

            var userTask2 = new AdvancedUserTask
            {
                Name = "userTask2",

                Cases = new(context => new List<string>() { "下一步userTask3" }),

                AssignUser = new(c => "admin456")
            };

            var flowJoin = new FlowJoin
            {
                Mode = new(FlowJoinMode.WaitAll)
            };

            var finish = new Finish
            {
            };

            builder.Root = new Flowchart
            {
                Activities =
                {
                    switchFlow,

                    userTask1,

                    userTask2,

                    flowJoin,

                    finish
                },

                Connections =
                {
                    new Connection(new Endpoint(switchFlow,"Case1"),new Endpoint(userTask1,"In")),

                    new Connection(new Endpoint(userTask1,"下一步userTask2"),new Endpoint(flowJoin)),

                    new Connection(new Endpoint(switchFlow,"Case2"),new Endpoint(userTask2,"In")),

                    new Connection(new Endpoint(userTask2,"下一步userTask3"),new Endpoint(flowJoin)),

                    new Connection(new Endpoint(flowJoin),new Endpoint(finish)),
                }
            };

            return new ValueTask();
        }
    }

like this ,Because Case2 will never execute! @sfmskywalker

Nokecy avatar Sep 13 '23 03:09 Nokecy

Hi @Nokecy , is this happening with the latest build?

sfmskywalker avatar Sep 13 '23 05:09 sfmskywalker

in elsa 3.0.0-preview.712 @sfmskywalker

Nokecy avatar Sep 13 '23 07:09 Nokecy

Thanks for the info @Nokecy ! I noticed that your sample workflow is using a custom activity called AdvancedUserTask - is that something you share as well? Or, perhaps even better, can you reproduce the problem with built-in activities only?

sfmskywalker avatar Sep 13 '23 09:09 sfmskywalker

public class FlowJoinConditionWaitAllBuiltInWorkflow : IWorkflow
{
    public ValueTask BuildAsync(IWorkflowBuilder builder, CancellationToken cancellationToken = default)
    {
        var switchFlow = new FlowSwitch
        {
            CanStartWorkflow = true,

            Cases = new List<FlowSwitchCase>()
            {
                 new FlowSwitchCase() { Label="Case1" ,Condition = new JavaScriptExpression("1 == 1") },

                 new FlowSwitchCase() { Label="Case2" ,Condition = new JavaScriptExpression("1 != 1")  },
            },

            Mode = new(SwitchMode.MatchAny)
        };

        var userTask1 = new WriteLine("user task1")
        {
            Name = "userTask1",
        };

        var userTask2 = new WriteLine("user task2")
        {
            Name = "userTask2",
        };

        var flowJoin = new FlowJoin
        {
            Mode = new(FlowJoinMode.WaitAll)
        };

        var finish = new Finish
        {
        };

        builder.Root = new Flowchart
        {
            Activities =
            {
                switchFlow,

                userTask1,

                userTask2,

                flowJoin,

                finish
            },

            Connections =
            {
                new Connection(new Endpoint(switchFlow,"Case1"),new Endpoint(userTask1,"In")),

                new Connection(new Endpoint(userTask1,"下一步userTask2"),new Endpoint(flowJoin)),

                new Connection(new Endpoint(switchFlow,"Case2"),new Endpoint(userTask2,"In")),

                new Connection(new Endpoint(userTask2,"下一步userTask3"),new Endpoint(flowJoin)),

                new Connection(new Endpoint(flowJoin),new Endpoint(finish)),
            }
        };

        return new ValueTask();
    }
}

@sfmskywalker like this

Nokecy avatar Sep 13 '23 09:09 Nokecy

any idea ? @sfmskywalker

Nokecy avatar Sep 25 '23 03:09 Nokecy

Hi @Nokecy ,

I looked into it, and it seems to me that the behavior is as should be expected:

  • The Switch activity produces the Case 1 outcome
  • Task 1 executes and completes
  • Join (wait all) executes and waits for the Task 2 activity to complete.
  • Task 2 will never execute, let alone complete, since the Switch activity already executed and always produces the Case 1 outcome.

Since Join is configured to wait for all inbound activities to complete, it will never complete - this is by design.

Here's a visual representation:

image

If you do need to wait for any inbound activity to complete, then you need to change the Join Mode from Wait All to Wait Any:

image

Now, the workflow will complete.

I'll close this issue as "by design", but please feel free to continue the conversation, and to reopen the issue if you still think that there is a problem.

Thank you!

sfmskywalker avatar Sep 25 '23 09:09 sfmskywalker

1695634200609

  • The Switch activity produces the Case 1 and Case2 outcome
  • Task 1 and Task 2 executes and completes
  • Task 3 was not executed because the conditions did not meet
  • workflow instance never completes
  • @sfmskywalker what should i do

Nokecy avatar Sep 25 '23 09:09 Nokecy

Make sure that the JoinMode of the Join activity is set to Wait Any.

sfmskywalker avatar Sep 25 '23 09:09 sfmskywalker

I need to ensure that task1 and task2 are completed simultaneously before proceeding to the next node! @sfmskywalker

Nokecy avatar Sep 26 '23 00:09 Nokecy

Both Task 1 and Task 2 will execute, even though Join will not wait for it.

But, if you want to ensure Task 1 and Task 2 have executed before Join continues, you need to add another Join for Task 1 and Task 2 using Wait All, and then connect that Join to the second Join. Task 3 also needs to connect to the second Join. This second Join needs to be Wait Any.

If, on the other hand, you need it to just work without knowing which cases are activated from the Switch activity, then that’s not going to work either. In that case, the Join activity needs to somehow “know” what outcomes of the Switch activity were activated so that it only waits for those branches to complete before continuing. Currently, Join only knows about its immediate inbound activities.

I’ll have to give this scenario some more thought, but if you have a suggestion I’m open to hearing it.

sfmskywalker avatar Sep 26 '23 05:09 sfmskywalker

i need this one , but i don't know how to do it! sorry sorry @sfmskywalker

i need it to just work without knowing which cases are activated from the Switch activity

Nokecy avatar Sep 26 '23 08:09 Nokecy

No worries! Let's reopen this issue and let it marinate for a while 😄 Thanks for clarifying the use case!

sfmskywalker avatar Sep 26 '23 08:09 sfmskywalker

I received a similar request from a user that essentially tries to model BPMN's Inclusive OR gateway:

image

In this diagram, the second gateway merges the paths into a single one, provided that all active branches from the OR gateway have completed.

Similarly, we need to allow the Join activity to continue if the active branches have completed. This would also allow the Join to work when merging True/False branches without requiring the WaitAny join mode.

sfmskywalker avatar Jan 17 '24 12:01 sfmskywalker

It seems to me that the simplest way to solve this would be for Join to have some notion of what the "switch" activity was so it know how many of incoming branches it needs to wait for. Maybe as an extra property of join (if empty it will wait for all incoming or any incoming), if filled it will wait for as many as were created by the "switch/branch" activity....

tomy2105 avatar Feb 23 '24 13:02 tomy2105

Fixed via #6632

sfmskywalker avatar May 06 '25 18:05 sfmskywalker