Skip to content

Commit 21f0800

Browse files
authored
Sql lock (#37)
1 parent 934c136 commit 21f0800

File tree

11 files changed

+165
-7
lines changed

11 files changed

+165
-7
lines changed

WorkflowCore.sln

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26228.4
4+
VisualStudioVersion = 15.0.26430.6
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EF47161E-E399-451C-BDE8-E92AAD3BD761}"
77
EndProject
@@ -84,6 +84,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample12", "sr
8484
EndProject
8585
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Tests.Sqlite", "test\WorkflowCore.Tests.Sqlite\WorkflowCore.Tests.Sqlite.csproj", "{F9F8F9CD-01D9-468B-856D-6A87F0762A01}"
8686
EndProject
87+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.LockProviders.SqlServer", "src\providers\WorkflowCore.LockProviders.SqlServer\WorkflowCore.LockProviders.SqlServer.csproj", "{AAE2E9F9-37EF-4AE1-A200-D37417C9040C}"
88+
EndProject
8789
Global
8890
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8991
Debug|Any CPU = Debug|Any CPU
@@ -222,6 +224,10 @@ Global
222224
{F9F8F9CD-01D9-468B-856D-6A87F0762A01}.Debug|Any CPU.Build.0 = Debug|Any CPU
223225
{F9F8F9CD-01D9-468B-856D-6A87F0762A01}.Release|Any CPU.ActiveCfg = Release|Any CPU
224226
{F9F8F9CD-01D9-468B-856D-6A87F0762A01}.Release|Any CPU.Build.0 = Release|Any CPU
227+
{AAE2E9F9-37EF-4AE1-A200-D37417C9040C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
228+
{AAE2E9F9-37EF-4AE1-A200-D37417C9040C}.Debug|Any CPU.Build.0 = Debug|Any CPU
229+
{AAE2E9F9-37EF-4AE1-A200-D37417C9040C}.Release|Any CPU.ActiveCfg = Release|Any CPU
230+
{AAE2E9F9-37EF-4AE1-A200-D37417C9040C}.Release|Any CPU.Build.0 = Release|Any CPU
225231
EndGlobalSection
226232
GlobalSection(SolutionProperties) = preSolution
227233
HideSolutionNode = FALSE
@@ -263,5 +269,6 @@ Global
263269
{58D0480F-D05D-4348-86D9-B0A7255700E6} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
264270
{BB776411-D279-419F-8697-5C6F52BCD5CD} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
265271
{F9F8F9CD-01D9-468B-856D-6A87F0762A01} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
272+
{AAE2E9F9-37EF-4AE1-A200-D37417C9040C} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
266273
EndGlobalSection
267274
EndGlobal

src/WorkflowCore/Primitives/SubscriptionStep.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public override ExecutionPipelineDirective InitForExecution(WorkflowExecutorResu
2929
if (EffectiveDate != null)
3030
effectiveDate = Convert.ToDateTime(EffectiveDate.Compile().DynamicInvoke(workflow.Data));
3131

32+
effectiveDate = effectiveDate.ToUniversalTime();
33+
3234
executionPointer.EventName = EventName;
3335
executionPointer.Active = false;
3436

src/WorkflowCore/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public static void AddWorkflow(this IServiceCollection services, Action<Workflow
2929
services.AddTransient<IWorkflowThread, WorkflowThread>();
3030
services.AddTransient<IEventThread, EventThread>();
3131
services.AddTransient<IRunnablePoller, RunnablePoller>();
32-
32+
3333
services.AddTransient<Foreach>();
3434
}
3535
}

src/WorkflowCore/Services/WorkflowExecutor.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ private void DetermineNextExecutionTime(WorkflowInstance workflow)
242242
{
243243
foreach (var pointer in workflow.ExecutionPointers.Where(x => x.Active && (x.Children ?? new List<string>()).Count > 0))
244244
{
245-
if (workflow.ExecutionPointers.Where(x => pointer.Children.Contains(x.Id)).All(x => x.EndTime != null))
245+
if (workflow.ExecutionPointers.Where(x => pointer.Children.Contains(x.Id)).All(x => IsBranchComplete(workflow.ExecutionPointers, x.Id)))
246246
{
247247
workflow.NextExecution = 0;
248248
return;
@@ -257,5 +257,23 @@ private void DetermineNextExecutionTime(WorkflowInstance workflow)
257257
}
258258
}
259259

260+
private bool IsBranchComplete(IEnumerable<ExecutionPointer> pointers, string rootId)
261+
{
262+
//TODO: move to own class
263+
var root = pointers.First(x => x.Id == rootId);
264+
265+
if (root.EndTime == null)
266+
return false;
267+
268+
var list = pointers.Where(x => x.PredecessorId == rootId).ToList();
269+
270+
bool result = true;
271+
272+
foreach (var item in list)
273+
result = result && IsBranchComplete(pointers, item.Id);
274+
275+
return result;
276+
}
277+
260278
}
261279
}

src/WorkflowCore/WorkflowCore.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
1919
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
2020
<Description>Workflow Core is a light weight workflow engine targeting .NET Standard.</Description>
21-
<Version>1.2.4</Version>
22-
<AssemblyVersion>1.2.4.0</AssemblyVersion>
23-
<FileVersion>1.2.4.0</FileVersion>
21+
<Version>1.2.5</Version>
22+
<AssemblyVersion>1.2.5.0</AssemblyVersion>
23+
<FileVersion>1.2.5.0</FileVersion>
2424
</PropertyGroup>
2525

2626
<ItemGroup>

src/providers/WorkflowCore.LockProviders.Redlock/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This makes it possible to have a cluster of nodes processing your workflows, alo
99
Install the NuGet package "WorkflowCore.LockProviders.Redlock"
1010

1111
```
12-
PM> Install-Package WorkflowCore.LockProviders.Redlock -Pre
12+
PM> Install-Package WorkflowCore.LockProviders.Redlock
1313
```
1414

1515
## Usage
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# SQL Server DLM provider for Workflow Core
2+
3+
Provides [DLM](https://en.wikipedia.org/wiki/Distributed_lock_manager) support on [Workflow Core](../../README.md) using SQL Server's sp_getapplock.
4+
5+
This makes it possible to have a cluster of nodes processing your workflows, along with a queue provider.
6+
7+
## Installing
8+
9+
Install the NuGet package "WorkflowCore.LockProviders.SqlServer"
10+
11+
```
12+
PM> Install-Package WorkflowCore.LockProviders.SqlServer
13+
```
14+
15+
## Usage
16+
17+
Use the .UseSqlServerLocking extension method when building your service provider.
18+
19+
```C#
20+
services.AddWorkflow(x => x.UseSqlServerLocking("connection string"));
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Logging;
6+
using WorkflowCore.LockProviders.SqlServer;
7+
using WorkflowCore.Models;
8+
9+
namespace Microsoft.Extensions.DependencyInjection
10+
{
11+
public static class ServiceCollectionExtensions
12+
{
13+
public static WorkflowOptions UseSqlServerLocking(this WorkflowOptions options, string connectionString)
14+
{
15+
options.UseDistributedLockManager(sp => new SqlLockProvider(connectionString, sp.GetService<ILoggerFactory>()));
16+
return options;
17+
}
18+
}
19+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System;
2+
using System.Data.SqlClient;
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.Logging;
5+
using WorkflowCore.Interface;
6+
7+
namespace WorkflowCore.LockProviders.SqlServer
8+
{
9+
public class SqlLockProvider : IDistributedLockProvider
10+
{
11+
private const string Prefix = "wfc";
12+
13+
private readonly SqlConnection _connection;
14+
private readonly ILogger _logger;
15+
16+
public SqlLockProvider(string connectionString, ILoggerFactory logFactory)
17+
{
18+
_logger = logFactory.CreateLogger<SqlLockProvider>();
19+
var csb = new SqlConnectionStringBuilder(connectionString);
20+
csb.Pooling = false;
21+
22+
_connection = new SqlConnection(csb.ToString());
23+
}
24+
25+
26+
public async Task<bool> AcquireLock(string Id)
27+
{
28+
var cmd = _connection.CreateCommand();
29+
cmd.CommandText = "EXEC @result = sp_getapplock @Resource = @id, @LockMode = 'Exclusive', @LockOwner = 'Session'";
30+
cmd.Parameters.AddWithValue("id", $"{Prefix}:{Id}");
31+
var result = Convert.ToInt32(await cmd.ExecuteScalarAsync());
32+
33+
switch (result)
34+
{
35+
case -1:
36+
_logger.LogDebug($"The lock request timed out for {Id}");
37+
break;
38+
case -2:
39+
_logger.LogDebug($"The lock request was canceled for {Id}");
40+
break;
41+
case -3:
42+
_logger.LogDebug($"The lock request was chosen as a deadlock victim for {Id}");
43+
break;
44+
case -999:
45+
_logger.LogError($"Lock provider error for {Id}");
46+
break;
47+
}
48+
49+
return (result >= 0);
50+
}
51+
52+
public async Task ReleaseLock(string Id)
53+
{
54+
var cmd = _connection.CreateCommand();
55+
cmd.CommandText = "EXEC @result = sp_releaseapplock @Resource = @id, @LockOwner = 'Session'";
56+
cmd.Parameters.AddWithValue("id", $"{Prefix}:{Id}");
57+
var result = Convert.ToInt32(await cmd.ExecuteScalarAsync());
58+
59+
if (result < 0)
60+
_logger.LogError($"Unable to release lock for {Id}");
61+
}
62+
63+
public void Start()
64+
{
65+
_connection.Open();
66+
}
67+
68+
public void Stop()
69+
{
70+
_connection.Close();
71+
}
72+
}
73+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard1.3</TargetFramework>
5+
<Description>Distributed lock provider for Workflow-core using SQL Server</Description>
6+
<PackageProjectUrl>https://github.com/danielgerlag/workflow-core</PackageProjectUrl>
7+
<PackageLicenseUrl>https://github.com/danielgerlag/workflow-core/blob/master/LICENSE.md</PackageLicenseUrl>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="System.Data.SqlClient" Version="4.3.1" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\WorkflowCore\WorkflowCore.csproj" />
16+
</ItemGroup>
17+
18+
</Project>

0 commit comments

Comments
 (0)