Add managed connection pooling and execution locking

This commit is contained in:
2026-02-27 17:28:50 +01:00
parent e43d7863ec
commit aedb97e879
12 changed files with 1554 additions and 348 deletions

View File

@@ -0,0 +1,153 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*/
using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
namespace DynamORM.Tests.Helpers
{
[TestFixture]
public class ConnectionPoolingAndLockingTests
{
private FakeProviderState _state;
private DynamicDatabase _db;
[TearDown]
public void TearDown()
{
if (_db != null)
_db.Dispose();
}
[Test]
public void TestConnectionPoolingReusesIdleConnection()
{
CreateDatabase(DynamicDatabaseOptions.ConnectionPooling);
using (_db.Open()) { }
using (_db.Open()) { }
Assert.AreEqual(1, _state.CreatedConnections);
Assert.AreEqual(1, _state.OpenCalls);
Assert.AreEqual(0, _state.CloseCalls);
}
[Test]
public void TestConnectionPoolingWaitsForReleasedConnectionWhenAtMaximum()
{
CreateDatabase(DynamicDatabaseOptions.ConnectionPooling);
_db.ConnectionPoolingKeepOpenCount = 1;
_db.ConnectionPoolingMaximumOpenCount = 1;
var first = _db.Open();
var started = new ManualResetEventSlim(false);
var completed = new ManualResetEventSlim(false);
var task = Task.Run(() =>
{
started.Set();
using (_db.Open()) { }
completed.Set();
});
Assert.IsTrue(started.Wait(TimeSpan.FromSeconds(2)));
Assert.IsFalse(completed.Wait(TimeSpan.FromMilliseconds(200)));
first.Dispose();
Assert.IsTrue(completed.Wait(TimeSpan.FromSeconds(2)));
task.Wait(TimeSpan.FromSeconds(2));
Assert.AreEqual(1, _state.CreatedConnections);
}
[Test]
public void TestConnectionPoolingClosesExpiredIdleConnections()
{
CreateDatabase(DynamicDatabaseOptions.ConnectionPooling);
_db.ConnectionPoolingConnectionLifetime = TimeSpan.Zero;
using (_db.Open()) { }
using (_db.Open()) { }
Assert.AreEqual(2, _state.CreatedConnections);
Assert.GreaterOrEqual(_state.CloseCalls, 1);
Assert.GreaterOrEqual(_state.DisposeCalls, 1);
}
[Test]
public void TestDirectTransactionUsesSameThreadConnectionAndSeparateThreadGetsDifferentOne()
{
CreateDatabase(DynamicDatabaseOptions.ConnectionPooling);
using (var trans = _db.BeginTransaction())
{
IDbConnection threadLocalA = _db.Open();
IDbConnection threadLocalB = _db.Open();
try
{
Assert.AreSame(((DynamicConnection)threadLocalA).Connection, ((DynamicConnection)threadLocalB).Connection);
IDbConnection otherThreadConnection = null;
var task = Task.Run(() =>
{
using (var other = _db.Open())
otherThreadConnection = ((DynamicConnection)other).Connection;
});
Assert.IsTrue(task.Wait(TimeSpan.FromSeconds(2)));
Assert.AreNotSame(((DynamicConnection)threadLocalA).Connection, otherThreadConnection);
}
finally
{
threadLocalA.Dispose();
threadLocalB.Dispose();
}
}
}
[TestCase(DynamicDatabaseOptions.SingleConnection)]
[TestCase(DynamicDatabaseOptions.SingleTransaction)]
public void TestSingleModesSerializeCommandExecution(DynamicDatabaseOptions option)
{
CreateDatabase(option);
_state.BlockFirstExecution = true;
_state.AllowExecution.Reset();
Task task1 = Task.Run(() => ExecuteFakeCommand());
Assert.IsTrue(_state.FirstExecutionEntered.Wait(TimeSpan.FromSeconds(2)));
Task task2 = Task.Run(() => ExecuteFakeCommand());
Thread.Sleep(200);
Assert.AreEqual(1, _state.MaxConcurrentExecutions);
_state.AllowExecution.Set();
Assert.IsTrue(Task.WaitAll(new[] { task1, task2 }, TimeSpan.FromSeconds(5)));
Assert.AreEqual(1, _state.MaxConcurrentExecutions);
}
private void ExecuteFakeCommand()
{
using (var connection = _db.Open())
using (var command = connection.CreateCommand())
{
command.SetCommand("SELECT 1;");
command.ExecuteNonQuery();
}
}
private void CreateDatabase(DynamicDatabaseOptions options)
{
_state = new FakeProviderState();
_db = new DynamicDatabase(new FakeProviderFactory(_state), "fake", options);
}
}
}