From 18a077837ee913715c4c3549e229c931c10668d1 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Fri, 3 Oct 2025 12:22:25 -0700 Subject: [PATCH 1/5] Fix connection pool concurrency issue (#3632) --- .../Data/ProviderBase/DbConnectionPool.cs | 82 ++-- ....Data.SqlClient.ManualTesting.Tests.csproj | 4 + .../ConnectionPoolStressTest.cs | 440 ++++++++++++++++++ 3 files changed, 490 insertions(+), 36 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs index 336f690776..73349b383f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs @@ -362,7 +362,42 @@ internal WaitHandle[] GetHandles(bool withCreate) } } - private const int MAX_Q_SIZE = (int)0x00100000; + /// + /// Helper class to obtain and release a semaphore. + /// + internal class SemaphoreHolder : IDisposable + { + private readonly Semaphore _semaphore; + + /// + /// Whether the semaphore was successfully obtained within the timeout. + /// + internal bool Obtained { get; private set; } + + /// + /// Obtains the semaphore, waiting up to the specified timeout. + /// + /// + /// + internal SemaphoreHolder(Semaphore semaphore, int timeout) + { + _semaphore = semaphore; + Obtained = _semaphore.WaitOne(timeout); + } + + /// + /// Releases the semaphore if it was successfully obtained. + /// + public void Dispose() + { + if (Obtained) + { + _semaphore.Release(1); + } + } + } + + private const int MAX_Q_SIZE = 0x00100000; // The order of these is important; we want the WaitAny call to be signaled // for a free object before a creation signal. Only the index first signaled @@ -1414,20 +1449,14 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj if (onlyOneCheckConnection) { - if (_waitHandles.CreationSemaphore.WaitOne(unchecked((int)waitForMultipleObjectsTimeout))) + using SemaphoreHolder semaphoreHolder = new(_waitHandles.CreationSemaphore, unchecked((int)waitForMultipleObjectsTimeout)); + if (semaphoreHolder.Obtained) { #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif - try - { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectID); - obj = UserCreateRequest(owningObject, userOptions); - } - finally - { - _waitHandles.CreationSemaphore.Release(1); - } + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectID); + obj = UserCreateRequest(owningObject, userOptions); } else { @@ -1657,7 +1686,6 @@ private void PoolCreateRequest(object state) { return; } - int waitResult = BOGUS_HANDLE; #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); @@ -1665,18 +1693,13 @@ private void PoolCreateRequest(object state) try { // Obtain creation mutex so we're the only one creating objects - // and we must have the wait result + using SemaphoreHolder semaphoreHolder = new(_waitHandles.CreationSemaphore, CreationTimeout); + + if (semaphoreHolder.Obtained) + { #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif - try - { } - finally - { - waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(withCreate: true), CreationTimeout); - } - if (CREATION_HANDLE == waitResult) - { DbConnectionInternal newObj; // Check ErrorOccurred again after obtaining mutex @@ -1710,17 +1733,12 @@ private void PoolCreateRequest(object state) } } } - else if (WaitHandle.WaitTimeout == waitResult) + else { // do not wait forever and potential block this worker thread // instead wait for a period of time and just requeue to try again QueuePoolCreateRequest(); } - else - { - // trace waitResult and ignore the failure - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called WaitForSingleObject failed {1}", ObjectID, waitResult); - } } catch (Exception e) { @@ -1732,15 +1750,7 @@ private void PoolCreateRequest(object state) // Now that CreateObject can throw, we need to catch the exception and discard it. // There is no further action we can take beyond tracing. The error will be // thrown to the user the next time they request a connection. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", ObjectID, e); - } - finally - { - if (CREATION_HANDLE == waitResult) - { - // reuse waitResult and ignore its value - _waitHandles.CreationSemaphore.Release(1); - } + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", Id, e); } } } 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 2c21c88d20..99078faec4 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 @@ -253,6 +253,10 @@ + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs new file mode 100644 index 0000000000..d3d41d4c9a --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs @@ -0,0 +1,440 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + /// + /// Connection pool stress test to validate pool behavior under various concurrent load scenarios. + /// + public class ConnectionPoolStressTest + { + #region Properties + + /// + /// Connection string + /// + internal string? ConnectionString { get; set; } + + /// + /// Maximum number of connections in the pool + /// + public int MaxPoolSize { get; set; } = 100; + + /// + /// SQL WAITFOR DELAY value for simulating slow queries + /// + public string WaitForDelay { get; set; } = "00:00:00.100"; + + /// + /// Number of concurrent connections to create + /// + public int ConcurrentConnections { get; set; } = 10; + + /// + /// Number of operations each thread should perform + /// + public int OperationsPerThread { get; set; } = 10; + + #endregion + + #region Connection Dooming + + // Reflection fields for accessing internal connection properties + private readonly FieldInfo? _internalConnectionField; + + public ConnectionPoolStressTest() + { + try + { + // Cache reflection info for Microsoft.Data.SqlClient + Type msDataConnectionType = typeof(SqlConnection); + _internalConnectionField = msDataConnectionType.GetField("_innerConnection", BindingFlags.NonPublic | BindingFlags.Instance); + } + catch (Exception ex) + { + Console.WriteLine($"Warning: Failed to initialize reflection for connection dooming: {ex.Message}"); + } + } + + /// + /// Dooms a Microsoft.Data.SqlClient connection by calling DoomThisConnection on its internal connection + /// + private bool DoomMicrosoftDataConnection(SqlConnection connection) + { + try + { + if(_internalConnectionField == null) + { + // Fail the test if reflection setup failed + return false; + } + + if (_internalConnectionField.GetValue(connection) is object internalConnection) + { + MethodInfo? doomMethod = internalConnection.GetType().GetMethod("DoomThisConnection", BindingFlags.NonPublic | BindingFlags.Instance); + if (doomMethod != null) + { + doomMethod.Invoke(internalConnection, null); + return true; + } + else + { + return false; + } + } + else + { + return false; + } + } + catch (Exception) + { + return false; + } + } + + #endregion + + #region Configuration + + /// + /// Sets up connection string + /// + /// Connection string to be set. + internal void SetConnectionString(string connectionString) + { + var connectionSB = new SqlConnectionStringBuilder(connectionString) + { + // Min size needs to be larger than the number of concurrent connections to trigger the pool exhaustion as it will make it more likely that PoolCreateRequest will run. + MinPoolSize = Math.Min(20, MaxPoolSize / 5), // Dynamic min pool size + MaxPoolSize = MaxPoolSize, + Pooling = true, // Explicitly enable pooling + TrustServerCertificate = true + }; + + ConnectionString = connectionSB.ConnectionString; + + // Ensure adequate thread pool capacity + ThreadPool.SetMaxThreads(Math.Max(ConcurrentConnections * 2, 100), 100); + } + + #endregion + + #region Stress Test Methods + + /// + /// Runs a synchronous stress test using Microsoft.Data.SqlClient with connection dooming + /// + internal void ConnectionPoolStress_MsData_Sync() + { + if (ConnectionString == null) + { + throw new InvalidOperationException("ConnectionString is not set. Call SetConnectionString() before running the test."); + } + + RunStressTest( + connectionString: ConnectionString, + doomAction: conn => DoomMicrosoftDataConnection((SqlConnection)conn), + async: false + ); + } + + /// + /// Runs asynchronous stress test using Microsoft.Data.SqlClient with connection dooming + /// + internal void ConnectionPoolStress_MsData_Async() + { + if (ConnectionString == null) + { + throw new InvalidOperationException("ConnectionString is not set. Call SetConnectionString() before running the test."); + } + + RunStressTest( + connectionString: ConnectionString, + doomAction: conn => DoomMicrosoftDataConnection((SqlConnection)conn), + async: true + ); + } + + /// + /// Generic stress test method that works with both SQL client libraries using DbConnection/DbCommand + /// + private void RunStressTest( + string connectionString, + Func doomAction, + bool async = false) + { + var threads = new Thread[ConcurrentConnections]; + using Barrier barrier = new(ConcurrentConnections); + using CountdownEvent countdown = new(ConcurrentConnections); + + var command = string.IsNullOrWhiteSpace(WaitForDelay) + ? "SELECT GETDATE()" + : $"WAITFOR DELAY '{WaitForDelay}'; SELECT GETDATE()"; + + // Create regular threads (don't doom connections) + for (int i = 0; i < ConcurrentConnections - 1; i++) + { + threads[i] = CreateWorkerThread( + connectionString, command, barrier, countdown, doomConnections: false, async); + } + + // Create special thread that dooms connections (if we have multiple threads) + if (ConcurrentConnections > 1) + { + threads[ConcurrentConnections - 1] = CreateWorkerThread( + connectionString, command, barrier, countdown, doomConnections: true, async, doomAction); + } + + // Start all threads + foreach (Thread thread in threads.Where(t => t != null)) + { + thread.Start(); + } + + // Wait for completion + countdown.Wait(); + } + + /// + /// Creates a worker thread that performs database operations using DbConnection/DbCommand + /// + private Thread CreateWorkerThread( + string connectionString, + string command, + Barrier barrier, + CountdownEvent countdown, + bool doomConnections, + bool async, + Func? doomAction = null) + { + return new Thread(async () => + { + try + { + barrier.SignalAndWait(); // Initial synchronization - all threads start together + + for (int j = 0; j < OperationsPerThread; j++) + { + if (doomConnections && doomAction != null) + { + // Dooming thread - barriers inside using block to doom before disposal + using var conn = new SqlConnection(connectionString); + if (async) + { + await conn.OpenAsync(); + } + else + { + conn.Open(); + } + + await ExecuteCommand(command, async, conn); + + // Synchronize after command execution, before dooming + barrier.SignalAndWait(); + + // Doom connection before it gets disposed/returned to pool + if (!doomAction(conn)) + { + throw new Exception("Unable to doom connection"); + } + + // Synchronize after dooming - ensures all threads see the effect + barrier.SignalAndWait(); + } + else + { + // Non-dooming threads - barriers after connection is closed + using (var conn = new SqlConnection(connectionString)) + { + if (async) + { + await conn.OpenAsync(); + } + else + { + conn.Open(); + } + + await ExecuteCommand(command, async, conn); + + } // Connection is closed/returned to pool here + + // Synchronize after connection is closed + barrier.SignalAndWait(); + + // Sync for coordination with dooming thread + barrier.SignalAndWait(); + } + } + } + finally + { + countdown.Signal(); + } + }) + { + IsBackground = true // Make threads background threads for cleaner shutdown + }; + } + + /// + /// Executes a database command with proper error handling + /// + private static async Task ExecuteCommand(string command, bool async, SqlConnection conn) + { + try + { + using var cmd = new SqlCommand(command, conn); + if (async) + { + await cmd.ExecuteScalarAsync(); + } + else + { + cmd.ExecuteScalar(); + } + } + catch (Exception ex) + { + Console.WriteLine($"Command execution failed: {ex.Message}"); + } + } + + #endregion + + #region Helpers + + private static bool RunSingleStressTest(Action testAction) + { + try + { + var stopwatch = Stopwatch.StartNew(); + testAction(); + stopwatch.Stop(); + } + catch (Exception ex) + { + if (ex.InnerException != null) + { + return false; + } + } + + return true; + } + + private static async Task TestConnectionPoolExhaustion(string connectionString, int maxPoolSize, bool async) + { + var connections = new List(); + + try + { + for (int i = 0; i < maxPoolSize; i++) + { + SqlConnection conn = new(connectionString); + if (async) + { + await conn.OpenAsync(); + } + else + { + conn.Open(); + } + connections.Add(conn); + } + Assert.Equal(maxPoolSize, connections.Count); + } + catch + { + return false; + } + finally + { + // Clean up all connections + foreach (SqlConnection conn in connections) + { + conn?.Dispose(); + } + } + + return true; + } + + #endregion + + #region Pool Exhaustion Tests + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [TestCategory("LongRunning")] // Takes around 13 seconds. + public async Task ConnectionPoolStress_Sync() + { + var test = new ConnectionPoolStressTest + { + MaxPoolSize = 100, + ConcurrentConnections = 10, + WaitForDelay = "00:00:00.100", + OperationsPerThread = 100, + }; + + test.SetConnectionString(DataTestUtility.TCPConnectionString); + + // Run the stress tests + if (!RunSingleStressTest(test.ConnectionPoolStress_MsData_Sync)) + { + // fail the test + Assert.Fail("ConnectionPoolStress_MsData_Sync failed"); + } + + if (!await TestConnectionPoolExhaustion(test.ConnectionString!, test.MaxPoolSize, false)) + { + // fail the test + Assert.Fail("ConnectionPoolStress_MsData_Sync failed"); + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [TestCategory("LongRunning")] // Takes around 11 seconds. + public async Task ConnectionPoolStress_Async() + { + var test = new ConnectionPoolStressTest + { + MaxPoolSize = 100, + ConcurrentConnections = 10, + WaitForDelay = "00:00:00.100", + OperationsPerThread = 100, + }; + + test.SetConnectionString(DataTestUtility.TCPConnectionString); + + // Test Microsoft.Data.SqlClient Async + if (!RunSingleStressTest(test.ConnectionPoolStress_MsData_Async)) + { + // fail the test + Assert.Fail("ConnectionPoolStress_MsData_Async failed"); + } + + // Test connection pool exhaustion (async) + if (!await TestConnectionPoolExhaustion(test.ConnectionString!, test.MaxPoolSize, true)) + { + // fail the test + Assert.Fail("ConnectionPoolStress_MsData_Async failed"); + } + } + + #endregion + } +} From b4a20b8a82a85ab1052122b7a46d9f7697de455f Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Fri, 3 Oct 2025 13:01:15 -0700 Subject: [PATCH 2/5] Fix object id --- .../src/Microsoft/Data/ProviderBase/DbConnectionPool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs index 73349b383f..21319441f8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs @@ -1750,7 +1750,7 @@ private void PoolCreateRequest(object state) // Now that CreateObject can throw, we need to catch the exception and discard it. // There is no further action we can take beyond tracing. The error will be // thrown to the user the next time they request a connection. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", Id, e); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", ObjectID, e); } } } From 74d924badacaf2787584403fd5a8e447795deb45 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Fri, 3 Oct 2025 13:36:03 -0700 Subject: [PATCH 3/5] Update src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj --- .../Microsoft.Data.SqlClient.ManualTesting.Tests.csproj | 1 - 1 file changed, 1 deletion(-) 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 99078faec4..e42387f253 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 @@ -255,7 +255,6 @@ - From 93d745585fc6802430c8e9fe89ee90c19552d8d7 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Mon, 6 Oct 2025 12:44:51 -0700 Subject: [PATCH 4/5] Revert semaphore holder --- .../Data/ProviderBase/DbConnectionPool.cs | 89 ++++++++----------- 1 file changed, 37 insertions(+), 52 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs index 21319441f8..732943464e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs @@ -362,41 +362,6 @@ internal WaitHandle[] GetHandles(bool withCreate) } } - /// - /// Helper class to obtain and release a semaphore. - /// - internal class SemaphoreHolder : IDisposable - { - private readonly Semaphore _semaphore; - - /// - /// Whether the semaphore was successfully obtained within the timeout. - /// - internal bool Obtained { get; private set; } - - /// - /// Obtains the semaphore, waiting up to the specified timeout. - /// - /// - /// - internal SemaphoreHolder(Semaphore semaphore, int timeout) - { - _semaphore = semaphore; - Obtained = _semaphore.WaitOne(timeout); - } - - /// - /// Releases the semaphore if it was successfully obtained. - /// - public void Dispose() - { - if (Obtained) - { - _semaphore.Release(1); - } - } - } - private const int MAX_Q_SIZE = 0x00100000; // The order of these is important; we want the WaitAny call to be signaled @@ -1447,23 +1412,36 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj DestroyObject(obj); obj = null; // Setting to null in case creating a new object fails - if (onlyOneCheckConnection) - { - using SemaphoreHolder semaphoreHolder = new(_waitHandles.CreationSemaphore, unchecked((int)waitForMultipleObjectsTimeout)); - if (semaphoreHolder.Obtained) - { + bool obtained = false; #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectID); - obj = UserCreateRequest(owningObject, userOptions); + if (onlyOneCheckConnection) + { + try + { + obtained = _waitHandles.CreationSemaphore.WaitOne((int)waitForMultipleObjectsTimeout); + if (obtained) + { + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectID); + obj = UserCreateRequest(owningObject, userOptions); + } + else + { + // Timeout waiting for creation semaphore - return null + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", ObjectID); + connection = null; + return false; + } } - else + finally { - // Timeout waiting for creation semaphore - return null - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", ObjectID); - connection = null; - return false; + if (obtained) + { + // Ensure that we release this waiter, regardless + // of any exceptions that may be thrown. + _waitHandles.CreationSemaphore.Release(1); + } } } } @@ -1687,19 +1665,17 @@ private void PoolCreateRequest(object state) return; } + bool obtained = false; #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif try { // Obtain creation mutex so we're the only one creating objects - using SemaphoreHolder semaphoreHolder = new(_waitHandles.CreationSemaphore, CreationTimeout); + obtained = _waitHandles.CreationSemaphore.WaitOne(CreationTimeout); - if (semaphoreHolder.Obtained) + if (obtained) { -#if NETFRAMEWORK - RuntimeHelpers.PrepareConstrainedRegions(); -#endif DbConnectionInternal newObj; // Check ErrorOccurred again after obtaining mutex @@ -1752,6 +1728,15 @@ private void PoolCreateRequest(object state) // thrown to the user the next time they request a connection. SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", ObjectID, e); } + finally + { + if (obtained) + { + // Ensure that we release this waiter, regardless + // of any exceptions that may be thrown. + _waitHandles.CreationSemaphore.Release(1); + } + } } } } From 34ebd97c1399f0ec02788f503ea2f7388b6c96f8 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Mon, 6 Oct 2025 16:29:27 -0700 Subject: [PATCH 5/5] Code improvements --- .../src/Microsoft/Data/ProviderBase/DbConnectionPool.cs | 6 +++--- .../SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs index 732943464e..0501704e12 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs @@ -1413,11 +1413,11 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj obj = null; // Setting to null in case creating a new object fails bool obtained = false; -#if NETFRAMEWORK - RuntimeHelpers.PrepareConstrainedRegions(); -#endif if (onlyOneCheckConnection) { +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif try { obtained = _waitHandles.CreationSemaphore.WaitOne((int)waitForMultipleObjectsTimeout); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs index d3d41d4c9a..2392cf12ba 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs @@ -18,6 +18,8 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { /// /// Connection pool stress test to validate pool behavior under various concurrent load scenarios. + /// TODO: Move this test to a more appropriate location once we have a better structure for connection pooling tests + /// that cover reliability and stress conditions designed specifically for connection pool. /// public class ConnectionPoolStressTest {