27
27
28
28
namespace Hangfire . PostgreSql
29
29
{
30
- internal class PostgreSqlDistributedLock : IDisposable
30
+ internal sealed class PostgreSqlDistributedLock : IDisposable
31
31
{
32
- private readonly IDbConnection _connection ;
33
32
private readonly string _resource ;
33
+ private readonly IDbConnection _connection ;
34
34
private readonly PostgreSqlStorageOptions _options ;
35
35
private bool _completed ;
36
36
37
37
public PostgreSqlDistributedLock ( string resource , TimeSpan timeout , IDbConnection connection ,
38
38
PostgreSqlStorageOptions options )
39
39
{
40
- if ( String . IsNullOrEmpty ( resource ) ) throw new ArgumentNullException ( " resource" ) ;
41
- if ( connection == null ) throw new ArgumentNullException ( " connection" ) ;
42
- if ( options == null ) throw new ArgumentNullException ( " options" ) ;
40
+ if ( string . IsNullOrEmpty ( resource ) ) throw new ArgumentNullException ( nameof ( resource ) ) ;
41
+ if ( connection == null ) throw new ArgumentNullException ( nameof ( connection ) ) ;
42
+ if ( options == null ) throw new ArgumentNullException ( nameof ( options ) ) ;
43
43
44
44
_resource = resource ;
45
45
_connection = connection ;
@@ -51,89 +51,117 @@ public PostgreSqlDistributedLock(string resource, TimeSpan timeout, IDbConnectio
51
51
PostgreSqlDistributedLock_Init_UpdateCount ( resource , timeout , connection , options ) ;
52
52
}
53
53
54
- public void PostgreSqlDistributedLock_Init_Transaction ( string resource , TimeSpan timeout , IDbConnection connection , PostgreSqlStorageOptions options )
54
+ private static void PostgreSqlDistributedLock_Init_Transaction ( string resource , TimeSpan timeout ,
55
+ IDbConnection connection , PostgreSqlStorageOptions options )
55
56
{
56
-
57
- Stopwatch lockAcquiringTime = new Stopwatch ( ) ;
58
- lockAcquiringTime . Start ( ) ;
57
+ var lockAcquiringTime = Stopwatch . StartNew ( ) ;
59
58
60
59
bool tryAcquireLock = true ;
61
60
62
61
while ( tryAcquireLock )
63
62
{
63
+ TryRemoveDeadlock ( resource , connection , options ) ;
64
+
64
65
try
65
66
{
66
67
int rowsAffected = - 1 ;
67
- using ( var trx = _connection . BeginTransaction ( IsolationLevel . RepeatableRead ) )
68
+ using ( var trx = connection . BeginTransaction ( IsolationLevel . RepeatableRead ) )
68
69
{
69
- rowsAffected = _connection . Execute ( @"
70
- INSERT INTO """ + _options . SchemaName + @""" .""lock""(""resource"")
71
- SELECT @resource
70
+ rowsAffected = connection . Execute ( $ @ "
71
+ INSERT INTO ""{ options . SchemaName } "" .""lock""(""resource"", ""acquired "")
72
+ SELECT @resource, @acquired
72
73
WHERE NOT EXISTS (
73
- SELECT 1 FROM """ + _options . SchemaName + @" "".""lock""
74
+ SELECT 1 FROM ""{ options . SchemaName } "".""lock""
74
75
WHERE ""resource"" = @resource
75
76
);
76
- " , new
77
- {
78
- resource = resource
79
- } , trx ) ;
77
+ " ,
78
+ new
79
+ {
80
+ resource = resource ,
81
+ acquired = DateTime . UtcNow
82
+ } , trx ) ;
80
83
trx . Commit ( ) ;
81
84
}
82
85
if ( rowsAffected > 0 ) return ;
83
86
}
84
- catch ( Exception )
87
+ catch
85
88
{
86
89
}
87
90
88
91
if ( lockAcquiringTime . ElapsedMilliseconds > timeout . TotalMilliseconds )
92
+ {
89
93
tryAcquireLock = false ;
94
+ }
90
95
else
91
96
{
92
- int sleepDuration = ( int ) ( timeout . TotalMilliseconds - lockAcquiringTime . ElapsedMilliseconds ) ;
97
+ int sleepDuration = ( int ) ( timeout . TotalMilliseconds - lockAcquiringTime . ElapsedMilliseconds ) ;
93
98
if ( sleepDuration > 1000 ) sleepDuration = 1000 ;
94
99
if ( sleepDuration > 0 )
100
+ {
95
101
Thread . Sleep ( sleepDuration ) ;
102
+ }
96
103
else
104
+ {
97
105
tryAcquireLock = false ;
106
+ }
98
107
}
99
108
}
100
109
101
110
throw new PostgreSqlDistributedLockException (
102
- String . Format (
103
- "Could not place a lock on the resource '{0}': {1}." ,
104
- _resource ,
105
- "Lock timeout" ) ) ;
111
+ $ "Could not place a lock on the resource \' { resource } \' : Lock timeout.") ;
106
112
}
107
113
108
- public void PostgreSqlDistributedLock_Init_UpdateCount ( string resource , TimeSpan timeout , IDbConnection connection , PostgreSqlStorageOptions options )
114
+ private static void TryRemoveDeadlock ( string resource , IDbConnection connection , PostgreSqlStorageOptions options )
109
115
{
116
+ try
117
+ {
118
+ using ( var transaction = connection . BeginTransaction ( IsolationLevel . RepeatableRead ) )
119
+ {
120
+ int affected = - 1 ;
121
+
122
+ affected = connection . Execute ( $@ "DELETE FROM ""{ options . SchemaName } "".""lock"" WHERE ""resource"" = @resource AND ""acquired"" < @timeout",
123
+ new
124
+ {
125
+ resource = resource ,
126
+ timeout = DateTime . UtcNow - options . DistributedLockTimeout
127
+ } ) ;
110
128
111
- Stopwatch lockAcquiringTime = new Stopwatch ( ) ;
112
- lockAcquiringTime . Start ( ) ;
129
+ transaction . Commit ( ) ;
130
+ }
131
+ }
132
+ catch
133
+ {
134
+ }
135
+ }
136
+
137
+ private static void PostgreSqlDistributedLock_Init_UpdateCount ( string resource , TimeSpan timeout , IDbConnection connection , PostgreSqlStorageOptions options )
138
+ {
139
+ var lockAcquiringTime = Stopwatch . StartNew ( ) ;
113
140
114
141
bool tryAcquireLock = true ;
115
142
116
143
while ( tryAcquireLock )
117
144
{
118
145
try
119
146
{
120
- _connection . Execute ( @"
121
- INSERT INTO """ + _options . SchemaName + @""" .""lock""(""resource"", ""updatecount"")
122
- SELECT @resource, 0
147
+ connection . Execute ( $ @ "
148
+ INSERT INTO ""{ options . SchemaName } "" .""lock""(""resource"", ""updatecount"", ""acquired "")
149
+ SELECT @resource, 0, @acquired
123
150
WHERE NOT EXISTS (
124
- SELECT 1 FROM """ + _options . SchemaName + @" "".""lock""
151
+ SELECT 1 FROM ""{ options . SchemaName } "".""lock""
125
152
WHERE ""resource"" = @resource
126
153
);
127
154
" , new
128
- {
129
- resource = resource
130
- } ) ;
155
+ {
156
+ resource = resource ,
157
+ acquired = DateTime . UtcNow
158
+ } ) ;
131
159
}
132
160
catch ( Exception )
133
161
{
134
162
}
135
163
136
- int rowsAffected = _connection . Execute ( @"UPDATE """ + _options . SchemaName + @" "".""lock"" SET ""updatecount"" = 1 WHERE ""updatecount"" = 0") ;
164
+ int rowsAffected = connection . Execute ( $ @ "UPDATE ""{ options . SchemaName } "".""lock"" SET ""updatecount"" = 1 WHERE ""updatecount"" = 0") ;
137
165
138
166
if ( rowsAffected > 0 ) return ;
139
167
@@ -151,25 +179,20 @@ SELECT 1 FROM """ + _options.SchemaName + @""".""lock""
151
179
}
152
180
153
181
throw new PostgreSqlDistributedLockException (
154
- String . Format (
155
- "Could not place a lock on the resource '{0}': {1}." ,
156
- _resource ,
157
- "Lock timeout" ) ) ;
182
+ $ "Could not place a lock on the resource '{ resource } ': Lock timeout.") ;
158
183
}
159
184
160
-
161
-
162
-
163
185
public void Dispose ( )
164
186
{
165
187
if ( _completed ) return ;
166
188
167
189
_completed = true ;
168
190
169
- int rowsAffected = _connection . Execute ( @"
170
- DELETE FROM """ + _options . SchemaName + @" "".""lock""
191
+ int rowsAffected = _connection . Execute ( $ @ "
192
+ DELETE FROM ""{ _options . SchemaName } "".""lock""
171
193
WHERE ""resource"" = @resource;
172
- " , new
194
+ " ,
195
+ new
173
196
{
174
197
resource = _resource
175
198
} ) ;
@@ -178,10 +201,8 @@ DELETE FROM """ + _options.SchemaName + @""".""lock""
178
201
if ( rowsAffected <= 0 )
179
202
{
180
203
throw new PostgreSqlDistributedLockException (
181
- String . Format (
182
- "Could not release a lock on the resource '{0}'. Lock does not exists." ,
183
- _resource ) ) ;
204
+ $ "Could not release a lock on the resource '{ _resource } '. Lock does not exists.") ;
184
205
}
185
206
}
186
207
}
187
- }
208
+ }
0 commit comments