Skip to content

Commit

Permalink
Merge pull request #11 from SumoServer/fix/lock
Browse files Browse the repository at this point in the history
Rely on SQLite uniqueness constraint to protect locks
  • Loading branch information
felixclase authored Dec 16, 2019
2 parents e67c998 + fd2581a commit bf1c618
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class DistributedLock
/// <summary>
/// The name of the resource being held.
/// </summary>
[Unique]
public string Resource { get; set; }

/// <summary>
Expand Down
13 changes: 12 additions & 1 deletion src/main/Hangfire.Storage.SQLite/SQLiteDistributedLock.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Hangfire.Logging;
using Hangfire.Storage.SQLite.Entities;
using SQLite;
using System;
using System.Collections.Generic;
using System.Text;
Expand Down Expand Up @@ -134,7 +135,17 @@ private void Acquire(TimeSpan timeout)

var rowsAffected = _dbContext.Database.Update(distributedLock);
if (rowsAffected == 0)
_dbContext.Database.Insert(distributedLock);
{
try
{
_dbContext.Database.Insert(distributedLock);
}
catch(SQLiteException e) when (e.Result == SQLite3.Result.Constraint)
{
// The lock already exists preventing us from inserting.
continue;
}
}

// If result is null, then it means we acquired the lock
if (result == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading;
using Hangfire.Storage.SQLite.Entities;
using Hangfire.Storage.SQLite.Test.Utils;
Expand Down Expand Up @@ -127,6 +128,50 @@ public void Ctor_WaitForLock_SignaledAtLockRelease()
});
}

[Fact, CleanDatabase]
public void Ctor_WaitForLock_OnlySingleLockCanBeAcquired()
{
var connection = ConnectionUtils.CreateConnection();
var numThreads = 10;
long concurrencyCounter = 0;
var manualResetEvent = new ManualResetEventSlim();
var success = new bool[numThreads];

// Spawn multiple threads to race each other.
var threads = Enumerable.Range(0, numThreads).Select(i => new Thread(() =>
{
// Wait for the start signal.
manualResetEvent.Wait();
// Attempt to acquire the distributed lock.
using (new SQLiteDistributedLock("resource1", TimeSpan.FromSeconds(5), connection, new SQLiteStorageOptions()))
{
// Find out if any other threads managed to acquire the lock.
var oldConcurrencyCounter = Interlocked.CompareExchange(ref concurrencyCounter, 1, 0);
// The old concurrency counter should be 0 as only one thread should be allowed to acquire the lock.
success[i] = oldConcurrencyCounter == 0;
Interlocked.MemoryBarrier();
// Hold the lock for some time.
Thread.Sleep(100);
Interlocked.Decrement(ref concurrencyCounter);
}
})).ToList();

threads.ForEach(t => t.Start());

manualResetEvent.Set();

threads.ForEach(t => Assert.True(t.Join(TimeSpan.FromMinutes(1)), "Thread is hanging unexpected"));

// All the threads should report success.
Interlocked.MemoryBarrier();
Assert.DoesNotContain(false, success);
}

[Fact]
public void Ctor_ThrowsAnException_WhenOptionsIsNull()
{
Expand Down

0 comments on commit bf1c618

Please sign in to comment.