Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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