@@ -19,6 +19,7 @@ private static readonly ThreadLocal<Dictionary<string, int>> AcquiredLocks
1919 = new ThreadLocal < Dictionary < string , int > > ( ( ) => new Dictionary < string , int > ( StringComparer . OrdinalIgnoreCase ) ) ;
2020
2121 private readonly string _resource ;
22+ private readonly string _resourceKey ;
2223
2324 private readonly HangfireDbContext _dbContext ;
2425
@@ -44,6 +45,7 @@ public SQLiteDistributedLock(string resource, TimeSpan timeout, HangfireDbContex
4445 _resource = resource ?? throw new ArgumentNullException ( nameof ( resource ) ) ;
4546 _dbContext = database ?? throw new ArgumentNullException ( nameof ( database ) ) ;
4647 _storageOptions = storageOptions ?? throw new ArgumentNullException ( nameof ( storageOptions ) ) ;
48+ _resourceKey = Guid . NewGuid ( ) . ToString ( ) ;
4749
4850 if ( string . IsNullOrEmpty ( resource ) )
4951 {
@@ -92,7 +94,7 @@ public void Dispose()
9294 }
9395
9496 // Timer callback may be invoked after the Dispose method call,
95- // so we are using lock to avoid unsynchronized calls .
97+ // but since we use the resource key, we will not disturb other owners .
9698 AcquiredLocks . Value . Remove ( _resource ) ;
9799
98100 if ( _heartbeatTimer != null )
@@ -110,32 +112,33 @@ private void Acquire(TimeSpan timeout)
110112 {
111113 try
112114 {
113- // If result is null, then it means we acquired the lock
114115 var isLockAcquired = false ;
115116 var now = DateTime . UtcNow ;
116117 var lockTimeoutTime = now . Add ( timeout ) ;
117118
118- while ( ! isLockAcquired && ( lockTimeoutTime >= now ) )
119+ while ( lockTimeoutTime >= now )
119120 {
120- DistributedLock result ;
121+ Cleanup ( ) ;
121122
122123 lock ( EventWaitHandleName )
123124 {
124- result = _dbContext . DistributedLockRepository . FirstOrDefault ( _ => _ . Resource == _resource ) ;
125- var distributedLock = result ?? new DistributedLock ( ) ;
125+ var result = _dbContext . DistributedLockRepository . FirstOrDefault ( _ => _ . Resource == _resource ) ;
126126
127- if ( string . IsNullOrWhiteSpace ( distributedLock . Id ) )
128- distributedLock . Id = Guid . NewGuid ( ) . ToString ( ) ;
129-
130- distributedLock . Resource = _resource ;
131- distributedLock . ExpireAt = DateTime . UtcNow . Add ( _storageOptions . DistributedLockLifetime ) ;
132-
133- var rowsAffected = _dbContext . Database . Update ( distributedLock ) ;
134- if ( rowsAffected == 0 )
127+ if ( result == null )
135128 {
136129 try
137130 {
131+ var distributedLock = new DistributedLock ( ) ;
132+ distributedLock . Id = Guid . NewGuid ( ) . ToString ( ) ;
133+ distributedLock . Resource = _resource ;
134+ distributedLock . ResourceKey = _resourceKey ;
135+ distributedLock . ExpireAt = DateTime . UtcNow . Add ( _storageOptions . DistributedLockLifetime ) ;
136+
138137 _dbContext . Database . Insert ( distributedLock ) ;
138+
139+ // we were able to acquire the lock - break the loop
140+ isLockAcquired = true ;
141+ break ;
139142 }
140143 catch ( SQLiteException e ) when ( e . Result == SQLite3 . Result . Constraint )
141144 {
@@ -145,24 +148,17 @@ private void Acquire(TimeSpan timeout)
145148 }
146149 }
147150
148- // If result is null, then it means we acquired the lock
149- if ( result == null )
150- {
151- isLockAcquired = true ;
152- }
153- else
154- {
155- var waitTime = ( int ) timeout . TotalMilliseconds / 10 ;
156- lock ( EventWaitHandleName )
157- Monitor . Wait ( EventWaitHandleName , waitTime ) ;
151+ // we couldn't acquire the lock - wait a bit and try again
152+ var waitTime = ( int ) timeout . TotalMilliseconds / 10 ;
153+ lock ( EventWaitHandleName )
154+ Monitor . Wait ( EventWaitHandleName , waitTime ) ;
158155
159- now = DateTime . UtcNow ;
160- }
156+ now = DateTime . UtcNow ;
161157 }
162158
163159 if ( ! isLockAcquired )
164160 {
165- throw new DistributedLockTimeoutException ( $ "Could not place a lock on the resource \' { _resource } \' : The lock request timed out." ) ;
161+ throw new DistributedLockTimeoutException ( _resource ) ;
166162 }
167163 }
168164 catch ( DistributedLockTimeoutException ex )
@@ -183,8 +179,8 @@ private void Release()
183179 {
184180 try
185181 {
186- // Remove resource lock
187- _dbContext . DistributedLockRepository . Delete ( _ => _ . Resource == _resource ) ;
182+ // Remove resource lock (if it's still ours)
183+ _dbContext . DistributedLockRepository . Delete ( _ => _ . Resource == _resource && _ . ResourceKey == _resourceKey ) ;
188184 lock ( EventWaitHandleName )
189185 Monitor . Pulse ( EventWaitHandleName ) ;
190186 }
@@ -198,7 +194,7 @@ private void Cleanup()
198194 {
199195 try
200196 {
201- // Delete expired locks
197+ // Delete expired locks (of any owner)
202198 _dbContext . DistributedLockRepository .
203199 Delete ( x => x . Resource == _resource && x . ExpireAt < DateTime . UtcNow ) ;
204200 }
@@ -218,13 +214,20 @@ private void StartHeartBeat()
218214 _heartbeatTimer = new Timer ( state =>
219215 {
220216 // Timer callback may be invoked after the Dispose method call,
221- // so we are using lock to avoid unsynchronized calls .
217+ // but since we use the resource key, we will not disturb other owners .
222218 try
223219 {
224- var distributedLock = _dbContext . DistributedLockRepository . FirstOrDefault ( x => x . Resource == _resource ) ;
225- distributedLock . ExpireAt = DateTime . UtcNow . Add ( _storageOptions . DistributedLockLifetime ) ;
220+ var distributedLock = _dbContext . DistributedLockRepository . FirstOrDefault ( x => x . Resource == _resource && x . ResourceKey == _resourceKey ) ;
221+ if ( distributedLock != null )
222+ {
223+ distributedLock . ExpireAt = DateTime . UtcNow . Add ( _storageOptions . DistributedLockLifetime ) ;
226224
227- _dbContext . Database . Update ( distributedLock ) ;
225+ _dbContext . Database . Update ( distributedLock ) ;
226+ }
227+ else
228+ {
229+ Logger . ErrorFormat ( "Unable to update heartbeat on the resource '{0}'. The resource is not locked or is locked by another owner." , _resource ) ;
230+ }
228231 }
229232 catch ( Exception ex )
230233 {
0 commit comments