From 65e35dd3cb8644f2f55c353cbce95bc36b69f98b Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 10 Aug 2021 19:41:58 +0200 Subject: [PATCH] Fix an issue with re-scheduling scopes while workflow is Suspended This solves an issue when a workflow uses a fork activity where multiple branches get scheduled, but one of them contains a blocking activity, putting the workflow in the Suspended state. But as the other scheduled activities execute and "unwind" when the leaf node finished, their current scope weren't being re-scheduled. --- ...cheduleBranchingActivitiesAndContainers.cs | 64 ++++++++++++++----- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs b/src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs index 6d97dbed3..9d16df68e 100644 --- a/src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs +++ b/src/core/Elsa.Core/Handlers/RescheduleBranchingActivitiesAndContainers.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Elsa.Events; using Elsa.Models; +using Elsa.Services.Models; using MediatR; namespace Elsa.Handlers @@ -16,34 +17,65 @@ namespace Elsa.Handlers { var activityExecutionContext = notification.ActivityExecutionContext; var workflowExecutionContext = activityExecutionContext.WorkflowExecutionContext; - - // Check to see if a suspension / completion has been instructed. If so, do nothing. - if (workflowExecutionContext.HasScheduledActivities || workflowExecutionContext.Status != WorkflowStatus.Running) - return Task.CompletedTask; - - // Check if we are within a scope. - if (!workflowExecutionContext.WorkflowInstance.Scopes.Any()) - return Task.CompletedTask; - - // Select the scope that is within the activity's inbound trajectory. Include current activity for consideration (it might itself be a scope). - var activityId = activityExecutionContext.ActivityId; - var inboundActivityIds = new[] { activityId }.Concat(workflowExecutionContext.GetInboundActivityPath(activityId)).ToHashSet(); var workflowInstance = workflowExecutionContext.WorkflowInstance; - var scopes = workflowInstance.Scopes; - var scope = scopes.FirstOrDefault(x => inboundActivityIds.Contains(x.ActivityId)); + var scope = GetScope(activityExecutionContext); + // Check to see if a suspension / completion has been instructed. + if (workflowExecutionContext.HasScheduledActivities || workflowExecutionContext.Status != WorkflowStatus.Running) + { + // If any blocking activity is within the current scope, exit. + var anyInScope = scope == null || IsAnyBlockingActivityInScope(workflowExecutionContext, scope.ActivityId); + + if(anyInScope || workflowExecutionContext.Status == WorkflowStatus.Finished) + return Task.CompletedTask; + } + if (scope == null) { - // Not scope in scope. + // No scope to reschedule. return Task.CompletedTask; } // Re-schedule the current scope activity. - scopes.Remove(scope); + workflowInstance.Scopes.Remove(scope); workflowInstance.ActivityData.GetItem(scope.ActivityId)!.SetState("Unwinding", true); workflowExecutionContext.ScheduleActivity(scope.ActivityId); return Task.CompletedTask; } + + private bool IsAnyBlockingActivityInScope(WorkflowExecutionContext workflowExecutionContext, string scopeActivityId) + { + // Get all blocking activity IDs. + var blockingActivityIds = workflowExecutionContext.WorkflowInstance.BlockingActivities.Select(x => x.ActivityId).ToHashSet(); + + // For each blocking activity, check if it is within the current scope (taking the first one). + foreach (var blockingActivityId in blockingActivityIds) + { + var inboundActivityIds = new[] { blockingActivityId }.Concat(workflowExecutionContext.GetInboundActivityPath(blockingActivityId)).ToHashSet(); + if (inboundActivityIds.Contains(scopeActivityId)) + return true; + } + + return false; + } + + private ActivityScope? GetScope(ActivityExecutionContext context) + { + var workflowExecutionContext = context.WorkflowExecutionContext; + + // Check if we are within a scope. + if (!workflowExecutionContext.WorkflowInstance.Scopes.Any()) + return null; + + // Select the scope that is within the activity's inbound trajectory. Include current activity for consideration (it might itself be a scope). + var activityId = context.ActivityId; + var inboundActivityIds = new[] { activityId }.Concat(workflowExecutionContext.GetInboundActivityPath(activityId)).ToHashSet(); + var workflowInstance = workflowExecutionContext.WorkflowInstance; + var scopes = workflowInstance.Scopes; + var scope = scopes.FirstOrDefault(x => inboundActivityIds.Contains(x.ActivityId)); + + return scope; + } } } \ No newline at end of file