From 86efb968c46a94efa50e213c6cbda9398c4ebe90 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 9 Sep 2021 21:06:11 +0200 Subject: [PATCH] Implement workflow instance deleter with support for locking --- .../Workflows/IWorkflowInstanceDeleter.cs | 19 +++++++++++ .../LockingWorkflowInstanceDeleter.cs | 33 +++++++++++++++++++ .../ElsaServiceCollectionExtensions.cs | 25 +++++++------- .../Workflows/WorkflowInstanceDeleter.cs | 28 ++++++++++++++++ .../Endpoints/WorkflowInstances/Delete.cs | 14 ++++---- 5 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 src/core/Elsa.Abstractions/Services/Workflows/IWorkflowInstanceDeleter.cs create mode 100644 src/core/Elsa.Core/Decorators/LockingWorkflowInstanceDeleter.cs create mode 100644 src/core/Elsa.Core/Services/Workflows/WorkflowInstanceDeleter.cs diff --git a/src/core/Elsa.Abstractions/Services/Workflows/IWorkflowInstanceDeleter.cs b/src/core/Elsa.Abstractions/Services/Workflows/IWorkflowInstanceDeleter.cs new file mode 100644 index 000000000..c73c264a2 --- /dev/null +++ b/src/core/Elsa.Abstractions/Services/Workflows/IWorkflowInstanceDeleter.cs @@ -0,0 +1,19 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Models; + +namespace Elsa.Services +{ + public interface IWorkflowInstanceDeleter + { + Task DeleteAsync(string workflowInstanceId, CancellationToken cancellationToken = default); + } + + public record DeleteWorkflowInstanceResult(DeleteWorkflowInstanceResultStatus Status, WorkflowInstance? WorkflowInstance); + + public enum DeleteWorkflowInstanceResultStatus + { + Ok, + NotFound + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Decorators/LockingWorkflowInstanceDeleter.cs b/src/core/Elsa.Core/Decorators/LockingWorkflowInstanceDeleter.cs new file mode 100644 index 000000000..d4c3c34d5 --- /dev/null +++ b/src/core/Elsa.Core/Decorators/LockingWorkflowInstanceDeleter.cs @@ -0,0 +1,33 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Exceptions; +using Elsa.Options; +using Elsa.Services; + +namespace Elsa.Decorators +{ + public class LockingWorkflowInstanceDeleter : IWorkflowInstanceDeleter + { + private readonly IWorkflowInstanceDeleter _workflowInstanceDeleter; + private readonly IDistributedLockProvider _distributedLockProvider; + private readonly ElsaOptions _elsaOptions; + + public LockingWorkflowInstanceDeleter(IWorkflowInstanceDeleter workflowInstanceDeleter, IDistributedLockProvider distributedLockProvider, ElsaOptions elsaOptions) + { + _workflowInstanceDeleter = workflowInstanceDeleter; + _distributedLockProvider = distributedLockProvider; + _elsaOptions = elsaOptions; + } + + public async Task DeleteAsync(string workflowInstanceId, CancellationToken cancellationToken = default) + { + var workflowInstanceLockKey = $"workflow-instance:{workflowInstanceId}"; + await using var workflowInstanceLockHandle = await _distributedLockProvider.AcquireLockAsync(workflowInstanceLockKey, _elsaOptions.DistributedLockTimeout, cancellationToken); + + if (workflowInstanceLockHandle == null) + throw new LockAcquisitionException("Could not acquire a lock within the configured amount of time"); + + return await _workflowInstanceDeleter.DeleteAsync(workflowInstanceId, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index 4bbab3ece..b7694bb02 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -56,7 +56,7 @@ namespace Microsoft.Extensions.DependencyInjection { TypeDescriptor.AddAttributes(typeof(Type), new TypeConverterAttribute(typeof(TypeTypeConverter))); } - + public static IServiceCollection AddElsaCore( this IServiceCollection services, Action? configure = default) @@ -91,11 +91,13 @@ namespace Microsoft.Extensions.DependencyInjection .AddConfiguration() .AddCoreActivities(); - services.Decorate(); - services.Decorate(); - services.Decorate(); - services.Decorate(); - services.Decorate(); + services + .Decorate() + .Decorate() + .Decorate() + .Decorate() + .Decorate() + .Decorate(); //TenantId default source services.TryAddScoped(); @@ -128,7 +130,7 @@ namespace Microsoft.Extensions.DependencyInjection elsaOptions.Services.AddTransient, TConsumer>(); return elsaOptions; } - + public static ElsaOptionsBuilder AddPubSubConsumer(this ElsaOptionsBuilder elsaOptions, string? queueName = default) where TConsumer : class, IHandleMessages { elsaOptions.Services.AddTransient, TConsumer>(); @@ -139,7 +141,7 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection AddActivityPropertyOptionsProvider(this IServiceCollection services) where T : class, IActivityPropertyOptionsProvider => services.AddSingleton(); public static IServiceCollection AddRuntimeSelectItemsProvider(this IServiceCollection services) where T : class, IRuntimeSelectListItemsProvider => services.AddScoped(); public static IServiceCollection AddActivityTypeProvider(this IServiceCollection services) where T : class, IActivityTypeProvider => services.AddSingleton(); - + public static IServiceCollection AddWorkflowStorageProvider(this IServiceCollection services) where T : class, IWorkflowStorageProvider => services .AddSingleton() @@ -176,6 +178,7 @@ namespace Microsoft.Extensions.DependencyInjection .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddSingleton() .AddTransient() .AddSingleton() @@ -189,7 +192,7 @@ namespace Microsoft.Extensions.DependencyInjection .AddTransient() .AddTransient() ; - + // Data Protection. services.AddDataProtection(); @@ -215,14 +218,14 @@ namespace Microsoft.Extensions.DependencyInjection .AddWorkflowProvider(); services.Configure(o => o.BlobStorageFactory = StorageFactory.Blobs.InMemory); - + // Workflow Storage Providers. services .AddSingleton() .AddWorkflowStorageProvider() .AddWorkflowStorageProvider() .AddWorkflowStorageProvider(); - + services.Configure(o => o.BlobStorageFactory = StorageFactory.Blobs.InMemory); // Metadata. diff --git a/src/core/Elsa.Core/Services/Workflows/WorkflowInstanceDeleter.cs b/src/core/Elsa.Core/Services/Workflows/WorkflowInstanceDeleter.cs new file mode 100644 index 000000000..ec14c7bf6 --- /dev/null +++ b/src/core/Elsa.Core/Services/Workflows/WorkflowInstanceDeleter.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Persistence; + +namespace Elsa.Services.Workflows +{ + public class WorkflowInstanceDeleter : IWorkflowInstanceDeleter + { + private readonly IWorkflowInstanceStore _workflowInstanceStore; + + public WorkflowInstanceDeleter(IWorkflowInstanceStore workflowInstanceStore) + { + _workflowInstanceStore = workflowInstanceStore; + } + + public async Task DeleteAsync(string workflowInstanceId, CancellationToken cancellationToken = default) + { + var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken); + + if (workflowInstance == null) + return new DeleteWorkflowInstanceResult(DeleteWorkflowInstanceResultStatus.NotFound, null); + + await _workflowInstanceStore.DeleteAsync(workflowInstance, cancellationToken); + + return new DeleteWorkflowInstanceResult(DeleteWorkflowInstanceResultStatus.Ok, workflowInstance); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowInstances/Delete.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowInstances/Delete.cs index 69cedc60c..4503465b7 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WorkflowInstances/Delete.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowInstances/Delete.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; using Elsa.Persistence; +using Elsa.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; @@ -13,11 +14,11 @@ namespace Elsa.Server.Api.Endpoints.WorkflowInstances [Produces("application/json")] public class Delete : Controller { - private readonly IWorkflowInstanceStore _workflowInstanceStore; + private readonly IWorkflowInstanceDeleter _workflowInstanceDeleter; - public Delete(IWorkflowInstanceStore workflowInstanceStore) + public Delete(IWorkflowInstanceDeleter workflowInstanceDeleter) { - _workflowInstanceStore = workflowInstanceStore; + _workflowInstanceDeleter = workflowInstanceDeleter; } [HttpDelete] @@ -31,12 +32,11 @@ namespace Elsa.Server.Api.Endpoints.WorkflowInstances ] public async Task Handle(string id, CancellationToken cancellationToken = default) { - var workflowInstance = await _workflowInstanceStore.FindByIdAsync(id, cancellationToken); + var result = await _workflowInstanceDeleter.DeleteAsync(id, cancellationToken); - if (workflowInstance == null) + if (result.Status == DeleteWorkflowInstanceResultStatus.NotFound) return NotFound(); - - await _workflowInstanceStore.DeleteAsync(workflowInstance, cancellationToken); + return NoContent(); } }