diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
index a8e23c7e75..a720a27650 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
@@ -265,6 +265,10 @@
+
+
+
+
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecAsyncTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecAsyncTest.cs
index 3cb601b0b0..cdf89fbac1 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecAsyncTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecAsyncTest.cs
@@ -34,38 +34,18 @@ private static string GenerateCommandText()
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public static void ExecuteTest()
{
- using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString))
+ using SqlConnection connection = new(DataTestUtility.TCPConnectionString);
+
+ using SqlCommand command = new(GenerateCommandText(), connection);
+ connection.Open();
+
+ IAsyncResult result = command.BeginExecuteNonQuery();
+ while (!result.IsCompleted)
{
- try
- {
- SqlCommand command = new SqlCommand(GenerateCommandText(), connection);
- connection.Open();
-
- IAsyncResult result = command.BeginExecuteNonQuery();
- while (!result.IsCompleted)
- {
- System.Threading.Thread.Sleep(100);
- }
- Assert.True(command.EndExecuteNonQuery(result) > 0, "FAILED: BeginExecuteNonQuery did not complete successfully.");
- }
- catch (SqlException ex)
- {
- Console.WriteLine("Error ({0}): {1}", ex.Number, ex.Message);
- Assert.Null(ex);
- }
- catch (InvalidOperationException ex)
- {
- Console.WriteLine("Error: {0}", ex.Message);
- Assert.Null(ex);
- }
- catch (Exception ex)
- {
- // You might want to pass these errors
- // back out to the caller.
- Console.WriteLine("Error: {0}", ex.Message);
- Assert.Null(ex);
- }
+ System.Threading.Thread.Sleep(100);
}
+
+ Assert.True(command.EndExecuteNonQuery(result) > 0, "FAILED: BeginExecuteNonQuery did not complete successfully.");
}
// Synapse: Parse error at line: 1, column: 201: Incorrect syntax near ';'.
@@ -74,24 +54,12 @@ public static void FailureTest()
{
using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString))
{
- bool caughtException = false;
SqlCommand command = new SqlCommand(GenerateCommandText(), connection);
connection.Open();
//Try to execute a synchronous query on same command
IAsyncResult result = command.BeginExecuteNonQuery();
- try
- {
- command.ExecuteNonQuery();
- }
- catch (Exception ex)
- {
- Assert.True(ex is InvalidOperationException, "FAILED: Thrown exception for BeginExecuteNonQuery was not an InvalidOperationException");
- caughtException = true;
- }
-
- Assert.True(caughtException, "FAILED: No exception thrown after trying second BeginExecuteNonQuery.");
- caughtException = false;
+ InvalidOperationException ex = Assert.Throws(() => command.ExecuteNonQuery());
while (!result.IsCompleted)
{
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs
index a26d42e02d..38c62d69e9 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs
@@ -18,6 +18,22 @@ public class InternalConnectionWrapper
private object _internalConnection = null;
private object _spid = null;
+ ///
+ /// Is this internal connection enlisted in a distributed transaction?
+ ///
+ public bool IsEnlistedInTransaction => ConnectionHelper.IsEnlistedInTransaction(_internalConnection);
+
+ ///
+ /// Is this internal connection the root of a distributed transaction?
+ ///
+ public bool IsTransactionRoot => ConnectionHelper.IsTransactionRoot(_internalConnection);
+
+ ///
+ /// True if this connection is the root of a transaction AND it is waiting for the transaction
+ /// to complete (i.e. it has been 'aged' or 'put into stasis'), otherwise false
+ ///
+ public bool IsTxRootWaitingForTxEnd => ConnectionHelper.IsTxRootWaitingForTxEnd(_internalConnection);
+
///
/// Gets the internal connection associated with the given SqlConnection
///
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionHelper.cs
index 32bac50d08..4f83a8aeb7 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionHelper.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionHelper.cs
@@ -32,6 +32,9 @@ internal static class ConnectionHelper
private static PropertyInfo s_pendingSQLDNS_AddrIPv4 = s_SQLDNSInfo.GetProperty("AddrIPv4", BindingFlags.Instance | BindingFlags.Public);
private static PropertyInfo s_pendingSQLDNS_AddrIPv6 = s_SQLDNSInfo.GetProperty("AddrIPv6", BindingFlags.Instance | BindingFlags.Public);
private static PropertyInfo s_pendingSQLDNS_Port = s_SQLDNSInfo.GetProperty("Port", BindingFlags.Instance | BindingFlags.Public);
+ private static PropertyInfo dbConnectionInternalIsTransRoot = s_dbConnectionInternal.GetProperty("IsTransactionRoot", BindingFlags.Instance | BindingFlags.NonPublic);
+ private static PropertyInfo dbConnectionInternalEnlistedTrans = s_sqlInternalConnection.GetProperty("EnlistedTransaction", BindingFlags.Instance | BindingFlags.NonPublic);
+ private static PropertyInfo dbConnectionInternalIsTxRootWaitingForTxEnd = s_dbConnectionInternal.GetProperty("IsTxRootWaitingForTxEnd", BindingFlags.Instance | BindingFlags.NonPublic);
public static object GetConnectionPool(object internalConnection)
{
@@ -69,6 +72,24 @@ private static void VerifyObjectIsConnection(object connection)
throw new ArgumentException("Object provided was not a SqlConnection", nameof(connection));
}
+ public static bool IsEnlistedInTransaction(object internalConnection)
+ {
+ VerifyObjectIsInternalConnection(internalConnection);
+ return (dbConnectionInternalEnlistedTrans.GetValue(internalConnection, null) != null);
+ }
+
+ public static bool IsTransactionRoot(object internalConnection)
+ {
+ VerifyObjectIsInternalConnection(internalConnection);
+ return (bool)dbConnectionInternalIsTransRoot.GetValue(internalConnection, null);
+ }
+
+ public static bool IsTxRootWaitingForTxEnd(object internalConnection)
+ {
+ VerifyObjectIsInternalConnection(internalConnection);
+ return (bool)dbConnectionInternalIsTxRootWaitingForTxEnd.GetValue(internalConnection, null);
+ }
+
public static object GetParser(object internalConnection)
{
VerifyObjectIsInternalConnection(internalConnection);
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserHelper.cs
index 9a736a4607..17f3a0244a 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserHelper.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserHelper.cs
@@ -18,7 +18,7 @@ private static void VerifyObjectIsTdsParser(object parser)
if (parser == null)
throw new ArgumentNullException("stateObject");
if (!s_tdsParser.IsInstanceOfType(parser))
- throw new ArgumentException("Object provided was not a DbConnectionInternal", "internalConnection");
+ throw new ArgumentException("Object provided was not a TdsParser", nameof(parser));
}
internal static object GetStateObject(object parser)
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs
index 32dda71943..1a8bb2fd4e 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs
@@ -56,7 +56,7 @@ private static void VerifyObjectIsTdsParserStateObject(object stateObject)
if (stateObject == null)
throw new ArgumentNullException(nameof(stateObject));
if (!s_tdsParserStateObjectManaged.IsInstanceOfType(stateObject))
- throw new ArgumentException("Object provided was not a DbConnectionInternal", "internalConnection");
+ throw new ArgumentException("Object provided was not a TdsParserStateObjectManaged", nameof(stateObject));
}
internal static object GetSessionHandle(object stateObject)
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.Debug.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.Debug.cs
new file mode 100644
index 0000000000..8f911fc2a8
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.Debug.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Runtime.ExceptionServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using static Microsoft.Data.SqlClient.ManualTesting.Tests.ConnectionPoolTest;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ public static class ConnectionPoolTestDebug
+ {
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsUsingManagedSNI))]
+ [ClassData(typeof(ConnectionPoolConnectionStringProvider))]
+ public static void ReplacementConnectionUsesSemaphoreTest(string connectionString)
+ {
+ string newConnectionString = (new SqlConnectionStringBuilder(connectionString) { MaxPoolSize = 2, ConnectTimeout = 5 }).ConnectionString;
+ SqlConnection.ClearAllPools();
+
+ using SqlConnection liveConnection = new(newConnectionString);
+ using SqlConnection deadConnection = new(newConnectionString);
+ liveConnection.Open();
+ deadConnection.Open();
+ InternalConnectionWrapper deadConnectionInternal = new(deadConnection);
+ InternalConnectionWrapper liveConnectionInternal = new(liveConnection);
+ deadConnectionInternal.KillConnection();
+ deadConnection.Close();
+ liveConnection.Close();
+
+ Task[] tasks = new Task[3];
+ Barrier syncBarrier = new(tasks.Length);
+ Func taskFunction = (() => ReplacementConnectionUsesSemaphoreTask(newConnectionString, syncBarrier));
+ for (int i = 0; i < tasks.Length; i++)
+ {
+ tasks[i] = Task.Factory.StartNew(taskFunction);
+ }
+
+ bool taskWithLiveConnection = false;
+ bool taskWithNewConnection = false;
+ bool taskWithCorrectException = false;
+
+ Task waitAllTask = Task.Factory.ContinueWhenAll(tasks, (completedTasks) =>
+ {
+ foreach (var item in completedTasks)
+ {
+ if (item.Status == TaskStatus.Faulted)
+ {
+ // One task should have a timeout exception
+ if ((!taskWithCorrectException) && (item.Exception.InnerException is InvalidOperationException) && (item.Exception.InnerException.Message.StartsWith(SystemDataResourceManager.Instance.ADP_PooledOpenTimeout)))
+ taskWithCorrectException = true;
+ else if (!taskWithCorrectException)
+ {
+ // Rethrow the unknown exception
+ ExceptionDispatchInfo exceptionInfo = ExceptionDispatchInfo.Capture(item.Exception);
+ exceptionInfo.Throw();
+ }
+ }
+ else if (item.Status == TaskStatus.RanToCompletion)
+ {
+ // One task should get the live connection
+ if (item.Result.Equals(liveConnectionInternal))
+ {
+ if (!taskWithLiveConnection)
+ taskWithLiveConnection = true;
+ }
+ else if (!item.Result.Equals(deadConnectionInternal) && !taskWithNewConnection)
+ taskWithNewConnection = true;
+ }
+ else
+ Console.WriteLine("ERROR: Task in unknown state: {0}", item.Status);
+ }
+ });
+
+ waitAllTask.Wait();
+ Assert.True(taskWithLiveConnection && taskWithNewConnection && taskWithCorrectException,
+ $"Tasks didn't finish as expected.\n" +
+ $"Task with live connection: {taskWithLiveConnection}\n" +
+ $"Task with new connection: {taskWithNewConnection}\n" +
+ $"Task with correct exception: {taskWithCorrectException}\n");
+ }
+
+ ///
+ /// Tests if killing the connection using the InternalConnectionWrapper is working
+ ///
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsUsingManagedSNI))]
+ [ClassData(typeof(ConnectionPoolConnectionStringProvider))]
+ public static void KillConnectionTest(string connectionString)
+ {
+ InternalConnectionWrapper wrapper = null;
+
+ using (SqlConnection connection = new(connectionString))
+ {
+ connection.Open();
+ wrapper = new InternalConnectionWrapper(connection);
+
+ using SqlCommand command = new("SELECT 5;", connection);
+
+ DataTestUtility.AssertEqualsWithDescription(5, command.ExecuteScalar(), "Incorrect scalar result.");
+
+ wrapper.KillConnection();
+ }
+
+ using (SqlConnection connection2 = new(connectionString))
+ {
+ connection2.Open();
+ Assert.False(wrapper.IsInternalConnectionOf(connection2), "New connection has internal connection that was just killed");
+ using SqlCommand command = new("SELECT 5;", connection2);
+
+ DataTestUtility.AssertEqualsWithDescription(5, command.ExecuteScalar(), "Incorrect scalar result.");
+ }
+ }
+
+ ///
+ /// Tests that cleanup removes connections that are unused for two cleanups
+ ///
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsUsingManagedSNI))]
+ [ClassData(typeof(ConnectionPoolConnectionStringProvider))]
+ public static void CleanupTest(string connectionString)
+ {
+ SqlConnection.ClearAllPools();
+
+ using SqlConnection conn1 = new(connectionString);
+ using SqlConnection conn2 = new(connectionString);
+ conn1.Open();
+ conn2.Open();
+ ConnectionPoolWrapper connectionPool = new(conn1);
+ Assert.Equal(2, connectionPool.ConnectionCount);
+
+ connectionPool.Cleanup();
+ Assert.Equal(2, connectionPool.ConnectionCount);
+
+ conn1.Close();
+ connectionPool.Cleanup();
+ Assert.Equal(2, connectionPool.ConnectionCount);
+
+ conn2.Close();
+ connectionPool.Cleanup();
+ Assert.Equal(1, connectionPool.ConnectionCount);
+
+ connectionPool.Cleanup();
+ Assert.Equal(0, connectionPool.ConnectionCount);
+
+ using SqlConnection conn3 = new(connectionString);
+ conn3.Open();
+ InternalConnectionWrapper internalConnection3 = new(conn3);
+
+ conn3.Close();
+ internalConnection3.KillConnection();
+ Assert.Equal(1, connectionPool.ConnectionCount);
+ Assert.False(internalConnection3.IsConnectionAlive(), "Connection should not be alive");
+
+ connectionPool.Cleanup();
+ Assert.Equal(1, connectionPool.ConnectionCount);
+ }
+
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsUsingManagedSNI))]
+ [ClassData(typeof(ConnectionPoolConnectionStringProvider))]
+ public static void ReplacementConnectionObeys0TimeoutTest(string connectionString)
+ {
+ string newConnectionString = (new SqlConnectionStringBuilder(connectionString) { ConnectTimeout = 0 }).ConnectionString;
+ SqlConnection.ClearAllPools();
+
+ // Kick off proxy
+ using (ProxyServer proxy = ProxyServer.CreateAndStartProxy(newConnectionString, out newConnectionString))
+ {
+ // Create one dead connection
+ using SqlConnection deadConnection = new(newConnectionString);
+ deadConnection.Open();
+ InternalConnectionWrapper deadConnectionInternal = new(deadConnection);
+ deadConnectionInternal.KillConnection();
+
+ // Block one live connection
+ proxy.PauseCopying();
+ Task blockedConnectionTask = Task.Run(() => ReplacementConnectionObeys0TimeoutTask(newConnectionString));
+ Thread.Sleep(100);
+ Assert.Equal(TaskStatus.Running, blockedConnectionTask.Status);
+
+ // Close and re-open the dead connection
+ deadConnection.Close();
+ Task newConnectionTask = Task.Run(() => ReplacementConnectionObeys0TimeoutTask(newConnectionString));
+ Thread.Sleep(100);
+ Assert.Equal(TaskStatus.Running, blockedConnectionTask.Status);
+ Assert.Equal(TaskStatus.Running, newConnectionTask.Status);
+
+ // restart the proxy
+ proxy.ResumeCopying();
+
+ Task.WaitAll(blockedConnectionTask, newConnectionTask);
+ blockedConnectionTask.Result.Close();
+ newConnectionTask.Result.Close();
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs
index f86f71468f..41a6404a20 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs
@@ -3,49 +3,43 @@
// See the LICENSE file in the project root for more information.
using System;
-using System.Runtime.ExceptionServices;
+using System.Collections;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
- public static class ConnectionPoolTest
+ public class ConnectionPoolConnectionStringProvider : IEnumerable