Skip to content

Commit

Permalink
Simplify testing, improve performance
Browse files Browse the repository at this point in the history
- introduced SqliteInMemoryTestBase to simplify correct usage of in-memory database
  - as in-memory databases get deleted once their last connection is "closed", we should always ensure that a test keeps the database alive until its finished, no matter how often the newly created connections get closed.
- improve performance when acquiring connections, by only migrating the tables on the creation of the storage
- reduced allocations by making use of TState-parameter in TryFewTimesDueToConcurrency
- replace manual `SQLITE_OPEN_URI`-constant with `SQLitePCL.raw.SQLITE_OPEN_URI`

Signed-off-by: kirides <[email protected]>
  • Loading branch information
kirides committed Apr 19, 2024
1 parent 8dc26d7 commit ac53b8f
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 115 deletions.
62 changes: 33 additions & 29 deletions src/main/Hangfire.Storage.SQLite/HangfireDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,41 +50,45 @@ public void Init(SQLiteStorageOptions storageOptions)
{
StorageOptions = storageOptions;

TryFewTimesDueToConcurrency(() => InitializePragmas(storageOptions));
TryFewTimesDueToConcurrency(() => Database.CreateTable<AggregatedCounter>());
TryFewTimesDueToConcurrency(() => Database.CreateTable<Counter>());
TryFewTimesDueToConcurrency(() => Database.CreateTable<HangfireJob>());
TryFewTimesDueToConcurrency(() => Database.CreateTable<HangfireList>());
TryFewTimesDueToConcurrency(() => Database.CreateTable<Hash>());
TryFewTimesDueToConcurrency(() => Database.CreateTable<JobParameter>());
TryFewTimesDueToConcurrency(() => Database.CreateTable<JobQueue>());
TryFewTimesDueToConcurrency(() => Database.CreateTable<HangfireServer>());
TryFewTimesDueToConcurrency(() => Database.CreateTable<Set>());
TryFewTimesDueToConcurrency(() => Database.CreateTable<State>());
TryFewTimesDueToConcurrency(() => Database.CreateTable<DistributedLock>());

void TryFewTimesDueToConcurrency(Action action, int times = 10)
TryFewTimesDueToConcurrency(state => state.Item1.InitializePragmas(state.storageOptions),
(this, storageOptions));
}

public void Migrate()
{
TryFewTimesDueToConcurrency(db => db.CreateTable<AggregatedCounter>(), Database);
TryFewTimesDueToConcurrency(db => db.CreateTable<Counter>(), Database);
TryFewTimesDueToConcurrency(db => db.CreateTable<HangfireJob>(), Database);
TryFewTimesDueToConcurrency(db => db.CreateTable<HangfireList>(), Database);
TryFewTimesDueToConcurrency(db => db.CreateTable<Hash>(), Database);
TryFewTimesDueToConcurrency(db => db.CreateTable<JobParameter>(), Database);
TryFewTimesDueToConcurrency(db => db.CreateTable<JobQueue>(), Database);
TryFewTimesDueToConcurrency(db => db.CreateTable<HangfireServer>(), Database);
TryFewTimesDueToConcurrency(db => db.CreateTable<Set>(), Database);
TryFewTimesDueToConcurrency(db => db.CreateTable<State>(), Database);
TryFewTimesDueToConcurrency(db => db.CreateTable<DistributedLock>(), Database);
}

static void TryFewTimesDueToConcurrency<TState>(Action<TState> action, TState state, int times = 10)
{
var current = 0;
while (current < times)
{
var current = 0;
while (current < times)
try
{
action(state);
return;
}
catch (SQLiteException e) when (e.Result == SQLite3.Result.Locked)
{
try
{
action();
return;
}
catch (SQLiteException e) when (e.Result == SQLite3.Result.Locked)
{
// This can happen if too many connections are opened
// at the same time, trying to create tables
Thread.Sleep(10);
}
current++;
// This can happen if too many connections are opened
// at the same time, trying to create tables
Thread.Sleep(10);
}
current++;
}
}


private void InitializePragmas(SQLiteStorageOptions storageOptions)
{
try
Expand Down
1 change: 1 addition & 0 deletions src/main/Hangfire.Storage.SQLite/SQLiteStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public SQLiteStorage(SQLiteDbConnectionFactory dbConnectionFactory, SQLiteStorag

using (var dbContext = CreateAndOpenConnection())
{
dbContext.Migrate();
_databasePath = dbContext.Database.DatabasePath;
// Use this to initialize the database as soon as possible
// in case of error, the user will immediately get an exception at startup
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
using Hangfire.Storage.SQLite.Entities;
using Hangfire.Storage.SQLite.Test.Utils;
using System;
using System.Threading;
using Xunit;

namespace Hangfire.Storage.SQLite.Test
{
public class CountersAggregatorFacts
public class CountersAggregatorFacts : SqliteInMemoryTestBase
{
[Fact]
public void CountersAggregatorExecutesProperly()
{
var storage = ConnectionUtils.CreateStorage();
using (var connection = (HangfireSQLiteConnection)storage.GetConnection())
using (var connection = (HangfireSQLiteConnection)Storage.GetConnection())
{
// Arrange
connection.DbContext.Database.Insert(new Counter
Expand All @@ -23,7 +21,7 @@ public void CountersAggregatorExecutesProperly()
ExpireAt = DateTime.UtcNow.AddHours(1)
});

var aggregator = new CountersAggregator(storage, TimeSpan.Zero);
var aggregator = new CountersAggregator(Storage, TimeSpan.Zero);
var cts = new CancellationTokenSource();
cts.Cancel();

Expand Down
28 changes: 12 additions & 16 deletions src/test/Hangfire.Storage.SQLite.Test/ExpirationManagerFacts.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
using Hangfire.Storage.SQLite.Entities;
using Hangfire.Storage.SQLite.Test.Utils;
using System;
using System.Collections.Generic;
using System.Threading;
using Xunit;

namespace Hangfire.Storage.SQLite.Test
{
public class ExpirationManagerFacts
public class ExpirationManagerFacts : SqliteInMemoryTestBase
{
private readonly SQLiteStorage _storage;

private readonly CancellationToken _token;
private static PersistentJobQueueProviderCollection _queueProviders;

public ExpirationManagerFacts()
{
_storage = ConnectionUtils.CreateStorage();
_queueProviders = _storage.QueueProviders;
_queueProviders = Storage.QueueProviders;

_token = new CancellationToken(true);
}
Expand All @@ -31,7 +27,7 @@ public void Ctor_ThrowsAnException_WhenStorageIsNull()
[Fact]
public void Execute_RemovesOutdatedRecords()
{
using var connection = _storage.CreateAndOpenConnection();
using var connection = Storage.CreateAndOpenConnection();
CreateExpirationEntries(connection, DateTime.UtcNow.AddMonths(-1));
var manager = CreateManager();
manager.Execute(_token);
Expand All @@ -41,7 +37,7 @@ public void Execute_RemovesOutdatedRecords()
[Fact]
public void Execute_DoesNotRemoveEntries_WithNoExpirationTimeSet()
{
using var connection = _storage.CreateAndOpenConnection();
using var connection = Storage.CreateAndOpenConnection();
CreateExpirationEntries(connection, null);
var manager = CreateManager();
manager.Execute(_token);
Expand All @@ -51,7 +47,7 @@ public void Execute_DoesNotRemoveEntries_WithNoExpirationTimeSet()
[Fact]
public void Execute_DoesNotRemoveEntries_WithFreshExpirationTime()
{
using var connection = _storage.CreateAndOpenConnection();
using var connection = Storage.CreateAndOpenConnection();
CreateExpirationEntries(connection, DateTime.UtcNow.AddMonths(1));
var manager = CreateManager();
manager.Execute(_token);
Expand All @@ -61,7 +57,7 @@ public void Execute_DoesNotRemoveEntries_WithFreshExpirationTime()
[Fact]
public void Execute_Processes_CounterTable()
{
using var connection = _storage.CreateAndOpenConnection();
using var connection = Storage.CreateAndOpenConnection();
connection.Database.Insert(new Counter
{
Id = Guid.NewGuid().ToString(),
Expand All @@ -78,7 +74,7 @@ public void Execute_Processes_CounterTable()
[Fact]
public void Execute_Processes_JobTable()
{
using var connection = _storage.CreateAndOpenConnection();
using var connection = Storage.CreateAndOpenConnection();
connection.Database.Insert(new HangfireJob()
{
InvocationData = "",
Expand All @@ -95,7 +91,7 @@ public void Execute_Processes_JobTable()
[Fact]
public void Execute_Processes_ListTable()
{
using var connection = _storage.CreateAndOpenConnection();
using var connection = Storage.CreateAndOpenConnection();
connection.Database.Insert(new HangfireList()
{
Key = "key",
Expand All @@ -112,7 +108,7 @@ public void Execute_Processes_ListTable()
[Fact]
public void Execute_Processes_SetTable()
{
using var connection = _storage.CreateAndOpenConnection();
using var connection = Storage.CreateAndOpenConnection();
connection.Database.Insert(new Set
{
Key = "key",
Expand All @@ -131,7 +127,7 @@ public void Execute_Processes_SetTable()
[Fact]
public void Execute_Processes_HashTable()
{
using var connection = _storage.CreateAndOpenConnection();
using var connection = Storage.CreateAndOpenConnection();
connection.Database.Insert(new Hash()
{
Key = "key",
Expand All @@ -151,7 +147,7 @@ public void Execute_Processes_HashTable()
[Fact]
public void Execute_Processes_AggregatedCounterTable()
{
using var connection = _storage.CreateAndOpenConnection();
using var connection = Storage.CreateAndOpenConnection();
connection.Database.Insert(new AggregatedCounter
{
Key = "key",
Expand Down Expand Up @@ -199,7 +195,7 @@ private static bool IsEntryExpired(HangfireDbContext connection)

private ExpirationManager CreateManager()
{
return new ExpirationManager(_storage);
return new ExpirationManager(Storage);
}

private static void Commit(HangfireDbContext connection, Action<SQLiteWriteOnlyTransaction> action)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
using Hangfire.Common;
using Hangfire.Server;
using Hangfire.Storage.SQLite.Entities;
using Hangfire.Storage.SQLite.Test.Utils;
using Moq;
using Newtonsoft.Json;
using Hangfire.Storage.SQLite.Entities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Xunit;

namespace Hangfire.Storage.SQLite.Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
using Hangfire.Common;
using Hangfire.Server;
using Hangfire.Storage.SQLite.Entities;
using Hangfire.Storage.SQLite.Test.Utils;
using Moq;
using Newtonsoft.Json;
using Hangfire.Storage.SQLite.Entities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Xunit;

namespace Hangfire.Storage.SQLite.Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace Hangfire.Storage.SQLite.Test
{
public partial class HangfireSQLiteConnectionFacts
public partial class HangfireSQLiteConnectionFacts : SqliteInMemoryTestBase
{
private readonly Mock<IPersistentJobQueue> _queue;
private readonly PersistentJobQueueProviderCollection _providers;
Expand Down Expand Up @@ -41,7 +41,7 @@ public void Ctor_ThrowsAnException_WhenConnectionIsNull()
public void Ctor_ThrowsAnException_WhenProvidersCollectionIsNull()
{
var exception = Assert.Throws<ArgumentNullException>(
() => new HangfireSQLiteConnection(ConnectionUtils.CreateConnection(), null));
() => new HangfireSQLiteConnection(Storage.CreateAndOpenConnection(), null));

Assert.Equal("queueProviders", exception.ParamName);
}
Expand Down Expand Up @@ -1449,7 +1449,7 @@ public void GetUtcDateTime_IsSupported()

private void UseConnection(Action<HangfireDbContext, HangfireSQLiteConnection> action)
{
using var database = ConnectionUtils.CreateConnection();
using var database = Storage.CreateAndOpenConnection();
using var connection = new HangfireSQLiteConnection(database, _providers);
action(database, connection);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Hangfire.Storage.SQLite.Test
{
public class SQLiteDistributedLockFacts
public class SQLiteDistributedLockFacts : SqliteInMemoryTestBase
{
[Fact]
public void Ctor_ThrowsAnException_WhenResourceIsNull()
Expand Down Expand Up @@ -117,7 +117,6 @@ public void Ctor_ThrowsAnException_WhenResourceIsLocked()
[Fact]
public void Ctor_WaitForLock_SignaledAtLockRelease()
{
var storage = ConnectionUtils.CreateStorage();
using var mre = new ManualResetEventSlim();
var t = NewBackgroundThread(() =>
{
Expand All @@ -128,7 +127,7 @@ public void Ctor_WaitForLock_SignaledAtLockRelease()
mre.Set();
Thread.Sleep(TimeSpan.FromSeconds(3));
}
}, storage);
});
});
UseConnection(database =>
{
Expand All @@ -145,7 +144,7 @@ public void Ctor_WaitForLock_SignaledAtLockRelease()
}

t.Join();
}, storage);
});
}

[Fact]
Expand Down Expand Up @@ -306,15 +305,15 @@ private async Task<bool> WaitForHeartBeat(SQLiteDistributedLock slock, TimeSpan
}
}

private static void UseConnection(Action<HangfireDbContext> action, SQLiteStorage storage = null)
private void UseConnection(Action<HangfireDbContext> action)
{
using var connection = storage?.CreateAndOpenConnection() ?? ConnectionUtils.CreateConnection();
using var connection = Storage.CreateAndOpenConnection();
action(connection);
}

private static async Task UseConnectionAsync(Func<HangfireDbContext, Task> func, SQLiteStorage storage = null)
private async Task UseConnectionAsync(Func<HangfireDbContext, Task> func)
{
using var connection = storage?.CreateAndOpenConnection() ?? ConnectionUtils.CreateConnection();
using var connection = Storage.CreateAndOpenConnection();
await func(connection);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using Hangfire.Storage.SQLite.Entities;
using Hangfire.Storage.SQLite.Test.Utils;
using System;
using System.Linq;
using Xunit;

namespace Hangfire.Storage.SQLite.Test
{
public class SQLiteFetchedJobFacts
public class SQLiteFetchedJobFacts : SqliteInMemoryTestBase
{
private const int JobId = 0;
private const string Queue = "queue";
Expand Down Expand Up @@ -153,9 +152,9 @@ private static int CreateJobQueueRecord(HangfireDbContext connection, int jobId,
return jobQueue.Id;
}

private static void UseConnection(Action<HangfireDbContext> action)
private void UseConnection(Action<HangfireDbContext> action)
{
using var connection = ConnectionUtils.CreateConnection();
using var connection = Storage.CreateAndOpenConnection();
action(connection);
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/test/Hangfire.Storage.SQLite.Test/SQLiteJobQueueFacts.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using Hangfire.Storage.SQLite.Entities;
using Hangfire.Storage.SQLite.Test.Utils;
using System;
using System.Linq;
using System.Threading;
using Xunit;

namespace Hangfire.Storage.SQLite.Test
{
public class SQLiteJobQueueFacts
public class SQLiteJobQueueFacts : SqliteInMemoryTestBase
{
private static readonly string[] DefaultQueues = { "default" };

Expand Down Expand Up @@ -331,9 +330,9 @@ private static SQLiteJobQueue CreateJobQueue(HangfireDbContext connection)
return new SQLiteJobQueue(connection, new SQLiteStorageOptions());
}

private static void UseConnection(Action<HangfireDbContext> action)
private void UseConnection(Action<HangfireDbContext> action)
{
using var connection = ConnectionUtils.CreateConnection();
using var connection = Storage.CreateAndOpenConnection();
action(connection);
}
}
Expand Down
Loading

0 comments on commit ac53b8f

Please sign in to comment.