workflow-core
workflow-core copied to clipboard
Strange behavior after Decide/Branch step - seems like interleaved execution
Describe the bug If a workflow has steps after a Decide/Branch step then it seems that steps in branch and after the branch are executed interleaved
To Reproduce Steps to reproduce the behavior: Run this program:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using WorkflowCore.Interface;
using WorkflowCore.Models;
namespace WorkflowCoreBranchProblem
{
internal class Program
{
static void Main(string[] args)
{
File.Delete(@"mydb.db");
var services = new ServiceCollection();
services.AddLogging(x => x.AddConsole());
services.AddTransient<LogStep>();
services.AddWorkflow(x => { x.UseSqlite("Data Source=mydb.db", true); });
var serviceProvider = services.BuildServiceProvider();
var host = serviceProvider.GetRequiredService<IWorkflowHost>();
host.RegisterWorkflow<TestWorkflow, TestWorkflowData>();
host.OnStepError += Host_OnStepError;
host.Start();
var workflowId = host.StartWorkflow("TestWorkflow", new TestWorkflowData());
Console.WriteLine($"Started workflow: {workflowId}");
Console.WriteLine("Press key to exit");
Console.ReadKey();
}
private static void Host_OnStepError(WorkflowInstance workflow, WorkflowStep step, Exception exception)
{
Console.WriteLine("Workflow Error: " + exception.Message);
}
}
class TestWorkflow : IWorkflow<TestWorkflowData>
{
public string Id => "TestWorkflow";
public int Version => 1;
public void Build(IWorkflowBuilder<TestWorkflowData> builder)
{
var branch1 = builder.CreateBranch()
.StartWith<LogStep>()
.Input(x => x.Number, x => 11)
.Then<LogStep>()
.Input(x => x.Number, x => 12)
.Then<LogStep>()
.Input(x => x.Number, x => 13)
.Then<LogStep>()
.Input(x => x.Number, x => 14)
.Then<LogStep>()
.Input(x => x.Number, x => 15);
var branch2 = builder.CreateBranch()
.StartWith<LogStep>()
.Input(x => x.Number, x => 21)
.Then<LogStep>()
.Input(x => x.Number, x => 22)
.Then<LogStep>()
.Input(x => x.Number, x => 23);
builder
.StartWith<LogStep>()
.Input(x => x.Number, x => 1)
.Then<LogStep>()
.Input(x => x.Number, x => 2)
.Output(x => x.Result, x => x.Result)
.Decide(data => data.Result)
.Branch((data, outcome) => data.Result == ResultType.Branch1, branch1)
.Branch((data, outcome) => data.Result == ResultType.Branch2, branch2)
.Then<LogStep>()
.Input(x => x.Number, x => 4)
.Then<LogStep>()
.Input(x => x.Number, x => 5);
}
}
class TestWorkflowData
{
public ResultType? Result { get; set; }
}
internal enum ResultType
{
Branch1,
Branch2
}
class LogStep : IStepBody
{
public int Number { get; set; }
public ResultType? Result { get; set; }
public async Task<ExecutionResult> RunAsync(IStepExecutionContext context)
{
Console.WriteLine($"LogStep: {Number}");
Result = ResultType.Branch1;
return ExecutionResult.Next();
}
}
}
Output is: LogStep: 1 LogStep: 2 LogStep: 11 LogStep: 4 LogStep: 12 LogStep: 5 LogStep: 13 LogStep: 14 LogStep: 15
Expected behavior Output should be: LogStep: 1 LogStep: 2 LogStep: 11 LogStep: 12 LogStep: 13 LogStep: 14 LogStep: 15 LogStep: 4 LogStep: 5
Additional context
- Is Decide/Branch supposed to be the last step in the workflow definition? I didn't find such information in the documentation Decision Branches
- Are there any workarounds?
To run the sample the following package references are required:
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="WorkflowCore" Version="3.9.0" />
<PackageReference Include="WorkflowCore.Persistence.Sqlite" Version="3.9.0" />
<PackageReference Include="WorkflowCore.Persistence.SqlServer" Version="3.9.0" />
</ItemGroup>
Do you find any information about your user case ? I suspect you are correct about thé position of décide/branch step in the workflow, but i'll bé glad if you can confirm.
Describe the bug If a workflow has steps after a Decide/Branch step then it seems that steps in branch and after the branch are executed interleaved
To Reproduce Steps to reproduce the behavior: Run this program:
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using WorkflowCore.Interface; using WorkflowCore.Models; namespace WorkflowCoreBranchProblem { internal class Program { static void Main(string[] args) { File.Delete(@"mydb.db"); var services = new ServiceCollection(); services.AddLogging(x => x.AddConsole()); services.AddTransient<LogStep>(); services.AddWorkflow(x => { x.UseSqlite("Data Source=mydb.db", true); }); var serviceProvider = services.BuildServiceProvider(); var host = serviceProvider.GetRequiredService<IWorkflowHost>(); host.RegisterWorkflow<TestWorkflow, TestWorkflowData>(); host.OnStepError += Host_OnStepError; host.Start(); var workflowId = host.StartWorkflow("TestWorkflow", new TestWorkflowData()); Console.WriteLine($"Started workflow: {workflowId}"); Console.WriteLine("Press key to exit"); Console.ReadKey(); } private static void Host_OnStepError(WorkflowInstance workflow, WorkflowStep step, Exception exception) { Console.WriteLine("Workflow Error: " + exception.Message); } } class TestWorkflow : IWorkflow<TestWorkflowData> { public string Id => "TestWorkflow"; public int Version => 1; public void Build(IWorkflowBuilder<TestWorkflowData> builder) { var branch1 = builder.CreateBranch() .StartWith<LogStep>() .Input(x => x.Number, x => 11) .Then<LogStep>() .Input(x => x.Number, x => 12) .Then<LogStep>() .Input(x => x.Number, x => 13) .Then<LogStep>() .Input(x => x.Number, x => 14) .Then<LogStep>() .Input(x => x.Number, x => 15); var branch2 = builder.CreateBranch() .StartWith<LogStep>() .Input(x => x.Number, x => 21) .Then<LogStep>() .Input(x => x.Number, x => 22) .Then<LogStep>() .Input(x => x.Number, x => 23); builder .StartWith<LogStep>() .Input(x => x.Number, x => 1) .Then<LogStep>() .Input(x => x.Number, x => 2) .Output(x => x.Result, x => x.Result) .Decide(data => data.Result) .Branch((data, outcome) => data.Result == ResultType.Branch1, branch1) .Branch((data, outcome) => data.Result == ResultType.Branch2, branch2) .Then<LogStep>() .Input(x => x.Number, x => 4) .Then<LogStep>() .Input(x => x.Number, x => 5); } } class TestWorkflowData { public ResultType? Result { get; set; } } internal enum ResultType { Branch1, Branch2 } class LogStep : IStepBody { public int Number { get; set; } public ResultType? Result { get; set; } public async Task<ExecutionResult> RunAsync(IStepExecutionContext context) { Console.WriteLine($"LogStep: {Number}"); Result = ResultType.Branch1; return ExecutionResult.Next(); } } }
Output is: LogStep: 1 LogStep: 2 LogStep: 11 LogStep: 4 LogStep: 12 LogStep: 5 LogStep: 13 LogStep: 14 LogStep: 15
Expected behavior Output should be: LogStep: 1 LogStep: 2 LogStep: 11 LogStep: 12 LogStep: 13 LogStep: 14 LogStep: 15 LogStep: 4 LogStep: 5
Additional context
- Is Decide/Branch supposed to be the last step in the workflow definition? I didn't find such information in the documentation Decision Branches
- Are there any workarounds?
Do you find any information about your user case ? I suspect you are correct about the position of décide/branch step in the workflow, but i'll be glad if you can confirm.
I dont understand what information you need from me. Can you rephrase your question?
The only workaround I found was to split the workflow creation into multiple methods so each branch would create the full remaining workflow until the end. For small workflow this would be ok but I assume if the workflow gets bigger the could would be more complex.
In the example above it means to move the workflow after the branch into a method and call it to append it to each created branch. In a way this would duplicate code but it works correctly