Strengthen connection pooling and locking tests
This commit is contained in:
@@ -80,6 +80,26 @@ namespace DynamORM.Tests.Helpers
|
|||||||
Assert.GreaterOrEqual(_state.DisposeCalls, 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]
|
[Test]
|
||||||
public void TestDirectTransactionUsesSameThreadConnectionAndSeparateThreadGetsDifferentOne()
|
public void TestDirectTransactionUsesSameThreadConnectionAndSeparateThreadGetsDifferentOne()
|
||||||
{
|
{
|
||||||
@@ -112,6 +132,29 @@ namespace DynamORM.Tests.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[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.SingleConnection)]
|
||||||
[TestCase(DynamicDatabaseOptions.SingleTransaction)]
|
[TestCase(DynamicDatabaseOptions.SingleTransaction)]
|
||||||
public void TestSingleModesSerializeCommandExecution(DynamicDatabaseOptions option)
|
public void TestSingleModesSerializeCommandExecution(DynamicDatabaseOptions option)
|
||||||
@@ -134,6 +177,44 @@ namespace DynamORM.Tests.Helpers
|
|||||||
Assert.AreEqual(1, _state.MaxConcurrentExecutions);
|
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()
|
private void ExecuteFakeCommand()
|
||||||
{
|
{
|
||||||
using (var connection = _db.Open())
|
using (var connection = _db.Open())
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
|||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DynamORM.Tests.Helpers
|
namespace DynamORM.Tests.Helpers
|
||||||
{
|
{
|
||||||
@@ -23,11 +24,16 @@ namespace DynamORM.Tests.Helpers
|
|||||||
public int MaxConcurrentExecutions;
|
public int MaxConcurrentExecutions;
|
||||||
public int CurrentConcurrentExecutions;
|
public int CurrentConcurrentExecutions;
|
||||||
public object LastTransactionSeen;
|
public object LastTransactionSeen;
|
||||||
|
public readonly List<object> SeenTransactions = new List<object>();
|
||||||
public readonly List<FakeProviderConnection> Connections = new List<FakeProviderConnection>();
|
public readonly List<FakeProviderConnection> Connections = new List<FakeProviderConnection>();
|
||||||
public ManualResetEventSlim FirstExecutionEntered = new ManualResetEventSlim(false);
|
public ManualResetEventSlim FirstExecutionEntered = new ManualResetEventSlim(false);
|
||||||
public ManualResetEventSlim AllowExecution = new ManualResetEventSlim(true);
|
public ManualResetEventSlim AllowExecution = new ManualResetEventSlim(true);
|
||||||
public bool BlockFirstExecution;
|
public bool BlockFirstExecution;
|
||||||
|
public bool BlockFirstReader;
|
||||||
|
public ManualResetEventSlim FirstReaderEntered = new ManualResetEventSlim(false);
|
||||||
|
public ManualResetEventSlim AllowReader = new ManualResetEventSlim(true);
|
||||||
private int _blocked;
|
private int _blocked;
|
||||||
|
private int _readerBlocked;
|
||||||
|
|
||||||
public void RecordExecution(IDbTransaction transaction)
|
public void RecordExecution(IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
@@ -39,6 +45,8 @@ namespace DynamORM.Tests.Helpers
|
|||||||
Interlocked.Increment(ref ExecuteNonQueryCalls);
|
Interlocked.Increment(ref ExecuteNonQueryCalls);
|
||||||
if (transaction != null)
|
if (transaction != null)
|
||||||
LastTransactionSeen = transaction;
|
LastTransactionSeen = transaction;
|
||||||
|
lock (SeenTransactions)
|
||||||
|
SeenTransactions.Add(transaction);
|
||||||
|
|
||||||
if (BlockFirstExecution && Interlocked.CompareExchange(ref _blocked, 1, 0) == 0)
|
if (BlockFirstExecution && Interlocked.CompareExchange(ref _blocked, 1, 0) == 0)
|
||||||
{
|
{
|
||||||
@@ -51,6 +59,25 @@ namespace DynamORM.Tests.Helpers
|
|||||||
{
|
{
|
||||||
Interlocked.Decrement(ref CurrentConcurrentExecutions);
|
Interlocked.Decrement(ref CurrentConcurrentExecutions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RecordReaderOpen(IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
int current = Interlocked.Increment(ref CurrentConcurrentExecutions);
|
||||||
|
int snapshot;
|
||||||
|
while ((snapshot = MaxConcurrentExecutions) < current)
|
||||||
|
Interlocked.CompareExchange(ref MaxConcurrentExecutions, current, snapshot);
|
||||||
|
|
||||||
|
if (transaction != null)
|
||||||
|
LastTransactionSeen = transaction;
|
||||||
|
lock (SeenTransactions)
|
||||||
|
SeenTransactions.Add(transaction);
|
||||||
|
|
||||||
|
if (BlockFirstReader && Interlocked.CompareExchange(ref _readerBlocked, 1, 0) == 0)
|
||||||
|
{
|
||||||
|
FirstReaderEntered.Set();
|
||||||
|
AllowReader.Wait(TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class FakeProviderFactory : DbProviderFactory
|
internal sealed class FakeProviderFactory : DbProviderFactory
|
||||||
@@ -192,7 +219,67 @@ namespace DynamORM.Tests.Helpers
|
|||||||
|
|
||||||
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
|
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
_state.RecordReaderOpen(DbTransaction);
|
||||||
|
return new FakeProviderDataReader(_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class FakeProviderDataReader : DbDataReader
|
||||||
|
{
|
||||||
|
private readonly FakeProviderState _state;
|
||||||
|
private bool _closed;
|
||||||
|
|
||||||
|
public FakeProviderDataReader(FakeProviderState state)
|
||||||
|
{
|
||||||
|
_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object this[int ordinal] { get { return 1; } }
|
||||||
|
public override object this[string name] { get { return 1; } }
|
||||||
|
public override int Depth { get { return 0; } }
|
||||||
|
public override int FieldCount { get { return 1; } }
|
||||||
|
public override bool HasRows { get { return true; } }
|
||||||
|
public override bool IsClosed { get { return _closed; } }
|
||||||
|
public override int RecordsAffected { get { return 0; } }
|
||||||
|
|
||||||
|
public override bool GetBoolean(int ordinal) { return true; }
|
||||||
|
public override byte GetByte(int ordinal) { return 1; }
|
||||||
|
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) { return 0; }
|
||||||
|
public override char GetChar(int ordinal) { return '1'; }
|
||||||
|
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) { return 0; }
|
||||||
|
public override string GetDataTypeName(int ordinal) { return "Int32"; }
|
||||||
|
public override DateTime GetDateTime(int ordinal) { return DateTime.UtcNow; }
|
||||||
|
public override decimal GetDecimal(int ordinal) { return 1m; }
|
||||||
|
public override double GetDouble(int ordinal) { return 1d; }
|
||||||
|
public override System.Collections.IEnumerator GetEnumerator() { yield break; }
|
||||||
|
public override Type GetFieldType(int ordinal) { return typeof(int); }
|
||||||
|
public override float GetFloat(int ordinal) { return 1f; }
|
||||||
|
public override Guid GetGuid(int ordinal) { return Guid.Empty; }
|
||||||
|
public override short GetInt16(int ordinal) { return 1; }
|
||||||
|
public override int GetInt32(int ordinal) { return 1; }
|
||||||
|
public override long GetInt64(int ordinal) { return 1L; }
|
||||||
|
public override string GetName(int ordinal) { return "Value"; }
|
||||||
|
public override int GetOrdinal(string name) { return 0; }
|
||||||
|
public override string GetString(int ordinal) { return "1"; }
|
||||||
|
public override object GetValue(int ordinal) { return 1; }
|
||||||
|
public override int GetValues(object[] values) { values[0] = 1; return 1; }
|
||||||
|
public override bool IsDBNull(int ordinal) { return false; }
|
||||||
|
public override bool NextResult() { return false; }
|
||||||
|
public override bool Read() { return false; }
|
||||||
|
|
||||||
|
public override void Close()
|
||||||
|
{
|
||||||
|
if (!_closed)
|
||||||
|
{
|
||||||
|
_closed = true;
|
||||||
|
_state.ExitExecution();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user