Saga with user task executing step 3 too often
See original GitHub issueHi,
this is the test case I created:
using System;
using System.Collections.Generic;
using System.Text;
using WorkflowCore.Interface;
using WorkflowCore.Models;
using Xunit;
using FluentAssertions;
using System.Linq;
using System.Threading.Tasks;
using WorkflowCore.Testing;
using WorkflowCore.Users.Models;
namespace WorkflowCore.IntegrationTests.Scenarios
{
public class RetrySagaWithUserTaskScenario : WorkflowTest<RetrySagaWithUserTaskScenario.Workflow, RetrySagaWithUserTaskScenario.MyDataClass>
{
public class MyDataClass
{
}
public class Workflow : IWorkflow<MyDataClass>
{
public static int Event1Fired;
public static int Event2Fired;
public static int Event3Fired;
public static int TailEventFired;
public static int Compensation1Fired;
public static int Compensation2Fired;
public static int Compensation3Fired;
public static int Compensation4Fired;
public string Id => "RetrySagaWithUserTaskWorkflow";
public int Version => 1;
public void Build(IWorkflowBuilder<MyDataClass> builder)
{
builder
.StartWith(context => ExecutionResult.Next())
.CompensateWith(context => Compensation1Fired++)
.Saga(x => x
.StartWith(context => ExecutionResult.Next())
.CompensateWith(context => Compensation2Fired++)
.UserTask("prompt", data => "assigner")
.WithOption("a", "Option A")
.Do(wb => wb
.StartWith(context => ExecutionResult.Next())
.Then(context =>
{
Event1Fired++;
if (Event1Fired < 3)
throw new Exception();
Event2Fired++;
})
.CompensateWith(context => Compensation3Fired++)
.Then(context => Event3Fired++)
.CompensateWith(context => Compensation4Fired++)
))
.OnError(WorkflowErrorHandling.Retry, TimeSpan.FromSeconds(1))
.Then(context => TailEventFired++);
}
}
public RetrySagaWithUserTaskScenario()
{
Setup();
Workflow.Event1Fired = 0;
Workflow.Event2Fired = 0;
Workflow.Event3Fired = 0;
Workflow.Compensation1Fired = 0;
Workflow.Compensation2Fired = 0;
Workflow.Compensation3Fired = 0;
Workflow.Compensation4Fired = 0;
Workflow.TailEventFired = 0;
}
[Fact]
public async Task Scenario()
{
var workflowId = StartWorkflow(new MyDataClass());
var instance = await Host.PersistenceStore.GetWorkflowInstance(workflowId);
string oldUserOptionKey = null;
for (var i = 0; i != 3; ++i)
{
var userOptions = await WaitForDifferentUserStepAsync(instance, TimeSpan.FromSeconds(1), oldUserOptionKey);
userOptions.Count.Should().Be(1);
var userOption = userOptions.Single();
userOption.Prompt.Should().Be("prompt");
userOption.AssignedPrincipal.Should().Be("assigner");
userOption.Options.Count.Should().Be(1);
var selectionOption = userOption.Options.Single();
selectionOption.Key.Should().Be("Option A");
selectionOption.Value.Should().Be("a");
await Host.PublishUserAction(userOption.Key, string.Empty, selectionOption.Value);
oldUserOptionKey = userOption.Key;
}
WaitForWorkflowToComplete(workflowId, TimeSpan.FromSeconds(30));
GetStatus(workflowId).Should().Be(WorkflowStatus.Complete);
UnhandledStepErrors.Count.Should().Be(2);
Workflow.Event1Fired.Should().Be(3);
Workflow.Event2Fired.Should().Be(1);
Workflow.Event3Fired.Should().Be(1);
Workflow.Compensation1Fired.Should().Be(0);
Workflow.Compensation2Fired.Should().Be(2);
Workflow.Compensation3Fired.Should().Be(2);
Workflow.Compensation4Fired.Should().Be(0);
Workflow.TailEventFired.Should().Be(1);
}
private static async Task<IReadOnlyCollection<OpenUserAction>> WaitForDifferentUserStepAsync(
WorkflowInstance instance,
TimeSpan timeout,
string oldUserActionKey = null)
{
var startTime = DateTime.UtcNow;
while (DateTime.UtcNow - startTime <= timeout)
{
var userActions = await WaitForUserStepAsync(instance);
if (oldUserActionKey != null && userActions.Any(x => x.Key == oldUserActionKey))
{
continue;
}
return userActions;
}
return Array.Empty<OpenUserAction>();
}
private static async Task<IReadOnlyCollection<OpenUserAction>> WaitForUserStepAsync(WorkflowInstance instance)
{
var delayCount = 200;
var openActions = instance.GetOpenUserActions()?.ToList();
while ((openActions?.Count ?? 0) == 0)
{
await Task.Delay(TimeSpan.FromMilliseconds(10));
openActions = instance.GetOpenUserActions()?.ToList();
if (delayCount-- == 0)
{
break;
}
}
return openActions;
}
}
}
I’m out of ideas and maybe you can exmplain why this doesn’t work.
I expected that the workflow executes the third step (Event3Fired) only once (when no exception gets thrown).
EDIT: Updated test code and title.
Issue Analytics
- State:
- Created 5 years ago
- Comments:6 (4 by maintainers)
Top Results From Across the Web
Using a saga for controlling task queue workflow in C# - ...
Adding steps would require code changes and deployment, but handling long running workflows with many steps seems perfect. Also, coordinating ...
Read more >Where is the "retry" in BPMN 2.0?
So in total it tries to execute the task three times in a short time period. The process engine is managing the number...
Read more >Transactions and Failover using Saga Pattern in ...
In this article, I'll introduce to you how to use the Saga Pattern for distributed transactions and will show up how it can...
Read more >Fixed: Lego Star Wars The Skywalker Saga Crashing PC ...
Some users reported that the crashing issue can be solved by simply running the game as an administrator. Here you may have a...
Read more >Dealing with problems and exceptions
Take a closer look at understanding workers, handling exceptions on a technical level, leveraging retries, using incidents, and more.
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found

This looks like a bug with nested structures contained within a saga… I will try get a fix out in a few days
how does it fail?