From 8dc26d7870a9365d966382489f2331b2afd379a7 Mon Sep 17 00:00:00 2001 From: kirides <13602143+kirides@users.noreply.github.com> Date: Wed, 17 Apr 2024 12:40:02 +0200 Subject: [PATCH] - #73 - implement a few of the missing features Signed-off-by: kirides <13602143+kirides@users.noreply.github.com> --- src/main/Hangfire.Storage.SQLite/Assembly.cs | 3 + .../HangfireSQLiteConnection.cs | 55 +++++- .../PersistentJobQueueProviderCollection.cs | 4 +- .../SQLiteDistributedLock.cs | 10 +- .../Hangfire.Storage.SQLite/SQLiteStorage.cs | 28 +-- .../SQLiteWriteOnlyTransaction.cs | 56 +++++- ...ctionFacts.GetFirstByLowestScoreFromSet.cs | 165 ++++++++++++++++++ .../SQLiteConnectionFacts.GetSetCount.cs | 146 ++++++++++++++++ .../SQLiteConnectionFacts.cs | 154 +++++----------- .../SQLiteWriteOnlyTransactionFacts.cs | 45 +++++ 10 files changed, 524 insertions(+), 142 deletions(-) create mode 100644 src/main/Hangfire.Storage.SQLite/Assembly.cs create mode 100644 src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.GetFirstByLowestScoreFromSet.cs create mode 100644 src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.GetSetCount.cs diff --git a/src/main/Hangfire.Storage.SQLite/Assembly.cs b/src/main/Hangfire.Storage.SQLite/Assembly.cs new file mode 100644 index 0000000..21237cb --- /dev/null +++ b/src/main/Hangfire.Storage.SQLite/Assembly.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Hangfire.Storage.SQLite.Test")] \ No newline at end of file diff --git a/src/main/Hangfire.Storage.SQLite/HangfireSQLiteConnection.cs b/src/main/Hangfire.Storage.SQLite/HangfireSQLiteConnection.cs index 48668f3..bf0dbdb 100644 --- a/src/main/Hangfire.Storage.SQLite/HangfireSQLiteConnection.cs +++ b/src/main/Hangfire.Storage.SQLite/HangfireSQLiteConnection.cs @@ -231,6 +231,28 @@ public override long GetSetCount(string key) .SetRepository .Count(_ => _.Key == key); } + + public override long GetSetCount(IEnumerable keys, int limit) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + + var count = DbContext + .SetRepository + .Where(_ => keys.Contains(_.Key)) + .Take(limit) + .Count(); + return Math.Min(count, limit); + } + + public override bool GetSetContains(string key, string value) + { + return DbContext + .SetRepository + .Any(x => x.Key == key && x.Value == value); + } public override string GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore) { @@ -257,6 +279,32 @@ public override string GetFirstByLowestScoreFromSet(string key, double fromScore .FirstOrDefault(); } + public override List GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore, int count) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (toScore < fromScore) + { + throw new ArgumentException("The 'toScore' value must be higher or equal to the 'fromScore' value."); + } + + var fromScoreDec = fromScore.ToInt64(); + var toScoreDec = toScore.ToInt64(); + + return DbContext + .SetRepository + .Where(_ => _.Key == key && + _.Score >= fromScoreDec && + _.Score <= toScoreDec) + .OrderBy(_ => _.Score) + .Select(_ => _.Value) + .Take(count) + .ToList(); + } + public override JobData GetJobData(string jobId) { if (jobId == null) @@ -434,7 +482,7 @@ public override void SetJobParameter(string id, string name, string value) public override void SetRangeInHash(string key, IEnumerable> keyValuePairs) { - using (var transaction = new SQLiteWriteOnlyTransaction(DbContext, _queueProviders)) + using (var transaction = CreateWriteTransaction()) { transaction.SetRangeInHash(key, keyValuePairs); transaction.Commit(); @@ -561,6 +609,11 @@ public override TimeSpan GetListTtl(string key) return result != DateTime.MinValue ? result - DateTime.UtcNow : TimeSpan.FromSeconds(-1); } + public override DateTime GetUtcDateTime() + { + return DateTime.UtcNow; + } + public override List GetRangeFromList(string key, int startingFrom, int endingAt) { if (key == null) diff --git a/src/main/Hangfire.Storage.SQLite/PersistentJobQueueProviderCollection.cs b/src/main/Hangfire.Storage.SQLite/PersistentJobQueueProviderCollection.cs index 875980e..aa3b27a 100644 --- a/src/main/Hangfire.Storage.SQLite/PersistentJobQueueProviderCollection.cs +++ b/src/main/Hangfire.Storage.SQLite/PersistentJobQueueProviderCollection.cs @@ -52,8 +52,8 @@ public void Add(IPersistentJobQueueProvider provider, IEnumerable queues /// public IPersistentJobQueueProvider GetProvider(string queue) { - return _providersByQueue.ContainsKey(queue) - ? _providersByQueue[queue] + return _providersByQueue.TryGetValue(queue, out var value) + ? value : _defaultProvider; } diff --git a/src/main/Hangfire.Storage.SQLite/SQLiteDistributedLock.cs b/src/main/Hangfire.Storage.SQLite/SQLiteDistributedLock.cs index 6b78114..158f46c 100644 --- a/src/main/Hangfire.Storage.SQLite/SQLiteDistributedLock.cs +++ b/src/main/Hangfire.Storage.SQLite/SQLiteDistributedLock.cs @@ -83,6 +83,7 @@ public void Dispose() _completed = true; _heartbeatTimer?.Dispose(); + _heartbeatTimer = null; Release(); } @@ -184,7 +185,14 @@ private void StartHeartBeat() Logger.ErrorFormat("Unable to update heartbeat on the resource '{0}'. The resource is not locked or is locked by another owner.", _resource); // if we no longer have a lock, stop the heartbeat immediately - _heartbeatTimer?.Dispose(); + try + { + _heartbeatTimer?.Dispose(); + } + catch (ObjectDisposedException) + { + // well, already disposed? + } return; } } diff --git a/src/main/Hangfire.Storage.SQLite/SQLiteStorage.cs b/src/main/Hangfire.Storage.SQLite/SQLiteStorage.cs index 7f7adc3..88743d5 100644 --- a/src/main/Hangfire.Storage.SQLite/SQLiteStorage.cs +++ b/src/main/Hangfire.Storage.SQLite/SQLiteStorage.cs @@ -15,21 +15,21 @@ public class SQLiteStorage : JobStorage, IDisposable private readonly SQLiteStorageOptions _storageOptions; - private readonly Dictionary _features = new Dictionary(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary _features = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "Storage.ExtendedApi", false }, - { "Job.Queue", true }, - { "Connection.GetUtcDateTime", false }, - { "Connection.BatchedGetFirstByLowestScoreFromSet", false }, - { "Connection.GetSetContains", true }, - { "Connection.GetSetCount.Limited", false }, - { "BatchedGetFirstByLowestScoreFromSet", false }, - { "Transaction.AcquireDistributedLock", true }, - { "Transaction.CreateJob", true }, - { "Transaction.SetJobParameter", true }, - { "TransactionalAcknowledge:InMemoryFetchedJob", false }, - { "Monitoring.DeletedStateGraphs", false }, - { "Monitoring.AwaitingJobs", false } + { JobStorageFeatures.ExtendedApi, true }, + { JobStorageFeatures.JobQueueProperty, true }, + { JobStorageFeatures.Connection.GetUtcDateTime, true }, + { JobStorageFeatures.Connection.BatchedGetFirstByLowest, true }, + { "BatchedGetFirstByLowestScoreFromSet", true }, // ^-- legacy name? + { JobStorageFeatures.Connection.GetSetContains, true }, + { JobStorageFeatures.Connection.LimitedGetSetCount, true }, + { JobStorageFeatures.Transaction.AcquireDistributedLock, true }, + { JobStorageFeatures.Transaction.CreateJob, false }, // NOTE: implement SQLiteWriteOnlyTransaction.CreateJob(...) + { JobStorageFeatures.Transaction.SetJobParameter, false }, // NOTE: implement SQLiteWriteOnlyTransaction.SetJobParameter(...) + { JobStorageFeatures.Transaction.RemoveFromQueue(typeof(SQLiteFetchedJob)), false }, // NOTE: implement SQLiteWriteOnlyTransaction.RemoveFromQueue(...) + { JobStorageFeatures.Monitoring.DeletedStateGraphs, false }, + { JobStorageFeatures.Monitoring.AwaitingJobs, false } }; private ConcurrentQueue _dbContextPool = new ConcurrentQueue(); diff --git a/src/main/Hangfire.Storage.SQLite/SQLiteWriteOnlyTransaction.cs b/src/main/Hangfire.Storage.SQLite/SQLiteWriteOnlyTransaction.cs index 65d3e19..45b2537 100644 --- a/src/main/Hangfire.Storage.SQLite/SQLiteWriteOnlyTransaction.cs +++ b/src/main/Hangfire.Storage.SQLite/SQLiteWriteOnlyTransaction.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; -using System.Text; +using Hangfire.Logging; using Hangfire.States; using Hangfire.Storage.SQLite.Entities; using Newtonsoft.Json; @@ -17,8 +16,10 @@ public class SQLiteWriteOnlyTransaction : JobStorageTransaction private readonly PersistentJobQueueProviderCollection _queueProviders; - private static object _lockObject = new object(); - + internal readonly List _acquiredLocks = new List(); + + private static readonly ILog Logger = LogProvider.For(); + /// /// /// @@ -36,6 +37,15 @@ private void QueueCommand(Action action) _commandQueue.Enqueue(action); } + public override void AcquireDistributedLock(string resource, TimeSpan timeout) + { + var acquiredLock = SQLiteDistributedLock.Acquire(resource, timeout, _dbContext, _dbContext.StorageOptions); + lock (_acquiredLocks) + { + _acquiredLocks.Add(acquiredLock); + } + } + public override void AddJobState(string jobId, IState state) { QueueCommand(_ => @@ -113,16 +123,44 @@ public override void AddToSet(string key, string value, double score) public override void Commit() { - Retry.Twice((attempts) => { - - lock (_lockObject) - { + try + { + Retry.Twice((attempts) => { _commandQueue.ToList().ForEach(_ => { _.Invoke(_dbContext); }); + }); + } + finally + { + ReleaseAcquiredLocks(); + } + } + + private void ReleaseAcquiredLocks() + { + lock (_acquiredLocks) + { + foreach (var acquiredLock in _acquiredLocks) + { + try + { + acquiredLock.Dispose(); + } + catch (Exception ex) + { + Logger.WarnException("Failed to release a distributed lock", ex); + } } - }); + _acquiredLocks.Clear(); + } + } + + public override void Dispose() + { + ReleaseAcquiredLocks(); + base.Dispose(); } /// diff --git a/src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.GetFirstByLowestScoreFromSet.cs b/src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.GetFirstByLowestScoreFromSet.cs new file mode 100644 index 0000000..fd5ac50 --- /dev/null +++ b/src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.GetFirstByLowestScoreFromSet.cs @@ -0,0 +1,165 @@ +using Hangfire.Common; +using Hangfire.Server; +using Hangfire.Storage.SQLite.Entities; +using Hangfire.Storage.SQLite.Test.Utils; +using Moq; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Xunit; + +namespace Hangfire.Storage.SQLite.Test +{ + public partial class HangfireSQLiteConnectionFacts + { + [Fact] + public void GetFirstByLowestScoreFromSet_ThrowsAnException_WhenKeyIsNull() + { + UseConnection((database, connection) => + { + var exception = Assert.Throws( + () => connection.GetFirstByLowestScoreFromSet(null, 0, 1)); + + Assert.Equal("key", exception.ParamName); + }); + } + + [Fact] + public void GetFirstByLowestScoreFromSet_ThrowsAnException_ToScoreIsLowerThanFromScore() + { + UseConnection((database, connection) => Assert.Throws( + () => connection.GetFirstByLowestScoreFromSet("key", 0, -1))); + } + + [Fact] + public void GetFirstByLowestScoreFromSet_ReturnsNull_WhenTheKeyDoesNotExist() + { + UseConnection((database, connection) => + { + var result = connection.GetFirstByLowestScoreFromSet( + "key", 0, 1); + + Assert.Null(result); + }); + } + + [Fact] + public void GetFirstByLowestScoreFromSet_ReturnsTheValueWithTheLowestScore() + { + UseConnection((database, connection) => + { + database.Database.Insert(new Set + { + Key = "key", + Score = 1.0m, + Value = "1.0" + }); + database.Database.Insert(new Set + { + Key = "key", + Score = -1.0m, + Value = "-1.0" + }); + database.Database.Insert(new Set + { + Key = "key", + Score = -5.0m, + Value = "-5.0" + }); + database.Database.Insert(new Set + { + Key = "another-key", + Score = -2.0m, + Value = "-2.0" + }); + + var result = connection.GetFirstByLowestScoreFromSet("key", -1.0, 3.0); + + Assert.Equal("-1.0", result); + }); + } + + [Fact] + [Trait("Feature", "BatchedGetFirstByLowestScoreFromSet")] + public void Batch_GetFirstByLowestScoreFromSet_ThrowsAnException_WhenKeyIsNull() + { + UseConnection((database, connection) => + { + var exception = Assert.Throws( + () => connection.GetFirstByLowestScoreFromSet(null, 0, 1, 1)); + + Assert.Equal("key", exception.ParamName); + }); + } + + [Fact] + [Trait("Feature", "BatchedGetFirstByLowestScoreFromSet")] + public void Batch_GetFirstByLowestScoreFromSet_ThrowsAnException_ToScoreIsLowerThanFromScore() + { + UseConnection((database, connection) => Assert.Throws( + () => connection.GetFirstByLowestScoreFromSet("key", 0, -1, 1))); + } + + [Fact] + [Trait("Feature", "BatchedGetFirstByLowestScoreFromSet")] + public void Batch_GetFirstByLowestScoreFromSet_ReturnsNull_WhenTheKeyDoesNotExist() + { + UseConnection((database, connection) => + { + var result = connection.GetFirstByLowestScoreFromSet( + "key", 0, 1, 1); + + Assert.Empty(result); + }); + } + + [Fact] + [Trait("Feature", "BatchedGetFirstByLowestScoreFromSet")] + public void Batch_GetFirstByLowestScoreFromSet_ReturnsTheValuesWithTheLowestScore() + { + UseConnection((database, connection) => + { + database.Database.InsertAll(new [] + { + new Set + { + Key = "key", + Score = 1.0m, + Value = "1.0" + }, + new Set + { + Key = "key", + Score = -1.0m, + Value = "-1.0" + }, + new Set + { + Key = "key", + Score = -4.0m, + Value = "-4.0" + }, + new Set + { + Key = "key", + Score = -5.0m, + Value = "-5.0" + }, + new Set + { + Key = "another-key", + Score = -2.0m, + Value = "-2.0" + } + }, typeof(Set)); + + var result = connection.GetFirstByLowestScoreFromSet("key", -5.0, 3.0, count: 3); + + Assert.Equal(new []{ "-5.0", "-4.0", "-1.0" }, result); + }); + } + } +} diff --git a/src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.GetSetCount.cs b/src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.GetSetCount.cs new file mode 100644 index 0000000..cda2b02 --- /dev/null +++ b/src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.GetSetCount.cs @@ -0,0 +1,146 @@ +using Hangfire.Common; +using Hangfire.Server; +using Hangfire.Storage.SQLite.Entities; +using Hangfire.Storage.SQLite.Test.Utils; +using Moq; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Xunit; + +namespace Hangfire.Storage.SQLite.Test +{ + public partial class HangfireSQLiteConnectionFacts + { + [Fact] + [Trait("Feature", "ExtendedApi")] + public void GetSetCount_ThrowsAnException_WhenKeyIsNull() + { + UseConnection((database, connection) => + { + Assert.Throws( + () => connection.GetSetCount(null)); + }); + } + + [Fact] + [Trait("Feature", "ExtendedApi")] + public void GetSetCount_ReturnsZero_WhenSetDoesNotExist() + { + UseConnection((database, connection) => + { + var result = connection.GetSetCount("my-set"); + Assert.Equal(0, result); + }); + } + + [Fact] + [Trait("Feature", "ExtendedApi")] + public void GetSetCount_ReturnsNumberOfElements_InASet() + { + UseConnection((database, connection) => + { + database.Database.Insert(new Set + { + Key = "set-1", + Value = "value-1" + }); + database.Database.Insert(new Set + { + Key = "set-2", + Value = "value-1" + }); + database.Database.Insert(new Set + { + Key = "set-1", + Value = "value-2" + }); + + var result = connection.GetSetCount("set-1"); + + Assert.Equal(2, result); + }); + } + + [Fact] + [Trait("Feature", "Connection.GetSetCount.Limited")] + public void Limited_GetSetCount_ThrowsAnException_WhenKeyIsNull() + { + UseConnection((database, connection) => + { + Assert.Throws( + () => connection.GetSetCount(null)); + }); + } + + [Fact] + [Trait("Feature", "Connection.GetSetCount.Limited")] + public void Limited_GetSetCount_ReturnsZero_WhenSetDoesNotExist() + { + UseConnection((database, connection) => + { + var result = connection.GetSetCount(new []{"my-set"}, 1); + Assert.Equal(0, result); + }); + } + + [Fact] + [Trait("Feature", "Connection.GetSetCount.Limited")] + public void Limited_GetSetCount_Returns_UpToLimit_NumberOfElements_InASet() + { + UseConnection((database, connection) => + { + database.Database.Insert(new Set + { + Key = "set-1", + Value = "value-1" + }); + database.Database.Insert(new Set + { + Key = "set-2", + Value = "value-1" + }); + database.Database.Insert(new Set + { + Key = "set-1", + Value = "value-2" + }); + + var result = connection.GetSetCount(new []{"set-1", "set-2"}, 2); + + Assert.Equal(2, result); + }); + } + + [Fact] + [Trait("Feature", "Connection.GetSetCount.Limited")] + public void Limited_GetSetCount_Returns_AllCounts_IfLimitHighEnough() + { + UseConnection((database, connection) => + { + database.Database.Insert(new Set + { + Key = "set-1", + Value = "value-1" + }); + database.Database.Insert(new Set + { + Key = "set-2", + Value = "value-1" + }); + database.Database.Insert(new Set + { + Key = "set-1", + Value = "value-2" + }); + + var result = connection.GetSetCount(new []{"set-1", "set-2"}, 99999); + + Assert.Equal(3, result); + }); + } + } +} diff --git a/src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.cs b/src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.cs index 03d301f..0a4d259 100644 --- a/src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.cs +++ b/src/test/Hangfire.Storage.SQLite.Test/SQLiteConnectionFacts.cs @@ -13,7 +13,7 @@ namespace Hangfire.Storage.SQLite.Test { - public class HangfireSQLiteConnectionFacts + public partial class HangfireSQLiteConnectionFacts { private readonly Mock _queue; private readonly PersistentJobQueueProviderCollection _providers; @@ -471,73 +471,6 @@ public void GetParameter_ReturnsParameterValue_WhenJobExists() }); } - [Fact] - public void GetFirstByLowestScoreFromSet_ThrowsAnException_WhenKeyIsNull() - { - UseConnection((database, connection) => - { - var exception = Assert.Throws( - () => connection.GetFirstByLowestScoreFromSet(null, 0, 1)); - - Assert.Equal("key", exception.ParamName); - }); - } - - [Fact] - public void GetFirstByLowestScoreFromSet_ThrowsAnException_ToScoreIsLowerThanFromScore() - { - UseConnection((database, connection) => Assert.Throws( - () => connection.GetFirstByLowestScoreFromSet("key", 0, -1))); - } - - [Fact] - public void GetFirstByLowestScoreFromSet_ReturnsNull_WhenTheKeyDoesNotExist() - { - UseConnection((database, connection) => - { - var result = connection.GetFirstByLowestScoreFromSet( - "key", 0, 1); - - Assert.Null(result); - }); - } - - [Fact] - public void GetFirstByLowestScoreFromSet_ReturnsTheValueWithTheLowestScore() - { - UseConnection((database, connection) => - { - database.Database.Insert(new Set - { - Key = "key", - Score = 1.0m, - Value = "1.0" - }); - database.Database.Insert(new Set - { - Key = "key", - Score = -1.0m, - Value = "-1.0" - }); - database.Database.Insert(new Set - { - Key = "key", - Score = -5.0m, - Value = "-5.0" - }); - database.Database.Insert(new Set - { - Key = "another-key", - Score = -2.0m, - Value = "-2.0" - }); - - var result = connection.GetFirstByLowestScoreFromSet("key", -1.0, 3.0); - - Assert.Equal("-1.0", result); - }); - } - [Fact] public void AnnounceServer_ThrowsAnException_WhenServerIdIsNull() { @@ -771,6 +704,28 @@ public void GetAllItemsFromSet_ReturnsAllItems_InCorrectOrder() }); } + [Theory] + [InlineData("v1", true)] + [InlineData("v2", false)] + public void GetSetContains_Returns_True_If_Value_Found(string value, bool contains) + { + UseConnection((database, connection) => + { + // Arrange + database.Database.Insert(new Set + { + Key = "list-1", + Value = "v1" + }); + + // Act + var result = connection.GetSetContains("list-1", value); + + // Assert + Assert.Equal(contains, result); + }); + } + [Fact] public void SetRangeInHash_ThrowsAnException_WhenKeyIsNull() { @@ -867,53 +822,6 @@ public void GetAllEntriesFromHash_ReturnsAllKeysAndTheirValues() }); } - [Fact] - public void GetSetCount_ThrowsAnException_WhenKeyIsNull() - { - UseConnection((database, connection) => - { - Assert.Throws( - () => connection.GetSetCount(null)); - }); - } - - [Fact] - public void GetSetCount_ReturnsZero_WhenSetDoesNotExist() - { - UseConnection((database, connection) => - { - var result = connection.GetSetCount("my-set"); - Assert.Equal(0, result); - }); - } - - [Fact] - public void GetSetCount_ReturnsNumberOfElements_InASet() - { - UseConnection((database, connection) => - { - database.Database.Insert(new Set - { - Key = "set-1", - Value = "value-1" - }); - database.Database.Insert(new Set - { - Key = "set-2", - Value = "value-1" - }); - database.Database.Insert(new Set - { - Key = "set-1", - Value = "value-2" - }); - - var result = connection.GetSetCount("set-1"); - - Assert.Equal(2, result); - }); - } - [Fact] public void GetRangeFromSet_ThrowsAnException_WhenKeyIsNull() { @@ -1523,6 +1431,22 @@ public void GetAllItemsFromList_ReturnsAllItemsFromAGivenList_InCorrectOrder() Assert.Equal(new[] { "5", "4", "3", "1" }, result); }); } + + [Fact] + public void GetUtcDateTime_IsSupported() + { + UseConnection((_, conn) => + { + var start = DateTime.UtcNow; + + // Act + var utcDateTime = conn.GetUtcDateTime(); + var end = DateTime.UtcNow; + + Assert.InRange(utcDateTime, start, end); + }); + } + private void UseConnection(Action action) { using var database = ConnectionUtils.CreateConnection(); diff --git a/src/test/Hangfire.Storage.SQLite.Test/SQLiteWriteOnlyTransactionFacts.cs b/src/test/Hangfire.Storage.SQLite.Test/SQLiteWriteOnlyTransactionFacts.cs index f74ea12..7a2778c 100644 --- a/src/test/Hangfire.Storage.SQLite.Test/SQLiteWriteOnlyTransactionFacts.cs +++ b/src/test/Hangfire.Storage.SQLite.Test/SQLiteWriteOnlyTransactionFacts.cs @@ -889,6 +889,51 @@ public void RemoveSet_ClearsTheSetData() }); } + [Fact] + public void AcquireDistributedLock_Returns_Lock() + { + UseConnection(database => + { + using (SQLiteWriteOnlyTransaction transaction = new SQLiteWriteOnlyTransaction(database, _queueProviders)) + { + transaction.AcquireDistributedLock("key", TimeSpan.Zero); + Assert.NotEmpty(transaction._acquiredLocks); + } + }); + } + + [Fact] + public void Committing_Transaction_Frees_Locks() + { + UseConnection(database => + { + using (SQLiteWriteOnlyTransaction transaction = new SQLiteWriteOnlyTransaction(database, _queueProviders)) + { + transaction.AcquireDistributedLock("key", TimeSpan.Zero); + Assert.NotEmpty(transaction._acquiredLocks); + + transaction.Commit(); + Assert.Empty(transaction._acquiredLocks); + } + }); + } + + [Fact] + public void Disposing_Transaction_Frees_Locks() + { + UseConnection(database => + { + using (SQLiteWriteOnlyTransaction transaction = new SQLiteWriteOnlyTransaction(database, _queueProviders)) + { + transaction.AcquireDistributedLock("key", TimeSpan.Zero); + Assert.NotEmpty(transaction._acquiredLocks); + + transaction.Dispose(); + Assert.Empty(transaction._acquiredLocks); + } + }); + } + private static HangfireJob GetTestJob(HangfireDbContext database, int jobId) { return database.HangfireJobRepository.FirstOrDefault(x => x.Id == jobId);