From cf2ac05e92e5f3e922052385cea29ba92696836f Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 6 Feb 2025 10:44:04 +0100 Subject: [PATCH 1/8] Refactor ArgumentJsonConverter to improve type handling. Enhanced type handling by distinguishing arrays, collections, and single types. Added support for "isCollection" metadata and adjusted serialization/deserialization logic to ensure accurate mapping of element types and aliases. This improves clarity and flexibility in JSON representation. --- .../Serialization/ArgumentJsonConverter.cs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs b/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs index 81f5a0d42..fc829f41c 100644 --- a/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs @@ -28,10 +28,18 @@ public class ArgumentJsonConverter : JsonConverter newOptions.Converters.RemoveWhere(x => x is ArgumentJsonConverterFactory); var jsonObject = (JsonObject)JsonSerializer.SerializeToNode(value, value.GetType(), newOptions)!; - var isArray = value.Type.IsCollectionType(); - jsonObject["isArray"] = isArray; - jsonObject["type"] = _wellKnownTypeRegistry.GetAliasOrDefault(isArray ? value.Type.GetCollectionElementType() : value.Type); - + var typeName = value.Type; + var typeAlias = _wellKnownTypeRegistry.TryGetAlias(typeName, out var alias) ? alias : null; + var isArray = typeName.IsArray; + var isCollection = typeName.IsCollectionType(); + var elementTypeName = isArray ? typeName.GetElementType() : isCollection ? typeName.GenericTypeArguments[0] : typeName; + var elementTypeAlias = _wellKnownTypeRegistry.GetAliasOrDefault(elementTypeName); + var finalTypeAlias = isArray || isCollection ? elementTypeAlias : typeAlias; + + if(isArray) jsonObject["isArray"] = isArray; + if(isCollection) jsonObject["isCollection"] = isCollection; + + jsonObject["type"] = finalTypeAlias; JsonSerializer.Serialize(writer, jsonObject, newOptions); } @@ -40,11 +48,12 @@ public class ArgumentJsonConverter : JsonConverter { var jsonObject = (JsonObject)JsonNode.Parse(ref reader)!; var isArray = jsonObject["isArray"]?.GetValue() ?? false; - var typeName = jsonObject["type"]!.GetValue(); - var type = _wellKnownTypeRegistry.GetTypeOrDefault(typeName); + var isCollection = jsonObject["isCollection"]?.GetValue() ?? false; + var typeAlias = jsonObject["type"]!.GetValue(); + var type = _wellKnownTypeRegistry.GetTypeOrDefault(typeAlias); - if (isArray) - type = type.MakeArrayType(); + if (isArray) type = type.MakeArrayType(); + if (isCollection) type = type.MakeGenericType(type.GenericTypeArguments[0]); var newOptions = new JsonSerializerOptions(options); newOptions.Converters.RemoveWhere(x => x is ArgumentJsonConverterFactory); From babf1125ff5cd3d30de9975979f11635fe79c28e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 6 Feb 2025 10:56:43 +0100 Subject: [PATCH 2/8] Refactor ArgumentJsonConverter to improve type alias handling Introduced logic to handle type aliases more accurately for arrays and collections. Enhanced the final type alias determination by accommodating cases where type aliases are present. Cleaned up unnecessary whitespace for better code readability. --- .../Serialization/ArgumentJsonConverter.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs b/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs index fc829f41c..2af9c2334 100644 --- a/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs @@ -20,13 +20,13 @@ public class ArgumentJsonConverter : JsonConverter { _wellKnownTypeRegistry = wellKnownTypeRegistry; } - + /// public override void Write(Utf8JsonWriter writer, ArgumentDefinition value, JsonSerializerOptions options) { var newOptions = new JsonSerializerOptions(options); newOptions.Converters.RemoveWhere(x => x is ArgumentJsonConverterFactory); - + var jsonObject = (JsonObject)JsonSerializer.SerializeToNode(value, value.GetType(), newOptions)!; var typeName = value.Type; var typeAlias = _wellKnownTypeRegistry.TryGetAlias(typeName, out var alias) ? alias : null; @@ -34,11 +34,12 @@ public class ArgumentJsonConverter : JsonConverter var isCollection = typeName.IsCollectionType(); var elementTypeName = isArray ? typeName.GetElementType() : isCollection ? typeName.GenericTypeArguments[0] : typeName; var elementTypeAlias = _wellKnownTypeRegistry.GetAliasOrDefault(elementTypeName); - var finalTypeAlias = isArray || isCollection ? elementTypeAlias : typeAlias; - - if(isArray) jsonObject["isArray"] = isArray; - if(isCollection) jsonObject["isCollection"] = isCollection; - + var isAliasedArray = (isArray || isCollection) && typeAlias != null; + var finalTypeAlias = isArray || isCollection ? typeAlias ?? elementTypeAlias : elementTypeAlias; + + if (isArray && !isAliasedArray) jsonObject["isArray"] = isArray; + if (isCollection) jsonObject["isCollection"] = isCollection; + jsonObject["type"] = finalTypeAlias; JsonSerializer.Serialize(writer, jsonObject, newOptions); } From 7a758f88502108dccfd9959747eec14d409d3171 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 6 Feb 2025 16:39:38 +0100 Subject: [PATCH 3/8] Fix multitenancy support in Hangfire services and jobs Refactored Hangfire-related services and jobs to include tenant context management using ITenantAccessor and ITenantFinder. Updated job constructors and method signatures to support tenant-specific execution. Improved exception-throwing syntax for better readability in BackgroundActivityInvoker. --- .../Implementations/DefaultTenantAccessor.cs | 2 +- .../Jobs/ExecuteBackgroundActivityJob.cs | 22 ++++----- .../Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs | 11 +++-- .../Elsa.Hangfire/Jobs/RunWorkflowJob.cs | 9 ++-- .../HangfireBackgroundActivityScheduler.cs | 9 ++-- .../Services/HangfireWorkflowScheduler.cs | 45 +++++++++---------- .../Services/BackgroundActivityInvoker.cs | 4 +- 7 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs index fd8a699ba..146dfd451 100644 --- a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs +++ b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs @@ -11,7 +11,7 @@ public class DefaultTenantAccessor : ITenantAccessor public Tenant? Tenant { get => CurrentTenantField.Value; - internal set => CurrentTenantField.Value = value; + private set => CurrentTenantField.Value = value; } public IDisposable PushContext(Tenant? tenant) diff --git a/src/modules/Elsa.Hangfire/Jobs/ExecuteBackgroundActivityJob.cs b/src/modules/Elsa.Hangfire/Jobs/ExecuteBackgroundActivityJob.cs index 5a9f2622c..0909cba4c 100644 --- a/src/modules/Elsa.Hangfire/Jobs/ExecuteBackgroundActivityJob.cs +++ b/src/modules/Elsa.Hangfire/Jobs/ExecuteBackgroundActivityJob.cs @@ -1,28 +1,22 @@ +using Elsa.Common.Multitenancy; using Elsa.Workflows.Runtime; +using JetBrains.Annotations; namespace Elsa.Hangfire.Jobs; /// /// A job that executes a background activity. /// -public class ExecuteBackgroundActivityJob +[UsedImplicitly] +public class ExecuteBackgroundActivityJob(IBackgroundActivityInvoker backgroundActivityInvoker, ITenantFinder tenantFinder, ITenantAccessor tenantAccessor) { - private readonly IBackgroundActivityInvoker _backgroundActivityInvoker; - - /// - /// Initializes a new instance of the class. - /// - /// - public ExecuteBackgroundActivityJob(IBackgroundActivityInvoker backgroundActivityInvoker) - { - _backgroundActivityInvoker = backgroundActivityInvoker; - } - /// /// Executes the job. /// - public async Task ExecuteAsync(ScheduledBackgroundActivity scheduledBackgroundActivity, CancellationToken cancellationToken = default) + public async Task ExecuteAsync(ScheduledBackgroundActivity scheduledBackgroundActivity, string? tenantId, CancellationToken cancellationToken = default) { - await _backgroundActivityInvoker.ExecuteAsync(scheduledBackgroundActivity, cancellationToken); + var tenant = tenantId != null ? await tenantFinder.FindByIdAsync(tenantId, cancellationToken) : null; + using var scope = tenantAccessor.PushContext(tenant); + await backgroundActivityInvoker.ExecuteAsync(scheduledBackgroundActivity, cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs b/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs index c0bc31706..6794264c8 100644 --- a/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs +++ b/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs @@ -1,4 +1,5 @@ -using Elsa.Scheduling; +using Elsa.Common.Multitenancy; +using Elsa.Scheduling; using Elsa.Workflows.Runtime; using Elsa.Workflows.Runtime.Messages; @@ -7,16 +8,18 @@ namespace Elsa.Hangfire.Jobs; /// /// A job that resumes a workflow. /// -public class ResumeWorkflowJob(IWorkflowRuntime workflowRuntime) +public class ResumeWorkflowJob(IWorkflowRuntime workflowRuntime, ITenantFinder tenantFinder, ITenantAccessor tenantAccessor) { /// /// Executes the job. /// - /// The name of the job. /// The workflow request. + /// The ID of the current tenant scheduling this job. /// The cancellation token. - public async Task ExecuteAsync(string name, ScheduleExistingWorkflowInstanceRequest request, CancellationToken cancellationToken) + public async Task ExecuteAsync(ScheduleExistingWorkflowInstanceRequest request, string? tenantId, CancellationToken cancellationToken) { + var tenant = tenantId != null ? await tenantFinder.FindByIdAsync(tenantId, cancellationToken) : null; + using var scope = tenantAccessor.PushContext(tenant); var client = await workflowRuntime.CreateClientAsync(request.WorkflowInstanceId, cancellationToken); var runRequest = new RunWorkflowInstanceRequest { diff --git a/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs b/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs index 708d75a44..29e38f012 100644 --- a/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs +++ b/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs @@ -1,3 +1,4 @@ +using Elsa.Common.Multitenancy; using Elsa.Scheduling; using Elsa.Workflows.Runtime; using Elsa.Workflows.Runtime.Messages; @@ -7,16 +8,18 @@ namespace Elsa.Hangfire.Jobs; /// /// A job that resumes a workflow. /// -public class RunWorkflowJob(IWorkflowRuntime workflowRuntime) +public class RunWorkflowJob(IWorkflowRuntime workflowRuntime, ITenantFinder tenantFinder, ITenantAccessor tenantAccessor) { /// /// Executes the job. /// - /// The name of the job. /// The workflow request. + /// The ID of the current tenant scheduling this job. /// The cancellation token. - public async Task ExecuteAsync(string name, ScheduleNewWorkflowInstanceRequest request, CancellationToken cancellationToken) + public async Task ExecuteAsync(ScheduleNewWorkflowInstanceRequest request, string? tenantId, CancellationToken cancellationToken) { + var tenant = tenantId != null ? await tenantFinder.FindByIdAsync(tenantId, cancellationToken) : null; + using var scope = tenantAccessor.PushContext(tenant); var client = await workflowRuntime.CreateClientAsync(cancellationToken); var createAndRunRequest = new CreateAndRunWorkflowInstanceRequest { diff --git a/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs b/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs index 798d6cfd2..ab3ac2089 100644 --- a/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs +++ b/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs @@ -1,3 +1,4 @@ +using Elsa.Common.Multitenancy; using Elsa.Hangfire.Jobs; using Elsa.Hangfire.States; using Elsa.Workflows.Runtime; @@ -9,11 +10,12 @@ namespace Elsa.Hangfire.Services; /// /// Invokes activities from a background worker within the context of its workflow instance using Hangfire. /// -public class HangfireBackgroundActivityScheduler(IBackgroundJobClient backgroundJobClient) : IBackgroundActivityScheduler +public class HangfireBackgroundActivityScheduler(IBackgroundJobClient backgroundJobClient, ITenantAccessor tenantAccessor) : IBackgroundActivityScheduler { public Task CreateAsync(ScheduledBackgroundActivity scheduledBackgroundActivity, CancellationToken cancellationToken = default) { - var jobId = backgroundJobClient.Create(x => x.ExecuteAsync(scheduledBackgroundActivity, CancellationToken.None), new PendingState()); + var tenantId = tenantAccessor.Tenant?.Id; + var jobId = backgroundJobClient.Create(x => x.ExecuteAsync(scheduledBackgroundActivity, tenantId, CancellationToken.None), new PendingState()); return Task.FromResult(jobId); } @@ -28,7 +30,8 @@ public class HangfireBackgroundActivityScheduler(IBackgroundJobClient background /// public Task ScheduleAsync(ScheduledBackgroundActivity scheduledBackgroundActivity, CancellationToken cancellationToken = default) { - var jobId = backgroundJobClient.Enqueue(x => x.ExecuteAsync(scheduledBackgroundActivity, CancellationToken.None)); + var tenantId = tenantAccessor.Tenant?.Id; + var jobId = backgroundJobClient.Enqueue(x => x.ExecuteAsync(scheduledBackgroundActivity, tenantId, CancellationToken.None)); return Task.FromResult(jobId); } diff --git a/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs b/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs index 802555b3c..58ffab6c9 100644 --- a/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs +++ b/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs @@ -1,3 +1,4 @@ +using Elsa.Common.Multitenancy; using Elsa.Hangfire.Extensions; using Elsa.Hangfire.Jobs; using Elsa.Scheduling; @@ -9,33 +10,25 @@ namespace Elsa.Hangfire.Services; /// /// An implementation of that uses Hangfire. /// -public class HangfireWorkflowScheduler : IWorkflowScheduler +public class HangfireWorkflowScheduler( + IBackgroundJobClient backgroundJobClient, + IRecurringJobManager recurringJobManager, + ITenantAccessor tenantAccessor, + JobStorage jobStorage) : IWorkflowScheduler { - private readonly IBackgroundJobClient _backgroundJobClient; - private readonly IRecurringJobManager _recurringJobManager; - private readonly JobStorage _jobStorage; - - /// - /// Initializes a new instance of the class. - /// - public HangfireWorkflowScheduler(IBackgroundJobClient backgroundJobClient, IRecurringJobManager recurringJobManager, JobStorage jobStorage) - { - _backgroundJobClient = backgroundJobClient; - _recurringJobManager = recurringJobManager; - _jobStorage = jobStorage; - } - /// public ValueTask ScheduleAtAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default) { - _backgroundJobClient.Schedule(job => job.ExecuteAsync(taskName, request, CancellationToken.None), at); + var tenantId = tenantAccessor.Tenant?.Id; + backgroundJobClient.Schedule(job => job.ExecuteAsync(request, tenantId, CancellationToken.None), at); return ValueTask.CompletedTask; } /// public ValueTask ScheduleAtAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default) { - _backgroundJobClient.Schedule(job => job.ExecuteAsync(taskName, request, CancellationToken.None), at); + var tenantId = tenantAccessor.Tenant?.Id; + backgroundJobClient.Schedule(job => job.ExecuteAsync(request, tenantId, CancellationToken.None), at); return ValueTask.CompletedTask; } @@ -54,14 +47,16 @@ public class HangfireWorkflowScheduler : IWorkflowScheduler /// public ValueTask ScheduleCronAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default) { - _recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(taskName, request, CancellationToken.None), cronExpression); + var tenantId = tenantAccessor.Tenant?.Id; + recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(request, tenantId, CancellationToken.None), cronExpression); return ValueTask.CompletedTask; } /// public ValueTask ScheduleCronAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default) { - _recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(taskName, request, CancellationToken.None), cronExpression); + var tenantId = tenantAccessor.Tenant?.Id; + recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(request, tenantId, CancellationToken.None), cronExpression); return ValueTask.CompletedTask; } @@ -75,18 +70,18 @@ public class HangfireWorkflowScheduler : IWorkflowScheduler private void DeleteJobByTaskName(string taskName) { var scheduledJobIds = GetScheduledJobIds(taskName); - foreach (var jobId in scheduledJobIds) _backgroundJobClient.Delete(jobId); + foreach (var jobId in scheduledJobIds) backgroundJobClient.Delete(jobId); var queuedJobsIds = GetQueuedJobIds(taskName); - foreach (var jobId in queuedJobsIds) _backgroundJobClient.Delete(jobId); + foreach (var jobId in queuedJobsIds) backgroundJobClient.Delete(jobId); var recurringJobIds = GetRecurringJobIds(taskName); - foreach (var jobId in recurringJobIds) _recurringJobManager.RemoveIfExists(jobId); + foreach (var jobId in recurringJobIds) recurringJobManager.RemoveIfExists(jobId); } private IEnumerable GetScheduledJobIds(string taskName) { - return _jobStorage.EnumerateScheduledJobs(taskName) + return jobStorage.EnumerateScheduledJobs(taskName) .Select(x => x.Key) .Distinct() .ToList(); @@ -94,7 +89,7 @@ public class HangfireWorkflowScheduler : IWorkflowScheduler private IEnumerable GetQueuedJobIds(string taskName) { - return _jobStorage.EnumerateQueuedJobs("default", taskName) + return jobStorage.EnumerateQueuedJobs("default", taskName) .Select(x => x.Key) .Distinct() .ToList(); @@ -102,7 +97,7 @@ public class HangfireWorkflowScheduler : IWorkflowScheduler private IEnumerable GetRecurringJobIds(string taskName) { - using var connection = _jobStorage.GetConnection(); + using var connection = jobStorage.GetConnection(); var jobs = connection.GetRecurringJobs().Where(x => x.Job.Type == typeof(TJob) && (string)x.Job.Args[0] == taskName); return jobs.Select(x => x.Id).Distinct().ToList(); } diff --git a/src/modules/Elsa.Workflows.Runtime/Services/BackgroundActivityInvoker.cs b/src/modules/Elsa.Workflows.Runtime/Services/BackgroundActivityInvoker.cs index c2541fe79..b5082642f 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/BackgroundActivityInvoker.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/BackgroundActivityInvoker.cs @@ -29,10 +29,10 @@ public class BackgroundActivityInvoker( { var workflowInstanceId = scheduledBackgroundActivity.WorkflowInstanceId; var workflowInstance = await workflowInstanceManager.FindByIdAsync(workflowInstanceId, cancellationToken); - if (workflowInstance == null) throw new Exception("Workflow instance not found"); + if (workflowInstance == null) throw new("Workflow instance not found"); var workflowState = workflowInstance.WorkflowState; var workflow = await workflowDefinitionService.FindWorkflowGraphAsync(workflowInstance.DefinitionVersionId, cancellationToken); - if (workflow == null) throw new Exception("Workflow definition not found"); + if (workflow == null) throw new("Workflow definition not found"); var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(serviceProvider, workflow, workflowState, cancellationToken: cancellationToken); var activityNodeId = scheduledBackgroundActivity.ActivityNodeId; var activityExecutionContext = workflowExecutionContext.ActivityExecutionContexts.First(x => x.NodeId == activityNodeId); From 1f78dbae57fa8108d96f81436e8b45e7dd38557b Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 6 Feb 2025 16:58:01 +0100 Subject: [PATCH 4/8] Add taskName parameter to job execution methods This change updates job scheduling and execution methods to include a taskName parameter. It ensures a unique identifier is passed for each job, improving tracking and consistency in job handling. Additionally, documentation comments were updated to reflect this new parameter. --- src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs | 4 +++- src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs | 4 +++- .../Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs | 8 ++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs b/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs index 6794264c8..4b4d19de8 100644 --- a/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs +++ b/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs @@ -13,10 +13,12 @@ public class ResumeWorkflowJob(IWorkflowRuntime workflowRuntime, ITenantFinder t /// /// Executes the job. /// + /// A unique name for this job. /// The workflow request. /// The ID of the current tenant scheduling this job. /// The cancellation token. - public async Task ExecuteAsync(ScheduleExistingWorkflowInstanceRequest request, string? tenantId, CancellationToken cancellationToken) + // ReSharper disable once UnusedParameter.Global + public async Task ExecuteAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, string? tenantId, CancellationToken cancellationToken) { var tenant = tenantId != null ? await tenantFinder.FindByIdAsync(tenantId, cancellationToken) : null; using var scope = tenantAccessor.PushContext(tenant); diff --git a/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs b/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs index 29e38f012..54b32dd3c 100644 --- a/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs +++ b/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs @@ -13,10 +13,12 @@ public class RunWorkflowJob(IWorkflowRuntime workflowRuntime, ITenantFinder tena /// /// Executes the job. /// + /// A unique name for this job. /// The workflow request. /// The ID of the current tenant scheduling this job. /// The cancellation token. - public async Task ExecuteAsync(ScheduleNewWorkflowInstanceRequest request, string? tenantId, CancellationToken cancellationToken) + // ReSharper disable once UnusedParameter.Global + public async Task ExecuteAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, string? tenantId, CancellationToken cancellationToken) { var tenant = tenantId != null ? await tenantFinder.FindByIdAsync(tenantId, cancellationToken) : null; using var scope = tenantAccessor.PushContext(tenant); diff --git a/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs b/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs index 58ffab6c9..743af48e1 100644 --- a/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs +++ b/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs @@ -20,7 +20,7 @@ public class HangfireWorkflowScheduler( public ValueTask ScheduleAtAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default) { var tenantId = tenantAccessor.Tenant?.Id; - backgroundJobClient.Schedule(job => job.ExecuteAsync(request, tenantId, CancellationToken.None), at); + backgroundJobClient.Schedule(job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), at); return ValueTask.CompletedTask; } @@ -28,7 +28,7 @@ public class HangfireWorkflowScheduler( public ValueTask ScheduleAtAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default) { var tenantId = tenantAccessor.Tenant?.Id; - backgroundJobClient.Schedule(job => job.ExecuteAsync(request, tenantId, CancellationToken.None), at); + backgroundJobClient.Schedule(job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), at); return ValueTask.CompletedTask; } @@ -48,7 +48,7 @@ public class HangfireWorkflowScheduler( public ValueTask ScheduleCronAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default) { var tenantId = tenantAccessor.Tenant?.Id; - recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(request, tenantId, CancellationToken.None), cronExpression); + recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), cronExpression); return ValueTask.CompletedTask; } @@ -56,7 +56,7 @@ public class HangfireWorkflowScheduler( public ValueTask ScheduleCronAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default) { var tenantId = tenantAccessor.Tenant?.Id; - recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(request, tenantId, CancellationToken.None), cronExpression); + recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), cronExpression); return ValueTask.CompletedTask; } From 1081960e6d5d8b4ab1408dcc38fdca7ec19ef266 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 6 Feb 2025 17:09:56 +0100 Subject: [PATCH 5/8] Add 'patch/*' branch to workflow triggers Included 'patch/*' as a trigger for the GitHub Actions workflow to ensure automated processes cover patch branches. This aligns with the existing structure for branch-specific workflows. --- .github/workflows/packages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 189b2242c..101dd71b4 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -6,6 +6,7 @@ on: - 'main' - 'bug/*' - 'perf/*' + - 'patch/*' release: types: [ prereleased, published ] env: From 3800db783a61d0af63d2d5f54ef693b9c627264d Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 6 Feb 2025 18:24:01 +0100 Subject: [PATCH 6/8] Update branch filter in release condition for GitHub Actions Replaced 'origin/main' with 'origin/patch/3.3.2-rc2' in the workflow's branch filter. This ensures the release process targets the correct branch during specific GitHub events. --- .github/workflows/packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 101dd71b4..1864ebee4 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -43,7 +43,7 @@ jobs: run: | if [[ "${{ github.ref }}" == refs/tags/* && "${{ github.event_name }}" == "release" && ("${{ github.event.action }}" == "published" || "${{ github.event.action }}" == "prereleased")]]; then git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* - git branch --remote --contains | grep origin/main + git branch --remote --contains | grep origin/patch/3.3.2-rc2 else git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* git branch --remote --contains | grep origin/${BRANCH_NAME} From 9e395c2bceb593ba8cd4fcf36843ed37893ec7fa Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 6 Feb 2025 18:40:03 +0100 Subject: [PATCH 7/8] Update branch check in release workflow Replaces the hardcoded branch reference `patch/3.3.2-rc2` with `patch/3.3.2` to align with the proper naming convention. This ensures the workflow correctly identifies the intended branch during release events. --- .github/workflows/packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 1864ebee4..8d2f51ad2 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -43,7 +43,7 @@ jobs: run: | if [[ "${{ github.ref }}" == refs/tags/* && "${{ github.event_name }}" == "release" && ("${{ github.event.action }}" == "published" || "${{ github.event.action }}" == "prereleased")]]; then git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* - git branch --remote --contains | grep origin/patch/3.3.2-rc2 + git branch --remote --contains | grep origin/patch/3.3.2 else git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* git branch --remote --contains | grep origin/${BRANCH_NAME} From 5d21bcb4a99211f6dd1121a9d9039829f27040a6 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 6 Feb 2025 19:32:08 +0100 Subject: [PATCH 8/8] Improve null handling and add job retry configuration Updated null checks for job types in Hangfire extensions to prevent potential runtime errors. Added Hangfire's `AutomaticRetry` attribute to RunWorkflowJob and ResumeWorkflowJob to configure retry behavior. Simplified object initialization in WorkflowDefinitions List endpoint. --- src/modules/Elsa.Hangfire/Extensions/JobStorageExtensions.cs | 4 ++-- src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs | 2 ++ src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs | 2 ++ .../Endpoints/WorkflowDefinitions/List/Endpoint.cs | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/modules/Elsa.Hangfire/Extensions/JobStorageExtensions.cs b/src/modules/Elsa.Hangfire/Extensions/JobStorageExtensions.cs index 64ed51057..5036635eb 100644 --- a/src/modules/Elsa.Hangfire/Extensions/JobStorageExtensions.cs +++ b/src/modules/Elsa.Hangfire/Extensions/JobStorageExtensions.cs @@ -23,7 +23,7 @@ public static class JobStorageExtensions { scheduledJobs = api.ScheduledJobs(skip, take); - var jobs = scheduledJobs.FindAll(x => x.Value.Job.Type == typeof(RunWorkflowJob) || x.Value.Job.Type == typeof(ResumeWorkflowJob)); + var jobs = scheduledJobs.FindAll(x => x.Value.Job?.Type == typeof(RunWorkflowJob) || x.Value.Job?.Type == typeof(ResumeWorkflowJob)); foreach (var job in jobs.Where(x => (string)x.Value.Job.Args[0] == name)) yield return job; @@ -45,7 +45,7 @@ public static class JobStorageExtensions { enqueuedJobs = api.EnqueuedJobs(queueName, skip, take); - var jobs = enqueuedJobs.FindAll(x => x.Value.Job.Type == typeof(RunWorkflowJob) || x.Value.Job.Type == typeof(ResumeWorkflowJob)); + var jobs = enqueuedJobs.FindAll(x => x.Value.Job?.Type == typeof(RunWorkflowJob) || x.Value.Job?.Type == typeof(ResumeWorkflowJob)); foreach (var job in jobs.Where(x => (string)x.Value.Job.Args[0] == taskName)) yield return job; diff --git a/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs b/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs index 4b4d19de8..0290bb702 100644 --- a/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs +++ b/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs @@ -2,6 +2,7 @@ using Elsa.Scheduling; using Elsa.Workflows.Runtime; using Elsa.Workflows.Runtime.Messages; +using Hangfire; namespace Elsa.Hangfire.Jobs; @@ -18,6 +19,7 @@ public class ResumeWorkflowJob(IWorkflowRuntime workflowRuntime, ITenantFinder t /// The ID of the current tenant scheduling this job. /// The cancellation token. // ReSharper disable once UnusedParameter.Global + [AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Fail)] public async Task ExecuteAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, string? tenantId, CancellationToken cancellationToken) { var tenant = tenantId != null ? await tenantFinder.FindByIdAsync(tenantId, cancellationToken) : null; diff --git a/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs b/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs index 54b32dd3c..9520abc58 100644 --- a/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs +++ b/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs @@ -2,6 +2,7 @@ using Elsa.Common.Multitenancy; using Elsa.Scheduling; using Elsa.Workflows.Runtime; using Elsa.Workflows.Runtime.Messages; +using Hangfire; namespace Elsa.Hangfire.Jobs; @@ -18,6 +19,7 @@ public class RunWorkflowJob(IWorkflowRuntime workflowRuntime, ITenantFinder tena /// The ID of the current tenant scheduling this job. /// The cancellation token. // ReSharper disable once UnusedParameter.Global + [AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Fail)] public async Task ExecuteAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, string? tenantId, CancellationToken cancellationToken) { var tenant = tenantId != null ? await tenantFinder.FindByIdAsync(tenantId, cancellationToken) : null; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs index 230460257..77cf9deae 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs @@ -33,7 +33,7 @@ internal class List(IWorkflowDefinitionStore store, IWorkflowDefinitionLinker li { var versionOptions = string.IsNullOrWhiteSpace(request.VersionOptions) ? default(VersionOptions?) : VersionOptions.FromString(request.VersionOptions); - return new WorkflowDefinitionFilter + return new() { IsSystem = request.IsSystem, VersionOptions = versionOptions,