|
3 | 3 | using System.Threading.Tasks;
|
4 | 4 | using Microsoft.Extensions.Logging;
|
5 | 5 | using WorkflowCore.Interface;
|
| 6 | +using System.Data; |
| 7 | +using System.Collections.Generic; |
| 8 | +using System.Collections.Concurrent; |
| 9 | +using System.Threading; |
6 | 10 |
|
7 | 11 | namespace WorkflowCore.LockProviders.SqlServer
|
8 | 12 | {
|
9 | 13 | public class SqlLockProvider : IDistributedLockProvider
|
10 | 14 | {
|
11 | 15 | private const string Prefix = "wfc";
|
12 | 16 |
|
13 |
| - private readonly SqlConnection _connection; |
| 17 | + private readonly string _connectionString; |
14 | 18 | private readonly ILogger _logger;
|
| 19 | + private readonly Dictionary<string, SqlConnection> _locks = new Dictionary<string, SqlConnection>(); |
| 20 | + private readonly AutoResetEvent _mutex = new AutoResetEvent(true); |
15 | 21 |
|
16 | 22 | public SqlLockProvider(string connectionString, ILoggerFactory logFactory)
|
17 | 23 | {
|
18 | 24 | _logger = logFactory.CreateLogger<SqlLockProvider>();
|
19 | 25 | var csb = new SqlConnectionStringBuilder(connectionString);
|
20 |
| - csb.Pooling = false; |
| 26 | + csb.Pooling = true; |
| 27 | + csb.ApplicationName = "Workflow Core Lock Manager"; |
21 | 28 |
|
22 |
| - _connection = new SqlConnection(csb.ToString()); |
| 29 | + _connectionString = csb.ToString(); |
23 | 30 | }
|
24 | 31 |
|
25 | 32 |
|
26 | 33 | public async Task<bool> AcquireLock(string Id)
|
27 | 34 | {
|
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) |
| 35 | + if (_mutex.WaitOne()) |
34 | 36 | {
|
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 |
| - } |
| 37 | + try |
| 38 | + { |
| 39 | + var connection = new SqlConnection(_connectionString); |
| 40 | + await connection.OpenAsync(); |
| 41 | + try |
| 42 | + { |
| 43 | + var cmd = connection.CreateCommand(); |
| 44 | + cmd.CommandText = "sp_getapplock"; |
| 45 | + cmd.CommandType = CommandType.StoredProcedure; |
| 46 | + cmd.Parameters.AddWithValue("@Resource", $"{Prefix}:{Id}"); |
| 47 | + cmd.Parameters.AddWithValue("@LockOwner", $"Session"); |
| 48 | + cmd.Parameters.AddWithValue("@LockMode", $"Exclusive"); |
| 49 | + cmd.Parameters.AddWithValue("@LockTimeout", 0); |
| 50 | + var returnParameter = cmd.Parameters.Add("RetVal", SqlDbType.Int); |
| 51 | + returnParameter.Direction = ParameterDirection.ReturnValue; |
| 52 | + await cmd.ExecuteNonQueryAsync(); |
| 53 | + var result = Convert.ToInt32(returnParameter.Value); |
48 | 54 |
|
49 |
| - return (result >= 0); |
| 55 | + switch (result) |
| 56 | + { |
| 57 | + case -1: |
| 58 | + _logger.LogDebug($"The lock request timed out for {Id}"); |
| 59 | + break; |
| 60 | + case -2: |
| 61 | + _logger.LogDebug($"The lock request was canceled for {Id}"); |
| 62 | + break; |
| 63 | + case -3: |
| 64 | + _logger.LogDebug($"The lock request was chosen as a deadlock victim for {Id}"); |
| 65 | + break; |
| 66 | + case -999: |
| 67 | + _logger.LogError($"Lock provider error for {Id}"); |
| 68 | + break; |
| 69 | + } |
| 70 | + if (result >= 0) |
| 71 | + { |
| 72 | + _locks[Id] = connection; |
| 73 | + return true; |
| 74 | + } |
| 75 | + else |
| 76 | + { |
| 77 | + connection.Close(); |
| 78 | + return false; |
| 79 | + } |
| 80 | + } |
| 81 | + catch (Exception ex) |
| 82 | + { |
| 83 | + connection.Close(); |
| 84 | + throw ex; |
| 85 | + } |
| 86 | + } |
| 87 | + finally |
| 88 | + { |
| 89 | + _mutex.Set(); |
| 90 | + } |
| 91 | + } |
| 92 | + return false; |
50 | 93 | }
|
51 | 94 |
|
52 | 95 | public async Task ReleaseLock(string Id)
|
53 | 96 | {
|
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}"); |
| 97 | + if (_mutex.WaitOne()) |
| 98 | + { |
| 99 | + try |
| 100 | + { |
| 101 | + SqlConnection connection = null; |
| 102 | + connection = _locks[Id]; |
| 103 | + |
| 104 | + if (connection == null) |
| 105 | + return; |
| 106 | + |
| 107 | + try |
| 108 | + { |
| 109 | + var cmd = connection.CreateCommand(); |
| 110 | + cmd.CommandText = "sp_releaseapplock"; |
| 111 | + cmd.CommandType = CommandType.StoredProcedure; |
| 112 | + cmd.Parameters.AddWithValue("@Resource", $"{Prefix}:{Id}"); |
| 113 | + cmd.Parameters.AddWithValue("@LockOwner", $"Session"); |
| 114 | + var returnParameter = cmd.Parameters.Add("RetVal", SqlDbType.Int); |
| 115 | + returnParameter.Direction = ParameterDirection.ReturnValue; |
| 116 | + |
| 117 | + await cmd.ExecuteNonQueryAsync(); |
| 118 | + var result = Convert.ToInt32(returnParameter.Value); |
| 119 | + |
| 120 | + if (result < 0) |
| 121 | + _logger.LogError($"Unable to release lock for {Id}"); |
| 122 | + } |
| 123 | + finally |
| 124 | + { |
| 125 | + connection.Close(); |
| 126 | + _locks.Remove(Id); |
| 127 | + } |
| 128 | + } |
| 129 | + finally |
| 130 | + { |
| 131 | + _mutex.Set(); |
| 132 | + } |
| 133 | + } |
61 | 134 | }
|
62 | 135 |
|
63 | 136 | public async Task Start()
|
64 |
| - { |
65 |
| - await _connection.OpenAsync(); |
| 137 | + { |
66 | 138 | }
|
67 | 139 |
|
68 | 140 | public async Task Stop()
|
69 | 141 | {
|
70 |
| - _connection.Close(); |
71 | 142 | }
|
72 | 143 | }
|
73 | 144 | }
|
0 commit comments