diff --git a/WorkflowCore.sln b/WorkflowCore.sln index dba44fab9..6fdcee18b 100644 --- a/WorkflowCore.sln +++ b/WorkflowCore.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26430.6 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EF47161E-E399-451C-BDE8-E92AAD3BD761}" EndProject @@ -84,6 +84,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample12", "sr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Tests.Sqlite", "test\WorkflowCore.Tests.Sqlite\WorkflowCore.Tests.Sqlite.csproj", "{F9F8F9CD-01D9-468B-856D-6A87F0762A01}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.LockProviders.SqlServer", "src\providers\WorkflowCore.LockProviders.SqlServer\WorkflowCore.LockProviders.SqlServer.csproj", "{AAE2E9F9-37EF-4AE1-A200-D37417C9040C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -222,6 +224,10 @@ Global {F9F8F9CD-01D9-468B-856D-6A87F0762A01}.Debug|Any CPU.Build.0 = Debug|Any CPU {F9F8F9CD-01D9-468B-856D-6A87F0762A01}.Release|Any CPU.ActiveCfg = Release|Any CPU {F9F8F9CD-01D9-468B-856D-6A87F0762A01}.Release|Any CPU.Build.0 = Release|Any CPU + {AAE2E9F9-37EF-4AE1-A200-D37417C9040C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAE2E9F9-37EF-4AE1-A200-D37417C9040C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAE2E9F9-37EF-4AE1-A200-D37417C9040C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAE2E9F9-37EF-4AE1-A200-D37417C9040C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -263,5 +269,6 @@ Global {58D0480F-D05D-4348-86D9-B0A7255700E6} = {5080DB09-CBE8-4C45-9957-C3BB7651755E} {BB776411-D279-419F-8697-5C6F52BCD5CD} = {5080DB09-CBE8-4C45-9957-C3BB7651755E} {F9F8F9CD-01D9-468B-856D-6A87F0762A01} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB} + {AAE2E9F9-37EF-4AE1-A200-D37417C9040C} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2} EndGlobalSection EndGlobal diff --git a/src/WorkflowCore/Primitives/SubscriptionStep.cs b/src/WorkflowCore/Primitives/SubscriptionStep.cs index 213d54796..438085c3a 100644 --- a/src/WorkflowCore/Primitives/SubscriptionStep.cs +++ b/src/WorkflowCore/Primitives/SubscriptionStep.cs @@ -29,6 +29,8 @@ public override ExecutionPipelineDirective InitForExecution(WorkflowExecutorResu if (EffectiveDate != null) effectiveDate = Convert.ToDateTime(EffectiveDate.Compile().DynamicInvoke(workflow.Data)); + effectiveDate = effectiveDate.ToUniversalTime(); + executionPointer.EventName = EventName; executionPointer.Active = false; diff --git a/src/WorkflowCore/ServiceCollectionExtensions.cs b/src/WorkflowCore/ServiceCollectionExtensions.cs index 3b9f73778..169504023 100644 --- a/src/WorkflowCore/ServiceCollectionExtensions.cs +++ b/src/WorkflowCore/ServiceCollectionExtensions.cs @@ -29,7 +29,7 @@ public static void AddWorkflow(this IServiceCollection services, Action(); services.AddTransient(); services.AddTransient(); - + services.AddTransient(); } } diff --git a/src/WorkflowCore/Services/WorkflowExecutor.cs b/src/WorkflowCore/Services/WorkflowExecutor.cs index 87df9bfe1..bf63fa18b 100644 --- a/src/WorkflowCore/Services/WorkflowExecutor.cs +++ b/src/WorkflowCore/Services/WorkflowExecutor.cs @@ -242,7 +242,7 @@ private void DetermineNextExecutionTime(WorkflowInstance workflow) { foreach (var pointer in workflow.ExecutionPointers.Where(x => x.Active && (x.Children ?? new List()).Count > 0)) { - if (workflow.ExecutionPointers.Where(x => pointer.Children.Contains(x.Id)).All(x => x.EndTime != null)) + if (workflow.ExecutionPointers.Where(x => pointer.Children.Contains(x.Id)).All(x => IsBranchComplete(workflow.ExecutionPointers, x.Id))) { workflow.NextExecution = 0; return; @@ -257,5 +257,23 @@ private void DetermineNextExecutionTime(WorkflowInstance workflow) } } + private bool IsBranchComplete(IEnumerable pointers, string rootId) + { + //TODO: move to own class + var root = pointers.First(x => x.Id == rootId); + + if (root.EndTime == null) + return false; + + var list = pointers.Where(x => x.PredecessorId == rootId).ToList(); + + bool result = true; + + foreach (var item in list) + result = result && IsBranchComplete(pointers, item.Id); + + return result; + } + } } diff --git a/src/WorkflowCore/WorkflowCore.csproj b/src/WorkflowCore/WorkflowCore.csproj index a8e2c982d..3fa629142 100644 --- a/src/WorkflowCore/WorkflowCore.csproj +++ b/src/WorkflowCore/WorkflowCore.csproj @@ -18,9 +18,9 @@ false false Workflow Core is a light weight workflow engine targeting .NET Standard. - 1.2.4 - 1.2.4.0 - 1.2.4.0 + 1.2.5 + 1.2.5.0 + 1.2.5.0 diff --git a/src/providers/WorkflowCore.LockProviders.Redlock/README.md b/src/providers/WorkflowCore.LockProviders.Redlock/README.md index d68092dfa..166706ee6 100644 --- a/src/providers/WorkflowCore.LockProviders.Redlock/README.md +++ b/src/providers/WorkflowCore.LockProviders.Redlock/README.md @@ -9,7 +9,7 @@ This makes it possible to have a cluster of nodes processing your workflows, alo Install the NuGet package "WorkflowCore.LockProviders.Redlock" ``` -PM> Install-Package WorkflowCore.LockProviders.Redlock -Pre +PM> Install-Package WorkflowCore.LockProviders.Redlock ``` ## Usage diff --git a/src/providers/WorkflowCore.LockProviders.SqlServer/README.md b/src/providers/WorkflowCore.LockProviders.SqlServer/README.md new file mode 100644 index 000000000..25832b458 --- /dev/null +++ b/src/providers/WorkflowCore.LockProviders.SqlServer/README.md @@ -0,0 +1,20 @@ +# SQL Server DLM provider for Workflow Core + +Provides [DLM](https://en.wikipedia.org/wiki/Distributed_lock_manager) support on [Workflow Core](../../README.md) using SQL Server's sp_getapplock. + +This makes it possible to have a cluster of nodes processing your workflows, along with a queue provider. + +## Installing + +Install the NuGet package "WorkflowCore.LockProviders.SqlServer" + +``` +PM> Install-Package WorkflowCore.LockProviders.SqlServer +``` + +## Usage + +Use the .UseSqlServerLocking extension method when building your service provider. + +```C# +services.AddWorkflow(x => x.UseSqlServerLocking("connection string")); diff --git a/src/providers/WorkflowCore.LockProviders.SqlServer/ServiceCollectionExtensions.cs b/src/providers/WorkflowCore.LockProviders.SqlServer/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..e93985658 --- /dev/null +++ b/src/providers/WorkflowCore.LockProviders.SqlServer/ServiceCollectionExtensions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using WorkflowCore.LockProviders.SqlServer; +using WorkflowCore.Models; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ServiceCollectionExtensions + { + public static WorkflowOptions UseSqlServerLocking(this WorkflowOptions options, string connectionString) + { + options.UseDistributedLockManager(sp => new SqlLockProvider(connectionString, sp.GetService())); + return options; + } + } +} diff --git a/src/providers/WorkflowCore.LockProviders.SqlServer/SqlLockProvider.cs b/src/providers/WorkflowCore.LockProviders.SqlServer/SqlLockProvider.cs new file mode 100644 index 000000000..c44cb2e5a --- /dev/null +++ b/src/providers/WorkflowCore.LockProviders.SqlServer/SqlLockProvider.cs @@ -0,0 +1,73 @@ +using System; +using System.Data.SqlClient; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using WorkflowCore.Interface; + +namespace WorkflowCore.LockProviders.SqlServer +{ + public class SqlLockProvider : IDistributedLockProvider + { + private const string Prefix = "wfc"; + + private readonly SqlConnection _connection; + private readonly ILogger _logger; + + public SqlLockProvider(string connectionString, ILoggerFactory logFactory) + { + _logger = logFactory.CreateLogger(); + var csb = new SqlConnectionStringBuilder(connectionString); + csb.Pooling = false; + + _connection = new SqlConnection(csb.ToString()); + } + + + public async Task AcquireLock(string Id) + { + var cmd = _connection.CreateCommand(); + cmd.CommandText = "EXEC @result = sp_getapplock @Resource = @id, @LockMode = 'Exclusive', @LockOwner = 'Session'"; + cmd.Parameters.AddWithValue("id", $"{Prefix}:{Id}"); + var result = Convert.ToInt32(await cmd.ExecuteScalarAsync()); + + switch (result) + { + case -1: + _logger.LogDebug($"The lock request timed out for {Id}"); + break; + case -2: + _logger.LogDebug($"The lock request was canceled for {Id}"); + break; + case -3: + _logger.LogDebug($"The lock request was chosen as a deadlock victim for {Id}"); + break; + case -999: + _logger.LogError($"Lock provider error for {Id}"); + break; + } + + return (result >= 0); + } + + public async Task ReleaseLock(string Id) + { + var cmd = _connection.CreateCommand(); + cmd.CommandText = "EXEC @result = sp_releaseapplock @Resource = @id, @LockOwner = 'Session'"; + cmd.Parameters.AddWithValue("id", $"{Prefix}:{Id}"); + var result = Convert.ToInt32(await cmd.ExecuteScalarAsync()); + + if (result < 0) + _logger.LogError($"Unable to release lock for {Id}"); + } + + public void Start() + { + _connection.Open(); + } + + public void Stop() + { + _connection.Close(); + } + } +} diff --git a/src/providers/WorkflowCore.LockProviders.SqlServer/WorkflowCore.LockProviders.SqlServer.csproj b/src/providers/WorkflowCore.LockProviders.SqlServer/WorkflowCore.LockProviders.SqlServer.csproj new file mode 100644 index 000000000..6eb110b2d --- /dev/null +++ b/src/providers/WorkflowCore.LockProviders.SqlServer/WorkflowCore.LockProviders.SqlServer.csproj @@ -0,0 +1,18 @@ + + + + netstandard1.3 + Distributed lock provider for Workflow-core using SQL Server + https://github.com/danielgerlag/workflow-core + https://github.com/danielgerlag/workflow-core/blob/master/LICENSE.md + + + + + + + + + + + \ No newline at end of file diff --git a/src/samples/WorkflowCore.Sample10/WorkflowCore.Sample10.csproj b/src/samples/WorkflowCore.Sample10/WorkflowCore.Sample10.csproj index a5a71cbde..4dedfae1f 100644 --- a/src/samples/WorkflowCore.Sample10/WorkflowCore.Sample10.csproj +++ b/src/samples/WorkflowCore.Sample10/WorkflowCore.Sample10.csproj @@ -8,6 +8,7 @@ +