Files
DynamORM/DynamORM.Tests/Helpers/ConnectionPoolingAndLockingTests.cs

235 lines
7.7 KiB
C#

/*
* 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 TestConnectionPoolingTrimsIdleConnectionsAbovePreferredLimit()
{
CreateDatabase(DynamicDatabaseOptions.ConnectionPooling);
_db.ConnectionPoolingKeepOpenCount = 1;
_db.ConnectionPoolingMaximumOpenCount = 3;
var c1 = _db.Open();
var c2 = _db.Open();
var c3 = _db.Open();
c1.Dispose();
c2.Dispose();
c3.Dispose();
Assert.AreEqual(3, _state.CreatedConnections);
Assert.AreEqual(2, _state.CloseCalls);
Assert.AreEqual(2, _state.DisposeCalls);
}
[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();
}
}
}
[Test]
public void TestDirectTransactionUsesTransactionOnlyOnOwningThread()
{
CreateDatabase(DynamicDatabaseOptions.ConnectionPooling);
using (var transaction = _db.BeginTransaction())
{
ExecuteFakeCommand();
var otherThread = Task.Run(() => ExecuteFakeCommand());
Assert.IsTrue(otherThread.Wait(TimeSpan.FromSeconds(2)));
transaction.Commit();
}
lock (_state.SeenTransactions)
{
Assert.AreEqual(2, _state.SeenTransactions.Count);
Assert.NotNull(_state.SeenTransactions[0]);
Assert.IsNull(_state.SeenTransactions[1]);
}
}
[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);
}
[TestCase(DynamicDatabaseOptions.SingleConnection)]
[TestCase(DynamicDatabaseOptions.SingleTransaction)]
public void TestSingleModesSerializeReaderLifetime(DynamicDatabaseOptions option)
{
CreateDatabase(option);
_state.BlockFirstReader = true;
_state.AllowReader.Reset();
IDataReader reader = null;
var task1 = Task.Run(() =>
{
using (var connection = _db.Open())
using (var command = connection.CreateCommand())
{
command.SetCommand("SELECT 1;");
reader = command.ExecuteReader();
}
});
Assert.IsTrue(_state.FirstReaderEntered.Wait(TimeSpan.FromSeconds(2)));
var secondCompleted = new ManualResetEventSlim(false);
var task2 = Task.Run(() =>
{
ExecuteFakeCommand();
secondCompleted.Set();
});
Assert.IsFalse(secondCompleted.Wait(TimeSpan.FromMilliseconds(200)));
_state.AllowReader.Set();
SpinWait.SpinUntil(() => reader != null, TimeSpan.FromSeconds(2));
reader.Dispose();
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);
}
}
}