diff --git a/src/core/Elsa.Abstractions/Models/WorkflowInstance.cs b/src/core/Elsa.Abstractions/Models/WorkflowInstance.cs index f866b79af..8e9135134 100644 --- a/src/core/Elsa.Abstractions/Models/WorkflowInstance.cs +++ b/src/core/Elsa.Abstractions/Models/WorkflowInstance.cs @@ -13,6 +13,7 @@ namespace Elsa.Models { Variables = new Variables(); ScheduledActivities = new Stack(); + Scopes = new Stack(); } public string DefinitionId { get; set; } = default!; @@ -38,8 +39,9 @@ namespace Elsa.Models get => _blockingActivities; set => _blockingActivities = new HashSet(value, BlockingActivityEqualityComparer.Instance); } - + public WorkflowFault? Fault { get; set; } public Stack ScheduledActivities { get; set; } + public Stack Scopes { get; set; } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs index 23aed2b29..80ea8f6a7 100644 --- a/src/core/Elsa.Abstractions/Services/CompositeActivity.cs +++ b/src/core/Elsa.Abstractions/Services/CompositeActivity.cs @@ -25,6 +25,7 @@ namespace Elsa.Services { if (!IsScheduled) { + context.WorkflowInstance.Scopes.Push(Id); IsScheduled = true; await OnEnterAsync(context); return Outcome(Enter); diff --git a/src/core/Elsa.Abstractions/Services/Models/IBranchingActivity.cs b/src/core/Elsa.Abstractions/Services/Models/IBranchingActivity.cs deleted file mode 100644 index dd251f753..000000000 --- a/src/core/Elsa.Abstractions/Services/Models/IBranchingActivity.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Elsa.Services.Models -{ - public interface IBranchingActivity - { - void Unwind(ActivityExecutionContext context); - } -} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/For/For.cs b/src/core/Elsa.Core/Activities/ControlFlow/For/For.cs index 0ae6085c9..5601e8e13 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/For/For.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/For/For.cs @@ -1,6 +1,7 @@ using System; using Elsa.ActivityResults; using Elsa.Attributes; +using Elsa.Services; using Elsa.Services.Models; // ReSharper disable once CheckNamespace @@ -11,7 +12,7 @@ namespace Elsa.Activities.ControlFlow Description = "Iterate between two numbers.", Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done } )] - public class For : IteratingActivity + public class For : Activity { [ActivityProperty(Hint = "An expression that evaluates to the starting number.")] public long Start { get; set; } @@ -46,6 +47,7 @@ namespace Elsa.Activities.ControlFlow if (loop) { + context.WorkflowInstance.Scopes.Push(Id); CurrentValue = currentValue + Step; return Outcome(OutcomeNames.Iterate, currentValue); } diff --git a/src/core/Elsa.Core/Activities/ControlFlow/ForEach/ForEach.cs b/src/core/Elsa.Core/Activities/ControlFlow/ForEach/ForEach.cs index 0be7fe23e..cb125a52d 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/ForEach/ForEach.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/ForEach/ForEach.cs @@ -14,7 +14,7 @@ namespace Elsa.Activities.ControlFlow Description = "Iterate over a collection.", Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done } )] - public class ForEach : IteratingActivity + public class ForEach : Activity { [ActivityProperty(Hint = "Enter an expression that evaluates to a collection of items to iterate over.")] public ICollection Items { get; set; } = new Collection(); @@ -44,6 +44,7 @@ namespace Elsa.Activities.ControlFlow { var output = collection[currentIndex]; CurrentIndex = currentIndex + 1; + context.WorkflowInstance.Scopes.Push(Id); return Combine(Outcome(OutcomeNames.Iterate, output)); } diff --git a/src/core/Elsa.Core/Activities/ControlFlow/IfElse/IfElse.cs b/src/core/Elsa.Core/Activities/ControlFlow/IfElse/IfElse.cs index 7b387c83c..0651c461f 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/IfElse/IfElse.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/IfElse/IfElse.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Linq; using Elsa.ActivityResults; using Elsa.Attributes; using Elsa.Services; @@ -14,58 +12,35 @@ namespace Elsa.Activities.ControlFlow Description = "Evaluate a Boolean expression and continue execution depending on the result.", Outcomes = new[] { True, False, OutcomeNames.Done } )] - public class IfElse : Activity, IBranchingActivity + public class IfElse : Activity { public const string True = "True"; public const string False = "False"; [ActivityProperty(Hint = "The condition to evaluate.")] public bool Condition { get; set; } + + private bool EnteredScope + { + get => GetState(); + set => SetState(value); + } protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context) { - var outcome = Condition ? True : False; - return Outcome(outcome); - } - - public void Unwind(ActivityExecutionContext context) - { - var workflowExecutionContext = context.WorkflowExecutionContext; - var workflowBlueprint = workflowExecutionContext.WorkflowBlueprint; - var activityExecutionContext = context; - var currentActivityBlueprint = activityExecutionContext.ActivityBlueprint; - var currentActivityId = currentActivityBlueprint.Id; - - // An IfElse activity completed within a burst of execution, which means its outcome (true or false) did not yield child activities to execute (an empty branch). - // Schedule its child activities connected to the "Done" outcome. - if (currentActivityBlueprint.Type == nameof(IfElse)) + if (!EnteredScope) { - var nextActivities = GetNextActivities(workflowExecutionContext, currentActivityId); - workflowExecutionContext.ScheduleActivities(nextActivities); + context.WorkflowInstance.Scopes.Push(Id); + EnteredScope = true; } else { - // Get all incoming connections. - var inboundConnections = workflowBlueprint.GetInboundConnectionPath(currentActivityId).ToList(); - - // Filter out those connections who have a source of IfElse. - var query = - from inboundConnection in inboundConnections - let parentActivityBlueprint = inboundConnection.Source.Activity - where inboundConnection.Source.Activity.Type == nameof(IfElse) - select inboundConnection; - - var firstMatch = query.FirstOrDefault(); - - if (firstMatch != null && firstMatch.Source.Outcome != OutcomeNames.Done) - { - var parentActivityBlueprint = firstMatch.Source.Activity; - var nextActivities = OutcomeResult.GetNextActivities(workflowExecutionContext, parentActivityBlueprint.Id, new[] { OutcomeNames.Done }).ToList(); - workflowExecutionContext.ScheduleActivities(nextActivities); - } + EnteredScope = false; + return Done(); } + + var outcome = Condition ? True : False; + return Outcome(outcome); } - - private IEnumerable GetNextActivities(WorkflowExecutionContext workflowExecutionContext, string currentActivityId) => OutcomeResult.GetNextActivities(workflowExecutionContext, currentActivityId, new[] { OutcomeNames.Done }); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/IteratingActivity.cs b/src/core/Elsa.Core/Activities/ControlFlow/IteratingActivity.cs deleted file mode 100644 index a8a4d20e5..000000000 --- a/src/core/Elsa.Core/Activities/ControlFlow/IteratingActivity.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Linq; -using Elsa.Services; -using Elsa.Services.Models; - -// ReSharper disable once CheckNamespace -namespace Elsa.Activities.ControlFlow -{ - public abstract class IteratingActivity : Activity, IBranchingActivity - { - public void Unwind(ActivityExecutionContext context) - { - var workflowExecutionContext = context.WorkflowExecutionContext; - var workflowBlueprint = workflowExecutionContext.WorkflowBlueprint; - var currentActivityId = context.ActivityBlueprint.Id; - var inboundConnections = workflowBlueprint.GetInboundConnectionPath(currentActivityId).ToList(); - - var query = - from inboundConnection in inboundConnections - let parentActivityBlueprint = inboundConnection.Source.Activity - where inboundConnection.Source.Activity.Type == Type - select inboundConnection; - - var firstMatch = query.FirstOrDefault(); - - if(firstMatch != null && firstMatch.Source.Outcome == OutcomeNames.Iterate) - workflowExecutionContext.ScheduleActivity(firstMatch.Source.Activity.Id); - } - } -} \ No newline at end of file diff --git a/src/core/Elsa.Core/Activities/ControlFlow/While/While.cs b/src/core/Elsa.Core/Activities/ControlFlow/While/While.cs index 820930f22..5be320c56 100644 --- a/src/core/Elsa.Core/Activities/ControlFlow/While/While.cs +++ b/src/core/Elsa.Core/Activities/ControlFlow/While/While.cs @@ -11,7 +11,7 @@ namespace Elsa.Activities.ControlFlow Description = "Execute while a given condition is true.", Outcomes = new[] { OutcomeNames.Iterate, OutcomeNames.Done } )] - public class While : IteratingActivity + public class While : Activity { [ActivityProperty(Hint = "The condition to evaluate.")] public bool Condition { get; set; } @@ -21,8 +21,12 @@ namespace Elsa.Activities.ControlFlow var loop = Condition; if (loop) + { + context.WorkflowInstance.Scopes.Push(Id); + return Outcome(OutcomeNames.Iterate); - + } + return Done(); } } diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index 35ad2fb18..ea39314ba 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -55,11 +55,6 @@ namespace Microsoft.Extensions.DependencyInjection .AddSingleton(options.StorageFactory) .AddStartupTask(); - services - .AddTransient() - .AddTransient() - .AddTransient(); - options .AddWorkflowsCore() .AddCoreActivities(); @@ -163,7 +158,6 @@ namespace Microsoft.Extensions.DependencyInjection .AddActivity() .AddActivity() .AddActivity() - .AddActivity() .AddActivity() .AddActivity() .AddActivity() diff --git a/src/core/Elsa.Core/Handlers/PersistWorkflow.cs b/src/core/Elsa.Core/Handlers/PersistWorkflow.cs index 9baf66078..441e040fc 100644 --- a/src/core/Elsa.Core/Handlers/PersistWorkflow.cs +++ b/src/core/Elsa.Core/Handlers/PersistWorkflow.cs @@ -59,6 +59,7 @@ namespace Elsa.Handlers private async ValueTask SaveWorkflowAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken) { + workflowExecutionContext.PruneActivityData(); var workflowInstance = workflowExecutionContext.WorkflowInstance; await _workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken); _logger.LogDebug("Committed workflow {WorkflowInstanceId} to storage", workflowInstance.Id); diff --git a/src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs b/src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs index a3773b093..fafbbf4ff 100644 --- a/src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs +++ b/src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs @@ -1,62 +1,31 @@ -using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Elsa.Events; using Elsa.Models; -using Elsa.Services.Models; using MediatR; namespace Elsa.Handlers { /// - /// Walks up the tee of inbound connections along the "Iterate" outcome of the looping construct (While/For/ForEach) and re-schedules the looping activity. + /// Walks up the tree of inbound connections along the "Iterate" outcome of the looping construct (While/For/ForEach) and re-schedules the looping activity. /// Also handles composite activity re-scheduling. /// public class RescheduleBranchingActivitiesAndContainers : INotificationHandler { - private readonly IEnumerable _branchingActivities; - - public RescheduleBranchingActivitiesAndContainers(IEnumerable branchingActivities) - { - _branchingActivities = branchingActivities; - } - public Task Handle(WorkflowExecutionBurstCompleted notification, CancellationToken cancellationToken) { - var workflowExecutionContext = notification.WorkflowExecutionContext; var activityExecutionContext = notification.ActivityExecutionContext; - - foreach (var branchingActivity in _branchingActivities) - { - if (workflowExecutionContext.HasScheduledActivities || workflowExecutionContext.Status != WorkflowStatus.Running) - break; - - branchingActivity.Unwind(activityExecutionContext); - } - - ScheduleContainers(notification); - return Task.CompletedTask; - } - - private static void ScheduleContainers(WorkflowExecutionBurstCompleted notification) - { - var workflowExecutionContext = notification.WorkflowExecutionContext; - var workflowBlueprint = workflowExecutionContext.WorkflowBlueprint; + var workflowExecutionContext = activityExecutionContext.WorkflowExecutionContext; // If no suspension has been instructed, re-schedule any container activities. - if (workflowExecutionContext.HasScheduledActivities || workflowExecutionContext.Status != WorkflowStatus.Running) - return; - - var activityBlueprint = notification.ActivityExecutionContext.ActivityBlueprint; - - // Re-schedule the parent activity, if any. - if (activityBlueprint.Parent != null && workflowBlueprint.GetActivity(activityBlueprint.Parent.Id) != null) - { - var output = GetFinishOutput(workflowExecutionContext); - workflowExecutionContext.ScheduleActivity(activityBlueprint.Parent.Id, output); - } - } + if (workflowExecutionContext.HasScheduledActivities || workflowExecutionContext.Status != WorkflowStatus.Running || !workflowExecutionContext.WorkflowInstance.Scopes.Any()) + return Task.CompletedTask; - private static FinishOutput? GetFinishOutput(WorkflowExecutionContext workflowExecutionContext) => workflowExecutionContext.WorkflowInstance.Output as FinishOutput; + var parentActivityId = workflowExecutionContext.WorkflowInstance.Scopes.Pop(); + workflowExecutionContext.ScheduleActivity(parentActivityId, activityExecutionContext.Output); + + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/WorkflowRunner.cs b/src/core/Elsa.Core/Services/WorkflowRunner.cs index 30879a80f..460d113dd 100644 --- a/src/core/Elsa.Core/Services/WorkflowRunner.cs +++ b/src/core/Elsa.Core/Services/WorkflowRunner.cs @@ -290,13 +290,13 @@ namespace Elsa.Services await _mediator.Publish(new ActivityExecuted(activityExecutionContext), cancellationToken); await result.ExecuteAsync(activityExecutionContext, cancellationToken); workflowExecutionContext.WorkflowInstance.Output = activityExecutionContext.Output; - workflowExecutionContext.PruneActivityData(); - activityOperation = Execute; workflowExecutionContext.CompletePass(); await _mediator.Publish(new WorkflowExecutionPassCompleted(workflowExecutionContext, activityExecutionContext), cancellationToken); if (!workflowExecutionContext.HasScheduledActivities) await _mediator.Publish(new WorkflowExecutionBurstCompleted(workflowExecutionContext, activityExecutionContext), cancellationToken); + + activityOperation = Execute; } if (workflowExecutionContext.HasBlockingActivities) diff --git a/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.Core/Configuration/WorkflowInstanceConfiguration.cs b/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.Core/Configuration/WorkflowInstanceConfiguration.cs index 386d9627b..0b7e20e04 100644 --- a/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.Core/Configuration/WorkflowInstanceConfiguration.cs +++ b/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.Core/Configuration/WorkflowInstanceConfiguration.cs @@ -19,6 +19,7 @@ namespace Elsa.Persistence.EntityFramework.Core.Configuration builder.Ignore(x => x.BlockingActivities); builder.Ignore(x => x.Fault); builder.Ignore(x => x.ScheduledActivities); + builder.Ignore(x => x.Scopes); builder.Ignore(x => x.Variables); builder.Property("Data"); } diff --git a/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.Core/Stores/EntityFrameworkWorkflowInstanceStore.cs b/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.Core/Stores/EntityFrameworkWorkflowInstanceStore.cs index ec0a88b51..2d0ac75dc 100644 --- a/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.Core/Stores/EntityFrameworkWorkflowInstanceStore.cs +++ b/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.Core/Stores/EntityFrameworkWorkflowInstanceStore.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq.Expressions; using Elsa.Models; using Elsa.Persistence.Specifications; @@ -34,6 +35,7 @@ namespace Elsa.Persistence.EntityFramework.Core.Stores entity.ActivityOutput, entity.BlockingActivities, entity.ScheduledActivities, + entity.Scopes, entity.Fault }; @@ -52,6 +54,7 @@ namespace Elsa.Persistence.EntityFramework.Core.Stores entity.ActivityOutput, entity.BlockingActivities, entity.ScheduledActivities, + entity.Scopes, entity.Fault }; @@ -66,6 +69,7 @@ namespace Elsa.Persistence.EntityFramework.Core.Stores entity.ActivityOutput = data.ActivityOutput; entity.BlockingActivities = data.BlockingActivities; entity.ScheduledActivities = data.ScheduledActivities; + entity.Scopes = data.Scopes ?? new Stack(); entity.Fault = data.Fault; } }