From 69ceaeb12c240a6c84a83bc36e7f01be24394ed6 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 2 Feb 2024 21:08:49 +0100 Subject: [PATCH 1/8] Update cancellation process in ActivityExecutionContext The cancellation process in ActivityExecutionContext has been updated to now use DisposeAsync() instead of Dispose(). This implements asynchronous disposal of the cancellation registration. Additionally, a new method supporting cancellation has been added. --- .../Contexts/ActivityExecutionContext.Cancel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Cancel.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Cancel.cs index 3732bfeea..19882ba13 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Cancel.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Cancel.cs @@ -36,9 +36,10 @@ public partial class ActivityExecutionContext // Add an execution log entry. AddExecutionLogEntry("Canceled", payload: JournalData, includeActivityState: true); - _cancellationRegistration.Dispose(); - + await _cancellationRegistration.DisposeAsync(); await this.SendSignalAsync(new CancelSignal()); + + // ReSharper disable once MethodSupportsCancellation await _publisher.SendAsync(new ActivityCancelled(this)); } } \ No newline at end of file From 838e26eb2bc8db7ac684c8c41e05e8966951b8f9 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 2 Feb 2024 21:09:28 +0100 Subject: [PATCH 2/8] Refactor variable handling in ExpressionExecutionContextExtensions The code has been restructured to account for cases where there is no activity execution context available. Instead of breaking the loop in such cases, the variables are derived from the memory blocks. Also, there is minor code formatting in output handling and array conversion for async enumerables. --- .../ExpressionExecutionContextExtensions.cs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs index e594dd0e6..385d5477a 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs @@ -310,12 +310,23 @@ public static class ExpressionExecutionContextExtensions while (currentScope != null) { if (!currentScope.TryGetActivityExecutionContext(out var activityExecutionContext)) - break; + { + var variables = currentScope.Memory.Blocks.Values + .Where(x => x.Metadata is VariableBlockMetadata) + .Select(x => x.Metadata as VariableBlockMetadata) + .Select(x => x!.Variable) + .ToList(); - var variables = activityExecutionContext.Variables; + foreach (var variable in variables) + yield return variable; + } + else + { + var variables = activityExecutionContext.Variables; - foreach (var variable in variables) - yield return variable; + foreach (var variable in variables) + yield return variable; + } currentScope = currentScope.ParentContext; } @@ -413,7 +424,10 @@ public static class ExpressionExecutionContextExtensions foreach (var output in activityDescriptor.Outputs) { var outputPascalName = output.Name.Pascalize(); - yield return new ActivityOutputs(activity.Id, activityIdPascalName, new[] { outputPascalName }); + yield return new ActivityOutputs(activity.Id, activityIdPascalName, new[] + { + outputPascalName + }); } } } @@ -486,7 +500,7 @@ public static class ExpressionExecutionContextExtensions // If this is an async enumerable, return as-is. if (obj.GetType().Name == "AsyncIListEnumerableAdapter`1") return obj; - + // Use LINQ to convert the IEnumerable to an array. var elementType = obj.GetType().GetGenericArguments().FirstOrDefault(); @@ -494,6 +508,9 @@ public static class ExpressionExecutionContextExtensions return obj; var toArrayMethod = typeof(Enumerable).GetMethod("ToArray")!.MakeGenericMethod(elementType); - return toArrayMethod.Invoke(null, new object[] { enumerable })!; + return toArrayMethod.Invoke(null, new object[] + { + enumerable + })!; } } \ No newline at end of file From 7560bfa756dd61b397779d356d8315cb90f66de3 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 2 Feb 2024 21:09:48 +0100 Subject: [PATCH 3/8] Improve error handling in JavaScript Evaluator Introduces a safeguard in the JintJavaScriptEvaluator in the Elsa.JavaScript module. This checks whether a workflow execution context can be retrieved before trying to get its input definitions. This prevents potential errors when the context isn't available. --- .../Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs index 56d38b6a0..557851197 100644 --- a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs +++ b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs @@ -132,7 +132,11 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator return; var inputs = context.GetWorkflowInputs().ToDictionary(x => x.Name); - var inputDefinitions = context.GetWorkflowExecutionContext().Workflow.Inputs; + + if (!context.TryGetWorkflowExecutionContext(out var workflowExecutionContext)) + return; + + var inputDefinitions = workflowExecutionContext.Workflow.Inputs; foreach (var inputDefinition in inputDefinitions) { From b3d8e65c7e87104700b8c28225ca2191a80570c7 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 2 Feb 2024 21:10:14 +0100 Subject: [PATCH 4/8] Remove unused namespaces in Tests.cs The commit removes unnecessary namespaces in the integration tests for JavaScript Lists and Arrays within the Elsa project. This results in a cleaner codebase, maintaining only the libraries required for the specific tests. --- .../Scenarios/JavaScriptListsAndArrays/Tests.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/integration/Elsa.IntegrationTests/Scenarios/JavaScriptListsAndArrays/Tests.cs b/test/integration/Elsa.IntegrationTests/Scenarios/JavaScriptListsAndArrays/Tests.cs index 0b02ed905..e0302646a 100644 --- a/test/integration/Elsa.IntegrationTests/Scenarios/JavaScriptListsAndArrays/Tests.cs +++ b/test/integration/Elsa.IntegrationTests/Scenarios/JavaScriptListsAndArrays/Tests.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; using System.Dynamic; -using System.Threading.Tasks; using Elsa.Expressions.Models; using Elsa.Extensions; using Elsa.JavaScript.Contracts; using Elsa.Testing.Shared; -using Jint; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; From 2147683c2ee334c6d05863c0a5f5ba2389c125c7 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 2 Feb 2024 21:10:36 +0100 Subject: [PATCH 5/8] Remove child contexts of completed workflows The updated code cleans the child contexts of any completed contexts from the list of workflows currently in execution. This change reduces the clutter and potential complications in the context management process. Previously, the unnecessary child contexts were being stored even after the parent context has been completed. --- .../Services/WorkflowStateExtractor.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs b/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs index 942d8931b..bc2cdefab 100644 --- a/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs +++ b/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs @@ -255,12 +255,12 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor { var contexts = activityExecutionContexts.ToList(); - // // If there are any faulted contexts, keep everything so that the user can fix the issue and potentially reschedule existing instances. - // if (contexts.Any(x => x.Status == ActivityStatus.Faulted)) + // Remove all child contexts of completed contexts. + foreach (var context in contexts.ToList().Where(context => context.IsCompleted)) + { + contexts.RemoveAll(x => x.ParentActivityExecutionContext == context); + } + return contexts; - - // return contexts - // .Where(x => !x.IsCompleted) - // .ToList(); } } \ No newline at end of file From e5de5934e6a87bba8869f47ad13746f0ded0b40a Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 2 Feb 2024 21:38:25 +0100 Subject: [PATCH 6/8] Prevent duplicate job scheduling in Quartz Updated the QuartzWorkflowScheduler in Elsa to prevent duplicate job scheduling. Before scheduling a job, the existence of the job is now checked first. This change has been applied to all job types - timestamp, simple schedule, and cron jobs. --- .../Elsa.Quartz/Services/QuartzWorkflowScheduler.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/modules/Elsa.Quartz/Services/QuartzWorkflowScheduler.cs b/src/modules/Elsa.Quartz/Services/QuartzWorkflowScheduler.cs index 14407ce3f..57703adf9 100644 --- a/src/modules/Elsa.Quartz/Services/QuartzWorkflowScheduler.cs +++ b/src/modules/Elsa.Quartz/Services/QuartzWorkflowScheduler.cs @@ -47,7 +47,9 @@ public class QuartzWorkflowScheduler : IWorkflowScheduler .WithIdentity(taskName) .StartAt(at) .Build(); - await scheduler.ScheduleJob(trigger, cancellationToken); + + if(!await scheduler.CheckExists(trigger.Key, cancellationToken)) + await scheduler.ScheduleJob(trigger, cancellationToken); } /// @@ -77,7 +79,9 @@ public class QuartzWorkflowScheduler : IWorkflowScheduler .StartAt(startAt) .WithSimpleSchedule(schedule => schedule.WithInterval(interval).RepeatForever()) .Build(); - await scheduler.ScheduleJob(trigger, cancellationToken); + + if(!await scheduler.CheckExists(trigger.Key, cancellationToken)) + await scheduler.ScheduleJob(trigger, cancellationToken); } /// @@ -99,7 +103,9 @@ public class QuartzWorkflowScheduler : IWorkflowScheduler .UsingJobData(CreateJobDataMap(request)) .WithIdentity(taskName) .WithCronSchedule(cronExpression).Build(); - await scheduler.ScheduleJob(trigger, cancellationToken); + + if(!await scheduler.CheckExists(trigger.Key, cancellationToken)) + await scheduler.ScheduleJob(trigger, cancellationToken); } /// From 6b2eea3502c21d7d852feacd314976a0859eb739 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 2 Feb 2024 21:38:51 +0100 Subject: [PATCH 7/8] Optimize activity execution contexts filtering Removed manual removal of child contexts from the list of active activity execution contexts. Replaced it with more efficient direct filtering of not completed activity execution contexts. This change may affect scripts that access activity output directly, but a variable capturing workaround is provided. --- .../Services/WorkflowStateExtractor.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs b/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs index bc2cdefab..9d95a4d4c 100644 --- a/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs +++ b/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs @@ -253,14 +253,9 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor private static IEnumerable GetActiveActivityExecutionContexts(IEnumerable activityExecutionContexts) { - var contexts = activityExecutionContexts.ToList(); - - // Remove all child contexts of completed contexts. - foreach (var context in contexts.ToList().Where(context => context.IsCompleted)) - { - contexts.RemoveAll(x => x.ParentActivityExecutionContext == context); - } - - return contexts; + // Filter out completed activity execution contexts. + // This will currently break scripts accessing activity output directly, but there's a workaround for that via variable capturing. + // We may ultimately restore direct output access, but in a different way. + return activityExecutionContexts.Where(x => !x.IsCompleted).ToList(); } } \ No newline at end of file From 9c69eebe940912630d1391bc6d1dc86f33cbcc7f Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 2 Feb 2024 21:39:08 +0100 Subject: [PATCH 8/8] Add 'Unschedule' to UserDictionary The term 'Unschedule' has been included to the UserDictionary within the Elsa solution settings. This update shows that 'Unschedule' is recognized in the context of the solution, thereby reducing the risk of related typographical errors. --- Elsa.sln.DotSettings | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Elsa.sln.DotSettings b/Elsa.sln.DotSettings index 0ea8896b9..05601a597 100644 --- a/Elsa.sln.DotSettings +++ b/Elsa.sln.DotSettings @@ -10,4 +10,5 @@ True True True - True \ No newline at end of file + True + True \ No newline at end of file