question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Saga with user task executing step 3 too often

See original GitHub issue

Hi,

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:closed
  • Created 5 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
danielgerlagcommented, Feb 14, 2019

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

0reactions
danielgerlagcommented, Feb 18, 2019

how does it fail?

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found