235 lines
7.7 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|