From df8dcb7f95799f45af7a6a814a38cd044782283b Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 15 May 2025 13:27:11 -0700 Subject: [PATCH 01/18] Add app context switch --- .../Data/SqlClient/LocalAppContextSwitches.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index 23eae838ae..2063bec90d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -22,6 +22,7 @@ private enum Tristate : byte internal const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; internal const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; internal const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; + internal const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; // this field is accessed through reflection in tests and should not be renamed or have the type changed without refactoring NullRow related tests private static Tristate s_legacyRowVersionNullBehavior; @@ -32,6 +33,7 @@ private enum Tristate : byte private static Tristate s_legacyVarTimeZeroScaleBehaviour; private static Tristate s_useCompatProcessSni; private static Tristate s_useCompatAsyncBehaviour; + private static Tristate s_useConnectionPoolV2; #if NET static LocalAppContextSwitches() @@ -270,5 +272,29 @@ public static bool LegacyVarTimeZeroScaleBehaviour return s_legacyVarTimeZeroScaleBehaviour == Tristate.True; } } + + /// + /// When set to true, the connection pool will use the new V2 connection pool implementation. + /// When set to false, the connection pool will use the legacy V1 implementation. + /// This app context switch defaults to 'false'. + /// + public static bool UseConnectionPoolV2 + { + get + { + if (s_useConnectionPoolV2 == Tristate.NotInitialized) + { + if (AppContext.TryGetSwitch(UseConnectionPoolV2String, out bool returnedValue) && returnedValue) + { + s_useConnectionPoolV2 = Tristate.True; + } + else + { + s_useConnectionPoolV2 = Tristate.False; + } + } + return s_useConnectionPoolV2 == Tristate.True; + } + } } } From 971e0c04775abd052b3e3057c998321678159e38 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 15 May 2025 13:34:37 -0700 Subject: [PATCH 02/18] Add ChannelDbConnectionPool shim and instantiate in pool group based on app context switch. --- .../src/Microsoft.Data.SqlClient.csproj | 9 +- .../netfx/src/Microsoft.Data.SqlClient.csproj | 9 +- .../src/Microsoft.Data.SqlClient.csproj | 5 +- .../ConnectionPool/ChannelDbConnectionPool.cs | 87 +++++++++++++++++++ .../ConnectionPool/DbConnectionPoolGroup.cs | 11 ++- 5 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 62d35ab21a..bd730e6ae2 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -95,6 +95,9 @@ Microsoft\Data\ProviderBase\DbConnectionFactory.cs + + Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPool.cs @@ -125,9 +128,6 @@ Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolState.cs - - Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs - Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolGroupProviderInfo.cs @@ -137,6 +137,9 @@ Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolProviderInfo.cs + + Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs + Microsoft\Data\ProviderBase\DbMetaDataFactory.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 52767110a0..ad6e833794 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -279,6 +279,9 @@ Microsoft\Data\ProviderBase\DbConnectionInternal.cs + + Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPool.cs @@ -312,9 +315,6 @@ Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolState.cs - - Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs - Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolGroupProviderInfo.cs @@ -324,6 +324,9 @@ Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolProviderInfo.cs + + Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs + Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 70f6b3ff2c..524ed5ccfc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -6,7 +6,7 @@ - + @@ -17,4 +17,7 @@ + + + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs new file mode 100644 index 0000000000..ac2fc54d2a --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Concurrent; +using System.Data.Common; +using System.Threading.Tasks; +using System.Transactions; +using Microsoft.Data.Common; +using Microsoft.Data.ProviderBase; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ConnectionPool +{ + internal sealed class ChannelDbConnectionPool : DbConnectionPool + { + internal override int Count => throw new NotImplementedException(); + + internal override DbConnectionFactory ConnectionFactory => throw new NotImplementedException(); + + internal override bool ErrorOccurred => throw new NotImplementedException(); + + internal override TimeSpan LoadBalanceTimeout => throw new NotImplementedException(); + + internal override DbConnectionPoolIdentity Identity => throw new NotImplementedException(); + + internal override bool IsRunning => throw new NotImplementedException(); + + internal override DbConnectionPoolGroup PoolGroup => throw new NotImplementedException(); + + internal override DbConnectionPoolGroupOptions PoolGroupOptions => throw new NotImplementedException(); + + internal override DbConnectionPoolProviderInfo ProviderInfo => throw new NotImplementedException(); + + internal override ConcurrentDictionary AuthenticationContexts => throw new NotImplementedException(); + + internal override bool UseLoadBalancing => throw new NotImplementedException(); + + internal override void Clear() + { + throw new NotImplementedException(); + } + + internal override void DestroyObject(DbConnectionInternal obj) + { + throw new NotImplementedException(); + } + + internal override void PutNewObject(DbConnectionInternal obj) + { + throw new NotImplementedException(); + } + + internal override void PutObject(DbConnectionInternal obj, object owningObject) + { + throw new NotImplementedException(); + } + + internal override void PutObjectFromTransactedPool(DbConnectionInternal obj) + { + throw new NotImplementedException(); + } + + internal override DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) + { + throw new NotImplementedException(); + } + + internal override void Shutdown() + { + throw new NotImplementedException(); + } + + internal override void Startup() + { + throw new NotImplementedException(); + } + + internal override void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) + { + throw new NotImplementedException(); + } + + internal override bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index af3b2d6bdf..411532b4bc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -185,7 +185,16 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor if (!_poolCollection.TryGetValue(currentIdentity, out pool)) { DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(ConnectionOptions); - DbConnectionPool newPool = new WaitHandleDbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); + + DbConnectionPool newPool; + if (LocalAppContextSwitches.UseConnectionPoolV2) + { + newPool = new WaitHandleDbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); + } + else + { + newPool = new ChannelDbConnectionPool(); + } if (MarkPoolGroupAsActive()) { From e11211077a080d5f1c26dd75a9c3a075b584b7d9 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 15 May 2025 13:44:24 -0700 Subject: [PATCH 03/18] Clean up connection pool interface. Rename TaskCompletionSource parameters for clarity. --- .../Data/ProviderBase/DbConnectionInternal.cs | 14 +++++++------- .../ConnectionPool/DbConnectionPool.cs | 6 +----- .../WaitHandleDbConnectionPool.cs | 18 +++++++++--------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs index b2cf6c25af..6ae9c72ab1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -464,10 +464,10 @@ internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFac // into the pool. if (connectionPool is not null) { - // PutObject calls Deactivate for us... - connectionPool.PutObject(this, owningObject); + // ReturnInternalConnection calls Deactivate for us... + connectionPool.ReturnInternalConnection(this, owningObject); - // NOTE: Before we leave the PutObject call, another thread may have + // NOTE: Before we leave the ReturnInternalConnection call, another thread may have // already popped the connection from the pool, so don't expect to be // able to verify it. } @@ -814,7 +814,7 @@ internal void SetInStasis() internal virtual bool TryOpenConnection( DbConnection outerConnection, DbConnectionFactory connectionFactory, - TaskCompletionSource retry, + TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions) { throw ADP.ConnectionAlreadyOpen(State); @@ -823,7 +823,7 @@ internal virtual bool TryOpenConnection( internal virtual bool TryReplaceConnection( DbConnection outerConnection, DbConnectionFactory connectionFactory, - TaskCompletionSource retry, + TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions) { throw ADP.MethodNotImplemented(); @@ -900,7 +900,7 @@ protected virtual void ReleaseAdditionalLocksForClose(bool lockToken) // No additional locks in default implementation } - protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) + protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions) { // ?->Connecting: prevent set_ConnectionString during Open if (connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this)) @@ -909,7 +909,7 @@ protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnect try { connectionFactory.PermissionDemand(outerConnection); - if (!connectionFactory.TryGetConnection(outerConnection, retry, userOptions, this, out openConnection)) + if (!connectionFactory.TryGetConnection(outerConnection, taskCompletionSource, userOptions, this, out openConnection)) { return false; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs index 0d00227469..bdf3db1e07 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs @@ -47,15 +47,11 @@ internal abstract class DbConnectionPool #region Abstract Methods internal abstract void Clear(); - internal abstract void DestroyObject(DbConnectionInternal obj); - internal abstract bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection); internal abstract DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection); - internal abstract void PutNewObject(DbConnectionInternal obj); - - internal abstract void PutObject(DbConnectionInternal obj, object owningObject); + internal abstract void ReturnInternalConnection(DbConnectionInternal obj, object owningObject); internal abstract void PutObjectFromTransactedPool(DbConnectionInternal obj); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index 9db1c7a5ec..bdb07041d5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -934,7 +934,7 @@ private void DeactivateObject(DbConnectionInternal obj) Debug.Assert(rootTxn == true || returnToGeneralPool == true || destroyObject == true); } - internal override void DestroyObject(DbConnectionInternal obj) + private void DestroyObject(DbConnectionInternal obj) { // A connection with a delegated transaction cannot be disposed of // until the delegated transaction has actually completed. Instead, @@ -1093,7 +1093,7 @@ private void WaitForPendingOpen() if (!next.Completion.TrySetResult(connection)) { // if the completion was cancelled, lets try and get this connection back for the next try - PutObject(connection, next.Owner); + ReturnInternalConnection(connection, next.Owner); } } } @@ -1108,12 +1108,12 @@ private void WaitForPendingOpen() } while (_pendingOpens.TryPeek(out next)); } - internal override bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection) + internal override bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection) { uint waitForMultipleObjectsTimeout = 0; bool allowCreate = false; - if (retry == null) + if (taskCompletionSource == null) { waitForMultipleObjectsTimeout = (uint)CreationTimeout; @@ -1136,7 +1136,7 @@ internal override bool TryGetConnection(DbConnection owningObject, TaskCompletio { return true; } - else if (retry == null) + else if (taskCompletionSource == null) { // timed out on a sync call return true; @@ -1146,7 +1146,7 @@ internal override bool TryGetConnection(DbConnection owningObject, TaskCompletio new PendingGetConnection( CreationTimeout == 0 ? Timeout.Infinite : ADP.TimerCurrent() + ADP.TimerFromSeconds(CreationTimeout / 1000), owningObject, - retry, + taskCompletionSource, userOptions); _pendingOpens.Enqueue(pendingGetConnection); @@ -1376,7 +1376,7 @@ private void PrepareConnection(DbConnection owningObject, DbConnectionInternal o { // if Activate throws an exception // put it back in the pool or have it properly disposed of - this.PutObject(obj, owningObject); + this.ReturnInternalConnection(obj, owningObject); throw; } } @@ -1613,7 +1613,7 @@ private void PoolCreateRequest(object state) } } - internal override void PutNewObject(DbConnectionInternal obj) + private void PutNewObject(DbConnectionInternal obj) { Debug.Assert(obj != null, "why are we adding a null object to the pool?"); @@ -1626,7 +1626,7 @@ internal override void PutNewObject(DbConnectionInternal obj) } - internal override void PutObject(DbConnectionInternal obj, object owningObject) + internal override void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) { Debug.Assert(obj != null, "null obj?"); From 60e94ecc3e7ada39870d5569b350101875657709 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 15 May 2025 14:50:00 -0700 Subject: [PATCH 04/18] Cleanup --- .../src/Microsoft.Data.SqlClient.csproj | 3 --- .../Microsoft/Data/ProviderBase/DbConnectionInternal.cs | 8 ++++---- .../SqlClient/ConnectionPool/DbConnectionPoolGroup.cs | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 524ed5ccfc..f42fe42b2c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -17,7 +17,4 @@ - - - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs index 6ae9c72ab1..73a75748dd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -814,7 +814,7 @@ internal void SetInStasis() internal virtual bool TryOpenConnection( DbConnection outerConnection, DbConnectionFactory connectionFactory, - TaskCompletionSource taskCompletionSource, + TaskCompletionSource retry, DbConnectionOptions userOptions) { throw ADP.ConnectionAlreadyOpen(State); @@ -823,7 +823,7 @@ internal virtual bool TryOpenConnection( internal virtual bool TryReplaceConnection( DbConnection outerConnection, DbConnectionFactory connectionFactory, - TaskCompletionSource taskCompletionSource, + TaskCompletionSource retry, DbConnectionOptions userOptions) { throw ADP.MethodNotImplemented(); @@ -900,7 +900,7 @@ protected virtual void ReleaseAdditionalLocksForClose(bool lockToken) // No additional locks in default implementation } - protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions) + protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) { // ?->Connecting: prevent set_ConnectionString during Open if (connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this)) @@ -909,7 +909,7 @@ protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnect try { connectionFactory.PermissionDemand(outerConnection); - if (!connectionFactory.TryGetConnection(outerConnection, taskCompletionSource, userOptions, this, out openConnection)) + if (!connectionFactory.TryGetConnection(outerConnection, retry, userOptions, this, out openConnection)) { return false; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index 411532b4bc..cc792d5e8f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -194,7 +194,7 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor else { newPool = new ChannelDbConnectionPool(); - } + } if (MarkPoolGroupAsActive()) { From f8aee85055bebfbdaa5e69c39c0f3729b8b5c165 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 15 May 2025 14:55:03 -0700 Subject: [PATCH 05/18] Fix app context switch evaluation. --- .../Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index cc792d5e8f..bb3516a144 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -189,11 +189,13 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor DbConnectionPool newPool; if (LocalAppContextSwitches.UseConnectionPoolV2) { - newPool = new WaitHandleDbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); + // ChannelDbConnectionPool is the new pool implementation + newPool = new ChannelDbConnectionPool(); } else { - newPool = new ChannelDbConnectionPool(); + // WaitHandleDbConnectionPool is the old pool implementation, and used by default if UseConnectionPoolV2 is off + newPool = new WaitHandleDbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); } if (MarkPoolGroupAsActive()) From b8fde3e12692c7d3e77d6623f1e1c868d1abe741 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 15 May 2025 14:58:40 -0700 Subject: [PATCH 06/18] Add license statement and class summary --- .../ConnectionPool/ChannelDbConnectionPool.cs | 25 ++++++++----------- .../ConnectionPool/DbConnectionPool.cs | 4 +++ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs index ac2fc54d2a..0815f6dd1f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -1,4 +1,7 @@ -using System; +// 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.Concurrent; using System.Data.Common; using System.Threading.Tasks; @@ -10,6 +13,10 @@ namespace Microsoft.Data.SqlClient.ConnectionPool { + /// + /// A connection pool implementation based on the channel data structure. + /// Provides methods to manage the pool of connections, including acquiring and releasing connections. + /// internal sealed class ChannelDbConnectionPool : DbConnectionPool { internal override int Count => throw new NotImplementedException(); @@ -39,27 +46,17 @@ internal override void Clear() throw new NotImplementedException(); } - internal override void DestroyObject(DbConnectionInternal obj) - { - throw new NotImplementedException(); - } - - internal override void PutNewObject(DbConnectionInternal obj) - { - throw new NotImplementedException(); - } - - internal override void PutObject(DbConnectionInternal obj, object owningObject) + internal override void PutObjectFromTransactedPool(DbConnectionInternal obj) { throw new NotImplementedException(); } - internal override void PutObjectFromTransactedPool(DbConnectionInternal obj) + internal override DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) { throw new NotImplementedException(); } - internal override DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) + internal override void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) { throw new NotImplementedException(); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs index bdf3db1e07..7dc97baed6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs @@ -12,6 +12,10 @@ namespace Microsoft.Data.SqlClient.ConnectionPool { + /// + /// A base class for implementing database connection pools. + /// Responsible for managing the lifecycle of connections and providing access to database connections. + /// internal abstract class DbConnectionPool { private static int _objectTypeCount; From 256f558b7b2058bdcd4e79e73361057e5d58cfc7 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 16 May 2025 09:52:21 -0700 Subject: [PATCH 07/18] Change DbConnection pool from abstract to interface. --- .../src/Microsoft.Data.SqlClient.csproj | 6 +- .../Data/SqlClient/SqlConnectionFactory.cs | 4 +- .../SqlClient/SqlInternalConnectionTds.cs | 4 +- .../netfx/src/Microsoft.Data.SqlClient.csproj | 6 +- .../Data/SqlClient/SqlConnectionFactory.cs | 4 +- .../SqlClient/SqlInternalConnectionTds.cs | 4 +- .../Data/ProviderBase/DbConnectionFactory.cs | 22 +++--- .../Data/ProviderBase/DbConnectionInternal.cs | 16 ++--- .../ConnectionPool/ChannelDbConnectionPool.cs | 44 ++++++------ .../ConnectionPool/DbConnectionPool.cs | 69 ------------------- .../ConnectionPool/DbConnectionPoolGroup.cs | 24 +++---- .../ConnectionPool/IDbConnectionPool.cs | 67 ++++++++++++++++++ .../WaitHandleDbConnectionPool.cs | 53 +++++++------- .../ConnectionPoolHelper.cs | 2 +- 14 files changed, 167 insertions(+), 158 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index bd730e6ae2..ae4b58a509 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -98,9 +98,6 @@ Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPool.cs - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContext.cs @@ -128,6 +125,9 @@ Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolState.cs + + Microsoft\Data\SqlClient\ConnectionPool\IDbConnectionPool.cs + Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolGroupProviderInfo.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index ca9bcc2cd9..3f1118789e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -32,12 +32,12 @@ override public DbProviderFactory ProviderFactory } } - override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection) + override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection) { return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection, userOptions: null); } - override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) + override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) { SqlConnectionString opt = (SqlConnectionString)options; SqlConnectionPoolKey key = (SqlConnectionPoolKey)poolKey; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index d128268185..f90d8fcc10 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -212,7 +212,7 @@ internal bool IsDNSCachingBeforeRedirectSupported internal byte _tceVersionSupported; // The pool that this connection is associated with, if at all it is. - private DbConnectionPool _dbConnectionPool; + private IDbConnectionPool _dbConnectionPool; // This is used to preserve the authentication context object if we decide to cache it for subsequent connections in the same pool. // This will finally end up in _dbConnectionPool.AuthenticationContexts, but only after 1 successful login to SQL Server using this context. @@ -453,7 +453,7 @@ internal SqlInternalConnectionTds( SessionData reconnectSessionData = null, bool applyTransientFaultHandling = false, string accessToken = null, - DbConnectionPool pool = null, + IDbConnectionPool pool = null, Func> accessTokenCallback = null) : base(connectionOptions) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index ad6e833794..4611decf98 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -282,9 +282,6 @@ Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPool.cs - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContext.cs @@ -315,6 +312,9 @@ Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolState.cs + + Microsoft\Data\SqlClient\ConnectionPool\IDbConnectionPool.cs + Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolGroupProviderInfo.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index d3402a48bd..3104c8aa65 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -33,12 +33,12 @@ override public DbProviderFactory ProviderFactory } } - override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection) + override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection) { return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection, userOptions: null); } - override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) + override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) { SqlConnectionString opt = (SqlConnectionString)options; SqlConnectionPoolKey key = (SqlConnectionPoolKey)poolKey; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 8079231f61..3991aa000f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -214,7 +214,7 @@ internal bool IsDNSCachingBeforeRedirectSupported internal byte _tceVersionSupported; // The pool that this connection is associated with, if at all it is. - private DbConnectionPool _dbConnectionPool; + private IDbConnectionPool _dbConnectionPool; // This is used to preserve the authentication context object if we decide to cache it for subsequent connections in the same pool. // This will finally end up in _dbConnectionPool.AuthenticationContexts, but only after 1 successful login to SQL Server using this context. @@ -426,7 +426,7 @@ internal SqlInternalConnectionTds( bool redirectedUserInstance, SqlConnectionString userConnectionOptions = null, // NOTE: userConnectionOptions may be different to connectionOptions if the connection string has been expanded (see SqlConnectionString.Expand) SessionData reconnectSessionData = null, - DbConnectionPool pool = null, + IDbConnectionPool pool = null, string accessToken = null, bool applyTransientFaultHandling = false, Func _connectionPoolGroups; - private readonly List _poolsToRelease; + private readonly List _poolsToRelease; private readonly List _poolGroupsToRelease; private readonly Timer _pruningTimer; @@ -37,7 +37,7 @@ internal abstract class DbConnectionFactory protected DbConnectionFactory() { _connectionPoolGroups = new Dictionary(); - _poolsToRelease = new List(); + _poolsToRelease = new List(); _poolGroupsToRelease = new List(); _pruningTimer = CreatePruningTimer(); } @@ -122,7 +122,7 @@ internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConne return newConnection; } - internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) + internal DbConnectionInternal CreatePooledConnection(IDbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) { Debug.Assert(pool != null, "null pool?"); DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool.PoolGroup.ProviderInfo; @@ -176,7 +176,7 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour Debug.Assert(owningConnection != null, "null owningConnection?"); DbConnectionPoolGroup poolGroup; - DbConnectionPool connectionPool; + IDbConnectionPool connectionPool; connection = null; // Work around race condition with clearing the pool between GetConnectionPool obtaining pool @@ -371,7 +371,7 @@ private void TryGetConnectionCompletedContinuation(Task ta } } - private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup) + private IDbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup) { // if poolgroup is disabled, it will be replaced with a new entry @@ -402,7 +402,7 @@ private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnecti Debug.Assert(connectionPoolGroup != null, "null connectionPoolGroup?"); SetConnectionPoolGroup(owningObject, connectionPoolGroup); } - DbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this); + IDbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this); return connectionPool; } @@ -530,8 +530,8 @@ private void PruneConnectionPoolGroups(object state) { if (0 != _poolsToRelease.Count) { - DbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray(); - foreach (DbConnectionPool pool in poolsToRelease) + IDbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray(); + foreach (IDbConnectionPool pool in poolsToRelease) { if (pool != null) { @@ -607,7 +607,7 @@ private void PruneConnectionPoolGroups(object state) } } - internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing) + internal void QueuePoolForRelease(IDbConnectionPool pool, bool clearing) { // Queue the pool up for release -- we'll clear it out and dispose // of it as the last part of the pruning timer callback so we don't @@ -645,12 +645,12 @@ internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) SqlClientEventSource.Metrics.ExitActiveConnectionPoolGroup(); } - virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) + virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) { return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection); } - abstract protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection); + abstract protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection); abstract protected DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs index 73a75748dd..53ff96963c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -108,7 +108,7 @@ internal bool IsEmancipated { // NOTE: There are race conditions between PrePush, PostPop and this // property getter -- only use this while this object is locked; - // (DbConnectionPool.Clear and ReclaimEmancipatedObjects + // (IDbConnectionPool.Clear and ReclaimEmancipatedObjects // do this for us) // The functionality is as follows: @@ -157,7 +157,7 @@ internal bool IsInPool /// /// The pooler that the connection came from (Pooled connections only) /// - internal DbConnectionPool Pool { get; private set; } + internal IDbConnectionPool Pool { get; private set; } public abstract string ServerVersion { get; } @@ -393,7 +393,7 @@ internal void CleanupConnectionOnTransactionCompletion(Transaction transaction) { DetachTransaction(transaction, false); - DbConnectionPool pool = Pool; + IDbConnectionPool pool = Pool; pool?.TransactionEnded(transaction, this); } @@ -454,7 +454,7 @@ internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFac { PrepareForCloseConnection(); - DbConnectionPool connectionPool = Pool; + IDbConnectionPool connectionPool = Pool; // Detach from enlisted transactions that are no longer active on close DetachCurrentTransactionIfEnded(); @@ -558,7 +558,7 @@ internal virtual void DelegatedTransactionEnded() Deactivate(); // call it one more time just in case - DbConnectionPool pool = Pool; + IDbConnectionPool pool = Pool; if (pool == null) { @@ -698,7 +698,7 @@ internal void MakeNonPooledObject(DbConnection owningObject) /// Used by DbConnectionFactory to indicate that this object IS part of a connection pool. /// /// - internal void MakePooledConnection(DbConnectionPool connectionPool) + internal void MakePooledConnection(IDbConnectionPool connectionPool) { _createTime = DateTime.UtcNow; Pool = connectionPool; @@ -717,7 +717,7 @@ internal virtual void OpenConnection(DbConnection outerConnection, DbConnectionF internal void PostPop(DbConnection newOwner) { - // Called by DbConnectionPool right after it pulls this from its pool, we take this + // Called by IDbConnectionPool right after it pulls this from its pool, we take this // opportunity to ensure ownership and pool counts are legit. Debug.Assert(!IsEmancipated, "pooled object not in pool"); @@ -757,7 +757,7 @@ internal virtual void PrepareForReplaceConnection() internal void PrePush(object expectedOwner) { - // Called by DbConnectionPool when we're about to be put into it's pool, we take this + // Called by IDbConnectionPool when we're about to be put into it's pool, we take this // opportunity to ensure ownership and pool counts are legit. // IMPORTANT NOTE: You must have taken a lock on the object before you call this method diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs index 0815f6dd1f..96be184b32 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -17,66 +17,70 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// A connection pool implementation based on the channel data structure. /// Provides methods to manage the pool of connections, including acquiring and releasing connections. /// - internal sealed class ChannelDbConnectionPool : DbConnectionPool + internal sealed class ChannelDbConnectionPool : IDbConnectionPool { - internal override int Count => throw new NotImplementedException(); + public int ObjectId => throw new NotImplementedException(); - internal override DbConnectionFactory ConnectionFactory => throw new NotImplementedException(); + public DbConnectionPoolState State { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - internal override bool ErrorOccurred => throw new NotImplementedException(); + public int Count => throw new NotImplementedException(); - internal override TimeSpan LoadBalanceTimeout => throw new NotImplementedException(); + public DbConnectionFactory ConnectionFactory => throw new NotImplementedException(); - internal override DbConnectionPoolIdentity Identity => throw new NotImplementedException(); + public bool ErrorOccurred => throw new NotImplementedException(); - internal override bool IsRunning => throw new NotImplementedException(); + public TimeSpan LoadBalanceTimeout => throw new NotImplementedException(); - internal override DbConnectionPoolGroup PoolGroup => throw new NotImplementedException(); + public DbConnectionPoolIdentity Identity => throw new NotImplementedException(); - internal override DbConnectionPoolGroupOptions PoolGroupOptions => throw new NotImplementedException(); + public bool IsRunning => throw new NotImplementedException(); - internal override DbConnectionPoolProviderInfo ProviderInfo => throw new NotImplementedException(); + public DbConnectionPoolGroup PoolGroup => throw new NotImplementedException(); - internal override ConcurrentDictionary AuthenticationContexts => throw new NotImplementedException(); + public DbConnectionPoolGroupOptions PoolGroupOptions => throw new NotImplementedException(); - internal override bool UseLoadBalancing => throw new NotImplementedException(); + public DbConnectionPoolProviderInfo ProviderInfo => throw new NotImplementedException(); - internal override void Clear() + public ConcurrentDictionary AuthenticationContexts => throw new NotImplementedException(); + + public bool UseLoadBalancing => throw new NotImplementedException(); + + public void Clear() { throw new NotImplementedException(); } - internal override void PutObjectFromTransactedPool(DbConnectionInternal obj) + public void PutObjectFromTransactedPool(DbConnectionInternal obj) { throw new NotImplementedException(); } - internal override DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) + public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) { throw new NotImplementedException(); } - internal override void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) + public void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) { throw new NotImplementedException(); } - internal override void Shutdown() + public void Shutdown() { throw new NotImplementedException(); } - internal override void Startup() + public void Startup() { throw new NotImplementedException(); } - internal override void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) + public void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) { throw new NotImplementedException(); } - internal override bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection) + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection) { throw new NotImplementedException(); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs deleted file mode 100644 index 7dc97baed6..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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.Concurrent; -using System.Data.Common; -using System.Threading.Tasks; -using System.Transactions; -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; - -namespace Microsoft.Data.SqlClient.ConnectionPool -{ - /// - /// A base class for implementing database connection pools. - /// Responsible for managing the lifecycle of connections and providing access to database connections. - /// - internal abstract class DbConnectionPool - { - private static int _objectTypeCount; - - internal int ObjectId { get; } = System.Threading.Interlocked.Increment(ref _objectTypeCount); - - internal DbConnectionPoolState State { get; set; } - - #region Abstract Properties - internal abstract int Count { get; } - - internal abstract DbConnectionFactory ConnectionFactory { get; } - - internal abstract bool ErrorOccurred { get; } - - internal abstract TimeSpan LoadBalanceTimeout { get; } - - internal abstract DbConnectionPoolIdentity Identity { get; } - - internal abstract bool IsRunning { get; } - - internal abstract DbConnectionPoolGroup PoolGroup { get; } - - internal abstract DbConnectionPoolGroupOptions PoolGroupOptions { get; } - - internal abstract DbConnectionPoolProviderInfo ProviderInfo { get; } - - internal abstract ConcurrentDictionary AuthenticationContexts { get; } - - internal abstract bool UseLoadBalancing { get; } - #endregion - - #region Abstract Methods - internal abstract void Clear(); - - internal abstract bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection); - - internal abstract DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection); - - internal abstract void ReturnInternalConnection(DbConnectionInternal obj, object owningObject); - - internal abstract void PutObjectFromTransactedPool(DbConnectionInternal obj); - - internal abstract void Startup(); - - internal abstract void Shutdown(); - - internal abstract void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject); - #endregion - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index bb3516a144..c36465e662 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -34,7 +34,7 @@ sealed internal class DbConnectionPoolGroup private readonly DbConnectionOptions _connectionOptions; private readonly DbConnectionPoolKey _poolKey; private readonly DbConnectionPoolGroupOptions _poolGroupOptions; - private ConcurrentDictionary _poolCollection; + private ConcurrentDictionary _poolCollection; private int _state; // see PoolGroupState* below @@ -64,7 +64,7 @@ internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, DbConnecti // HybridDictionary does not create any sub-objects until add // so it is safe to use for non-pooled connection as long as // we check _poolGroupOptions first - _poolCollection = new ConcurrentDictionary(); + _poolCollection = new ConcurrentDictionary(); _state = PoolGroupStateActive; } @@ -113,22 +113,22 @@ internal int Clear() // will return the number of connections in the group after clearing has finished // First, note the old collection and create a new collection to be used - ConcurrentDictionary oldPoolCollection = null; + ConcurrentDictionary oldPoolCollection = null; lock (this) { if (_poolCollection.Count > 0) { oldPoolCollection = _poolCollection; - _poolCollection = new ConcurrentDictionary(); + _poolCollection = new ConcurrentDictionary(); } } // Then, if a new collection was created, release the pools from the old collection if (oldPoolCollection != null) { - foreach (KeyValuePair entry in oldPoolCollection) + foreach (KeyValuePair entry in oldPoolCollection) { - DbConnectionPool pool = entry.Value; + IDbConnectionPool pool = entry.Value; if (pool != null) { DbConnectionFactory connectionFactory = pool.ConnectionFactory; @@ -142,7 +142,7 @@ internal int Clear() return _poolCollection.Count; } - internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory) + internal IDbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory) { // When this method returns null it indicates that the connection // factory should not use pooling. @@ -150,7 +150,7 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor // We don't support connection pooling on Win9x; // PoolGroupOptions will only be null when we're not supposed to pool // connections. - DbConnectionPool pool = null; + IDbConnectionPool pool = null; if (_poolGroupOptions != null) { #if NETFRAMEWORK @@ -186,7 +186,7 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor { DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(ConnectionOptions); - DbConnectionPool newPool; + IDbConnectionPool newPool; if (LocalAppContextSwitches.UseConnectionPoolV2) { // ChannelDbConnectionPool is the new pool implementation @@ -264,11 +264,11 @@ internal bool Prune() { if (_poolCollection.Count > 0) { - var newPoolCollection = new ConcurrentDictionary(); + var newPoolCollection = new ConcurrentDictionary(); - foreach (KeyValuePair entry in _poolCollection) + foreach (KeyValuePair entry in _poolCollection) { - DbConnectionPool pool = entry.Value; + IDbConnectionPool pool = entry.Value; if (pool != null) { // Actually prune the pool if there are no connections in the pool and no errors occurred. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs new file mode 100644 index 0000000000..e0a85657db --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -0,0 +1,67 @@ +// 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.Concurrent; +using System.Data.Common; +using System.Threading.Tasks; +using System.Transactions; +using Microsoft.Data.Common; +using Microsoft.Data.ProviderBase; + +namespace Microsoft.Data.SqlClient.ConnectionPool +{ + /// + /// A base class for implementing database connection pools. + /// Responsible for managing the lifecycle of connections and providing access to database connections. + /// + internal interface IDbConnectionPool + { + #region Properties + int ObjectId { get; } + + DbConnectionPoolState State { get; set; } + + int Count { get; } + + DbConnectionFactory ConnectionFactory { get; } + + bool ErrorOccurred { get; } + + TimeSpan LoadBalanceTimeout { get; } + + DbConnectionPoolIdentity Identity { get; } + + bool IsRunning { get; } + + DbConnectionPoolGroup PoolGroup { get; } + + DbConnectionPoolGroupOptions PoolGroupOptions { get; } + + DbConnectionPoolProviderInfo ProviderInfo { get; } + + ConcurrentDictionary AuthenticationContexts { get; } + + bool UseLoadBalancing { get; } + #endregion + + #region Methods + void Clear(); + + bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection); + + DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection); + + void ReturnInternalConnection(DbConnectionInternal obj, object owningObject); + + void PutObjectFromTransactedPool(DbConnectionInternal obj); + + void Startup(); + + void Shutdown(); + + void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject); + #endregion + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index bdb07041d5..e64e04f545 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -19,8 +19,15 @@ namespace Microsoft.Data.SqlClient.ConnectionPool { - internal sealed class WaitHandleDbConnectionPool : DbConnectionPool + internal sealed class WaitHandleDbConnectionPool : IDbConnectionPool { + + private static int _objectTypeCount; + + public int ObjectId => Interlocked.Increment(ref _objectTypeCount); + + public DbConnectionPoolState State { get; set; } + // This class is a way to stash our cloned Tx key for later disposal when it's no longer needed. // We can't get at the key in the dictionary without enumerating entries, so we stash an extra // copy as part of the value. @@ -60,12 +67,12 @@ private sealed class TransactedConnectionPool { Dictionary _transactedCxns; - DbConnectionPool _pool; + IDbConnectionPool _pool; private static int _objectTypeCount; // EventSource Counter internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); - internal TransactedConnectionPool(DbConnectionPool pool) + internal TransactedConnectionPool(IDbConnectionPool pool) { Debug.Assert(pool != null, "null pool?"); @@ -82,7 +89,7 @@ internal int ObjectID } } - internal DbConnectionPool Pool + internal IDbConnectionPool Pool { get { @@ -455,15 +462,15 @@ private int CreationTimeout get { return PoolGroupOptions.CreationTimeout; } } - internal override int Count => _totalObjects; + public int Count => _totalObjects; - internal override DbConnectionFactory ConnectionFactory => _connectionFactory; + public DbConnectionFactory ConnectionFactory => _connectionFactory; - internal override bool ErrorOccurred => _errorOccurred; + public bool ErrorOccurred => _errorOccurred; private bool HasTransactionAffinity => PoolGroupOptions.HasTransactionAffinity; - internal override TimeSpan LoadBalanceTimeout => PoolGroupOptions.LoadBalanceTimeout; + public TimeSpan LoadBalanceTimeout => PoolGroupOptions.LoadBalanceTimeout; private bool NeedToReplenish { @@ -488,9 +495,9 @@ private bool NeedToReplenish } } - internal override DbConnectionPoolIdentity Identity => _identity; + public DbConnectionPoolIdentity Identity => _identity; - internal override bool IsRunning + public bool IsRunning { get { return State is Running; } } @@ -499,18 +506,18 @@ internal override bool IsRunning private int MinPoolSize => PoolGroupOptions.MinPoolSize; - internal override DbConnectionPoolGroup PoolGroup => _connectionPoolGroup; + public DbConnectionPoolGroup PoolGroup => _connectionPoolGroup; - internal override DbConnectionPoolGroupOptions PoolGroupOptions => _connectionPoolGroupOptions; + public DbConnectionPoolGroupOptions PoolGroupOptions => _connectionPoolGroupOptions; - internal override DbConnectionPoolProviderInfo ProviderInfo => _connectionPoolProviderInfo; + public DbConnectionPoolProviderInfo ProviderInfo => _connectionPoolProviderInfo; /// /// Return the pooled authentication contexts. /// - internal override ConcurrentDictionary AuthenticationContexts => _pooledDbAuthenticationContexts; + public ConcurrentDictionary AuthenticationContexts => _pooledDbAuthenticationContexts; - internal override bool UseLoadBalancing => PoolGroupOptions.UseLoadBalancing; + public bool UseLoadBalancing => PoolGroupOptions.UseLoadBalancing; private bool UsingIntegrateSecurity => _identity != null && DbConnectionPoolIdentity.NoIdentity != _identity; @@ -622,7 +629,7 @@ private void CleanupCallback(object state) QueuePoolCreateRequest(); } - internal override void Clear() + public void Clear() { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Clearing.", ObjectId); DbConnectionInternal obj; @@ -1108,7 +1115,7 @@ private void WaitForPendingOpen() } while (_pendingOpens.TryPeek(out next)); } - internal override bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection) + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection) { uint waitForMultipleObjectsTimeout = 0; bool allowCreate = false; @@ -1388,7 +1395,7 @@ private void PrepareConnection(DbConnection owningObject, DbConnectionInternal o /// Options used to create the new connection /// Inner connection that will be replaced /// A new inner connection that is attached to the - internal override DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) + public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", ObjectId); DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection); @@ -1626,7 +1633,7 @@ private void PutNewObject(DbConnectionInternal obj) } - internal override void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) + public void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) { Debug.Assert(obj != null, "null obj?"); @@ -1654,7 +1661,7 @@ internal override void ReturnInternalConnection(DbConnectionInternal obj, object DeactivateObject(obj); } - internal override void PutObjectFromTransactedPool(DbConnectionInternal obj) + public void PutObjectFromTransactedPool(DbConnectionInternal obj) { Debug.Assert(obj != null, "null pooledObject?"); Debug.Assert(obj.EnlistedTransaction == null, "pooledObject is still enlisted?"); @@ -1758,7 +1765,7 @@ private bool ReclaimEmancipatedObjects() return emancipatedObjectFound; } - internal override void Startup() + public void Startup() { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, CleanupWait={1}", ObjectId, _cleanupWait); _cleanupTimer = CreateCleanupTimer(); @@ -1769,7 +1776,7 @@ internal override void Startup() } } - internal override void Shutdown() + public void Shutdown() { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectId); State = ShuttingDown; @@ -1787,7 +1794,7 @@ internal override void Shutdown() // that is implemented inside DbConnectionPool. This method's counterpart (PutTransactedObject) should // only be called from DbConnectionPool.DeactivateObject and thus the plumbing to provide access to // other objects is unnecessary (hence the asymmetry of Ended but no Begin) - internal override void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) + public void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) { Debug.Assert(transaction != null, "null transaction?"); Debug.Assert(transactedObject != null, "null transactedObject?"); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs index e930f437e9..0fd302ed2c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs @@ -14,7 +14,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SystemDataInternals internal static class ConnectionPoolHelper { private static Assembly s_MicrosoftDotData = Assembly.Load(new AssemblyName(typeof(SqlConnection).GetTypeInfo().Assembly.FullName)); - private static Type s_dbConnectionPool = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPool"); + private static Type s_dbConnectionPool = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.IDbConnectionPool"); private static Type s_waitHandleDbConnectionPool = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool"); private static Type s_dbConnectionPoolGroup = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolGroup"); private static Type s_dbConnectionPoolIdentity = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolIdentity"); From 393f175559c06380f84e7f01f0d2282daf7d9419 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 16 May 2025 11:16:07 -0700 Subject: [PATCH 08/18] Add doc comments. Refactor params for consistency. --- .../ConnectionPool/ChannelDbConnectionPool.cs | 2 +- .../ConnectionPool/DbConnectionPoolGroup.cs | 4 +- .../ConnectionPool/IDbConnectionPool.cs | 91 ++++++++++++++++++- 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs index 96be184b32..c3665ca374 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -80,7 +80,7 @@ public void TransactionEnded(Transaction transaction, DbConnectionInternal trans throw new NotImplementedException(); } - public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection) + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection) { throw new NotImplementedException(); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index c36465e662..d7a84fe732 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -189,12 +189,12 @@ internal IDbConnectionPool GetConnectionPool(DbConnectionFactory connectionFacto IDbConnectionPool newPool; if (LocalAppContextSwitches.UseConnectionPoolV2) { - // ChannelDbConnectionPool is the new pool implementation + // ChannelDbConnectionPool is the v2 pool implementation newPool = new ChannelDbConnectionPool(); } else { - // WaitHandleDbConnectionPool is the old pool implementation, and used by default if UseConnectionPoolV2 is off + // WaitHandleDbConnectionPool is the v1 pool implementation, and used by default if UseConnectionPoolV2 is off newPool = new WaitHandleDbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs index e0a85657db..a8ead94fc7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -13,54 +13,137 @@ namespace Microsoft.Data.SqlClient.ConnectionPool { /// - /// A base class for implementing database connection pools. - /// Responsible for managing the lifecycle of connections and providing access to database connections. + /// A base interface for implementing database connection pools. + /// Derived classes are responsible for managing the lifecycle + /// of connections and providing access to database connections. /// internal interface IDbConnectionPool { #region Properties + /// + /// An id that uniqely identifies this connection pool. + /// int ObjectId { get; } + /// + /// The current state of the connection pool. + /// DbConnectionPoolState State { get; set; } + /// + /// The number of connections currently managed by the pool. + /// May be larger than the number of connections currently sitting idle in the pool. + /// int Count { get; } + /// + /// Gets the factory used to create database connections. + /// DbConnectionFactory ConnectionFactory { get; } + /// + /// Indicates whether an error has occurred in the pool. + /// Primarily used to support the pool blocking period feature. + /// bool ErrorOccurred { get; } + /// + /// Gets the duration of time to wait before reassigning a connection to a different server in a load-balanced + /// environment. + /// TimeSpan LoadBalanceTimeout { get; } + /// + /// Gets the identity used by the connection pool when establishing connections. + /// DbConnectionPoolIdentity Identity { get; } + /// + /// Indicates whether the connection pool is currently running. + /// bool IsRunning { get; } + /// + /// Gets a reference to the connection pool group that this pool belongs to. + /// DbConnectionPoolGroup PoolGroup { get; } + /// + /// Gets the options for the connection pool group. + /// DbConnectionPoolGroupOptions PoolGroupOptions { get; } + /// + /// Gets the provider information for the connection pool. + /// DbConnectionPoolProviderInfo ProviderInfo { get; } + /// + /// Gets the authentication contexts cached by the pool. + /// ConcurrentDictionary AuthenticationContexts { get; } + /// + /// Indicates whether the connection pool is using load balancing. + /// bool UseLoadBalancing { get; } #endregion #region Methods + /// + /// Clears the connection pool, releasing all connections and resetting the state. + /// void Clear(); - bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection); - + /// + /// Attempts to get a connection from the pool. + /// + /// The SqlConnection that will own this internal connection. + /// Used when calling this method in an async context. + /// The internal connection will be set on completion source rather than passed out via the out parameter. + /// The user options to use if a new connection must be opened. + /// The retrieved connection will be passed out via this parameter. + /// Returns true if a connection was set in the out parameter, otherwise returns false. + bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection); + + /// + /// Replaces the internal connection currently associated with owningObject with a new internal connection from the pool. + /// + /// The connection whos internal connection should be replaced. + /// The user options to use if a new connection must be opened. + /// The internal connection currently associated with the owning object. + /// DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection); + /// + /// Returns an internal connection to the pool. + /// + /// The internal connection to return to the pool. + /// The connection that currently owns this internal connection. Used to verify ownership. void ReturnInternalConnection(DbConnectionInternal obj, object owningObject); + /// + /// Puts an internal connection from a transacted pool back into the general pool. + /// + /// The internal connection to return to the pool. void PutObjectFromTransactedPool(DbConnectionInternal obj); + /// + /// Initializes and starts the connection pool. Should be called once when the pool is created. + /// void Startup(); + /// + /// Shuts down the connection pool releasing any resources. Should be called once when the pool is no longer needed. + /// void Shutdown(); + /// + /// Informs the pool that a transaction has ended. The pool will commit and reset any internal + /// the transacted object associated with this transaction. + /// + /// The transaction that has ended. + /// The internal connection that should be committed and reset. void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject); #endregion } From 540e93f14a350b41cafa052fe6f563dbde6f1e6b Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 16 May 2025 12:18:21 -0700 Subject: [PATCH 09/18] Add unit test for context switch default value. --- .../tests/FunctionalTests/LocalAppContextSwitchesTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs index 295a354349..99f68c8073 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs @@ -2,6 +2,7 @@ // 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.Reflection; using Xunit; @@ -15,6 +16,8 @@ public class LocalAppContextSwitchesTests [InlineData("MakeReadAsyncBlocking", false)] [InlineData("UseMinimumLoginTimeout", true)] [InlineData("UseCompatibilityProcessSni", false)] + [InlineData("UseCompatibilityAsyncBehaviour", false)] + [InlineData("UseConnectionPoolV2", false)] public void DefaultSwitchValue(string property, bool expectedDefaultValue) { var switchesType = typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); From a4240371a5995b8745c8fdea0f74d59fb3dd8540 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 16 May 2025 12:26:45 -0700 Subject: [PATCH 10/18] Add class summary for WaitHandleDbConnectionPool --- .../WaitHandleDbConnectionPool.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index e64e04f545..f15601e52f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -19,6 +19,33 @@ namespace Microsoft.Data.SqlClient.ConnectionPool { + /// + /// A concrete implementation of used by Microsoft.Data.SqlClient + /// to efficiently manage a pool of reusable objects backing ADO.NET SqlConnection instances. + /// + /// Primary Responsibilities: + /// + /// Connection Reuse and Pooling: Uses two stacks (_stackNew and _stackOld) to manage idle connections. Ensures efficient reuse and limits new connection creation. + /// Transaction-Aware Pooling: Tracks connections enlisted in using TransactedConnectionPool and TransactedConnectionList, ensuring proper context reuse. + /// Concurrency and Synchronization: Uses wait handles and semaphores via PoolWaitHandles to coordinate safe multi-threaded access. + /// Connection Lifecycle Management: Manages creation (CreateObject), deactivation (DeactivateObject), destruction (DestroyObject), and reclamation (ReclaimEmancipatedObjects) of internal connections. + /// Error Handling and Resilience: Implements retry and exponential backoff in TryGetConnection and handles transient errors using _errorWait. + /// Minimum Pool Size Enforcement: Maintains the MinPoolSize by spawning background tasks to create new connections when needed. + /// Load Balancing Support: Honors LoadBalanceTimeout to clean up idle connections and distribute load evenly. + /// Telemetry and Tracing: Uses SqlClientEventSource for extensive diagnostic tracing of connection lifecycle events. + /// Pending Request Queue: Queues unresolved connection requests in _pendingOpens and processes them using background threads. + /// Identity and Authentication Context: Manages identity-based reuse via a dictionary of DbConnectionPoolAuthenticationContext keyed by user identity. + /// + /// + /// Key Concepts in Design: + /// + /// Stacks and queues for free and pending connections + /// Synchronization via WaitHandle, Semaphore, and ManualResetEvent + /// Support for transaction enlistment and affinity + /// Timer-based cleanup to prune idle or expired connections + /// Background thread spawning for servicing deferred requests and replenishing the pool + /// + /// internal sealed class WaitHandleDbConnectionPool : IDbConnectionPool { From d0ca337820e64f70f8ca12a20657cb467a433b61 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 16 May 2025 13:02:30 -0700 Subject: [PATCH 11/18] Rename ObjectId property to Id. --- .../Data/ProviderBase/DbConnectionFactory.cs | 2 +- .../ConnectionPool/ChannelDbConnectionPool.cs | 2 +- .../ConnectionPool/IDbConnectionPool.cs | 4 +- .../WaitHandleDbConnectionPool.cs | 82 +++++++++---------- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs index f5d2e09eb2..6f12c748f2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs @@ -540,7 +540,7 @@ private void PruneConnectionPoolGroups(object state) if (0 == pool.Count) { _poolsToRelease.Remove(pool); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePool={1}", ObjectID, pool.ObjectId); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePool={1}", ObjectID, pool.Id); SqlClientEventSource.Metrics.ExitInactiveConnectionPool(); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs index c3665ca374..b2feff743d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -19,7 +19,7 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// internal sealed class ChannelDbConnectionPool : IDbConnectionPool { - public int ObjectId => throw new NotImplementedException(); + public int Id => throw new NotImplementedException(); public DbConnectionPoolState State { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs index a8ead94fc7..78c9de923f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -14,7 +14,7 @@ namespace Microsoft.Data.SqlClient.ConnectionPool { /// /// A base interface for implementing database connection pools. - /// Derived classes are responsible for managing the lifecycle + /// Implementations are responsible for managing the lifecycle /// of connections and providing access to database connections. /// internal interface IDbConnectionPool @@ -23,7 +23,7 @@ internal interface IDbConnectionPool /// /// An id that uniqely identifies this connection pool. /// - int ObjectId { get; } + int Id { get; } /// /// The current state of the connection pool. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index f15601e52f..162b533ecc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -51,7 +51,7 @@ internal sealed class WaitHandleDbConnectionPool : IDbConnectionPool private static int _objectTypeCount; - public int ObjectId => Interlocked.Increment(ref _objectTypeCount); + public int Id => Interlocked.Increment(ref _objectTypeCount); public DbConnectionPoolState State { get; set; } @@ -105,7 +105,7 @@ internal TransactedConnectionPool(IDbConnectionPool pool) _pool = pool; _transactedCxns = new Dictionary(); - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed for connection pool {1}", ObjectID, _pool.ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed for connection pool {1}", ObjectID, _pool.Id); } internal int ObjectID @@ -478,7 +478,7 @@ internal WaitHandleDbConnectionPool( _poolCreateRequest = new WaitCallback(PoolCreateRequest); // used by CleanupCallback State = Running; - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed.", Id); //_cleanupTimer & QueuePoolCreateRequest is delayed until DbConnectionPoolGroup calls // StartBackgroundCallbacks after pool is actually in the collection @@ -568,7 +568,7 @@ private void CleanupCallback(object state) // // With this logic, objects are pruned from the pool if unused for // at least one period but not more than two periods. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", Id); // Destroy free objects that put us above MinPoolSize from old stack. while (Count > MinPoolSize) @@ -643,7 +643,7 @@ private void CleanupCallback(object state) break; Debug.Assert(obj != null, "null connection is not expected"); - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, ChangeStacks={1}", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, ChangeStacks={1}", Id, obj.ObjectID); Debug.Assert(!obj.IsEmancipated, "pooled object not in pool"); Debug.Assert(obj.CanBePooled, "pooled object is not poolable"); @@ -658,7 +658,7 @@ private void CleanupCallback(object state) public void Clear() { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Clearing.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Clearing.", Id); DbConnectionInternal obj; // First, quickly doom everything. @@ -696,7 +696,7 @@ public void Clear() // Finally, reclaim everything that's emancipated (which, because // it's been doomed, will cause it to be disposed of as well) ReclaimEmancipatedObjects(); - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Cleared.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Cleared.", Id); } private Timer CreateCleanupTimer() => @@ -768,7 +768,7 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio SqlClientEventSource.Metrics.EnterPooledConnection(); } - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Added to pool.", ObjectId, newObj?.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Added to pool.", Id, newObj?.ObjectID); // Reset the error wait: _errorWait = ERROR_WAIT_DEFAULT; @@ -839,7 +839,7 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio private void DeactivateObject(DbConnectionInternal obj) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Deactivating.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Deactivating.", Id, obj.ObjectID); obj.DeactivateConnection(); bool returnToGeneralPool = false; @@ -977,11 +977,11 @@ private void DestroyObject(DbConnectionInternal obj) // again. if (obj.IsTxRootWaitingForTxEnd) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Has Delegated Transaction, waiting to Dispose.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Has Delegated Transaction, waiting to Dispose.", Id, obj.ObjectID); } else { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removing from pool.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removing from pool.", Id, obj.ObjectID); bool removed = false; lock (_objectList) { @@ -992,12 +992,12 @@ private void DestroyObject(DbConnectionInternal obj) if (removed) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removed from pool.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removed from pool.", Id, obj.ObjectID); SqlClientEventSource.Metrics.ExitPooledConnection(); } obj.Dispose(); - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Disposed.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Disposed.", Id, obj.ObjectID); SqlClientEventSource.Metrics.HardDisconnectRequest(); } @@ -1005,7 +1005,7 @@ private void DestroyObject(DbConnectionInternal obj) private void ErrorCallback(object state) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Resetting Error handling.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Resetting Error handling.", Id); _errorOccurred = false; _waitHandles.ErrorEvent.Reset(); @@ -1160,7 +1160,7 @@ public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource {0}, DbConnectionInternal State != Running.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, DbConnectionInternal State != Running.", Id); connection = null; return true; } @@ -1206,7 +1206,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj DbConnectionInternal obj = null; Transaction transaction = null; - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Getting connection.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Getting connection.", Id); // If automatic transaction enlistment is required, then we try to // get the connection from the transacted connection pool first. @@ -1249,19 +1249,19 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj switch (waitResult) { case WaitHandle.WaitTimeout: - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", Id); Interlocked.Decrement(ref _waitCount); connection = null; return false; case ERROR_HANDLE: // Throw the error that PoolCreateRequest stashed. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Errors are set.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Errors are set.", Id); Interlocked.Decrement(ref _waitCount); throw TryCloneCachedException(); case CREATION_HANDLE: - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", Id); try { obj = UserCreateRequest(owningObject, userOptions); @@ -1314,7 +1314,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj if ((obj != null) && (!obj.IsConnectionAlive())) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", Id, obj.ObjectID); DestroyObject(obj); obj = null; // Setting to null in case creating a new object fails @@ -1327,7 +1327,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj #endif try { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", Id); obj = UserCreateRequest(owningObject, userOptions); } finally @@ -1338,7 +1338,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj else { // Timeout waiting for creation semaphore - return null - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", Id); connection = null; return false; } @@ -1347,22 +1347,22 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj break; case WAIT_ABANDONED + SEMAPHORE_HANDLE: - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Semaphore handle abandonded.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Semaphore handle abandonded.", Id); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(SEMAPHORE_HANDLE, _waitHandles.PoolSemaphore); case WAIT_ABANDONED + ERROR_HANDLE: - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Error handle abandonded.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Error handle abandonded.", Id); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(ERROR_HANDLE, _waitHandles.ErrorEvent); case WAIT_ABANDONED + CREATION_HANDLE: - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creation handle abandoned.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creation handle abandoned.", Id); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(CREATION_HANDLE, _waitHandles.CreationSemaphore); default: - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, WaitForMultipleObjects={1}", ObjectId, waitResult); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, WaitForMultipleObjects={1}", Id, waitResult); Interlocked.Decrement(ref _waitCount); throw ADP.InternalError(ADP.InternalErrorCode.UnexpectedWaitAnyResult); } @@ -1424,7 +1424,7 @@ private void PrepareConnection(DbConnection owningObject, DbConnectionInternal o /// A new inner connection that is attached to the public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", Id); DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection); if (newConnection != null) @@ -1466,7 +1466,7 @@ private DbConnectionInternal GetFromGeneralPool() if (obj != null) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from general pool.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from general pool.", Id, obj.ObjectID); SqlClientEventSource.Metrics.ExitFreeConnection(); } @@ -1484,7 +1484,7 @@ private DbConnectionInternal GetFromTransactedPool(out Transaction transaction) if (obj != null) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from transacted pool.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from transacted pool.", Id, obj.ObjectID); SqlClientEventSource.Metrics.ExitFreeConnection(); @@ -1496,14 +1496,14 @@ private DbConnectionInternal GetFromTransactedPool(out Transaction transaction) } catch { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", Id, obj.ObjectID); DestroyObject(obj); throw; } } else if (!obj.IsConnectionAlive()) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", Id, obj.ObjectID); DestroyObject(obj); obj = null; } @@ -1520,7 +1520,7 @@ private void PoolCreateRequest(object state) { // called by pooler to ensure pool requests are currently being satisfied - // creation mutex has not been obtained - long scopeID = SqlClientEventSource.Log.TryPoolerScopeEnterEvent(" {0}", ObjectId); + long scopeID = SqlClientEventSource.Log.TryPoolerScopeEnterEvent(" {0}", Id); try { if (State is Running) @@ -1614,7 +1614,7 @@ private void PoolCreateRequest(object state) else { // trace waitResult and ignore the failure - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called WaitForSingleObject failed {1}", ObjectId, waitResult); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called WaitForSingleObject failed {1}", Id, waitResult); } } catch (Exception e) @@ -1627,7 +1627,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); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", Id, e); } finally { @@ -1651,7 +1651,7 @@ private void PutNewObject(DbConnectionInternal obj) { Debug.Assert(obj != null, "why are we adding a null object to the pool?"); - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Pushing to general pool.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Pushing to general pool.", Id, obj.ObjectID); _stackNew.Push(obj); _waitHandles.PoolSemaphore.Release(1); @@ -1703,7 +1703,7 @@ public void PutObjectFromTransactedPool(DbConnectionInternal obj) // method, we can safely presume that the caller is the only person // that is using the connection, and that all pre-push logic has been // done and all transactions are ended. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Transaction has ended.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Transaction has ended.", Id, obj.ObjectID); if (State is Running && obj.CanBePooled) { @@ -1728,7 +1728,7 @@ private void QueuePoolCreateRequest() private bool ReclaimEmancipatedObjects() { bool emancipatedObjectFound = false; - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", Id); List reclaimedObjects = new List(); int count; @@ -1780,7 +1780,7 @@ private bool ReclaimEmancipatedObjects() for (int i = 0; i < count; ++i) { DbConnectionInternal obj = reclaimedObjects[i]; - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Reclaiming.", ObjectId, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Reclaiming.", Id, obj.ObjectID); SqlClientEventSource.Metrics.ReclaimedConnectionRequest(); @@ -1794,7 +1794,7 @@ private bool ReclaimEmancipatedObjects() public void Startup() { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, CleanupWait={1}", ObjectId, _cleanupWait); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, CleanupWait={1}", Id, _cleanupWait); _cleanupTimer = CreateCleanupTimer(); if (NeedToReplenish) @@ -1805,7 +1805,7 @@ public void Startup() public void Shutdown() { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", Id); State = ShuttingDown; // deactivate timer callbacks @@ -1827,7 +1827,7 @@ public void TransactionEnded(Transaction transaction, DbConnectionInternal trans Debug.Assert(transactedObject != null, "null transactedObject?"); // Note: connection may still be associated with transaction due to Explicit Unbinding requirement. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Transaction Completed", ObjectId, transaction.GetHashCode(), transactedObject.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Transaction Completed", Id, transaction.GetHashCode(), transactedObject.ObjectID); // called by the internal connection when it get's told that the // transaction is completed. We tell the transacted pool to remove From 6067bd1d381088b174688e2dc4cd329abc4ddce2 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Mon, 19 May 2025 08:33:24 -0700 Subject: [PATCH 12/18] Fix reflection of pool count. --- .../SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs index 0fd302ed2c..9202338545 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs @@ -23,7 +23,7 @@ internal static class ConnectionPoolHelper private static Type s_dbConnectionPoolKey = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolKey"); private static Type s_dictStringPoolGroup = typeof(Dictionary<,>).MakeGenericType(s_dbConnectionPoolKey, s_dbConnectionPoolGroup); private static Type s_dictPoolIdentityPool = typeof(ConcurrentDictionary<,>).MakeGenericType(s_dbConnectionPoolIdentity, s_dbConnectionPool); - private static PropertyInfo s_dbConnectionPoolCount = s_waitHandleDbConnectionPool.GetProperty("Count", BindingFlags.Instance | BindingFlags.NonPublic); + private static PropertyInfo s_dbConnectionPoolCount = s_waitHandleDbConnectionPool.GetProperty("Count", BindingFlags.Instance); private static PropertyInfo s_dictStringPoolGroupGetKeys = s_dictStringPoolGroup.GetProperty("Keys"); private static PropertyInfo s_dictPoolIdentityPoolValues = s_dictPoolIdentityPool.GetProperty("Values"); private static FieldInfo s_dbConnectionFactoryPoolGroupList = s_dbConnectionFactory.GetField("_connectionPoolGroups", BindingFlags.Instance | BindingFlags.NonPublic); From aac03adc95213c957bacefecaae68a2fae6065f1 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Mon, 19 May 2025 09:31:54 -0700 Subject: [PATCH 13/18] Fix binding flags. --- .../SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs index 9202338545..6c828b188b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs @@ -23,7 +23,7 @@ internal static class ConnectionPoolHelper private static Type s_dbConnectionPoolKey = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolKey"); private static Type s_dictStringPoolGroup = typeof(Dictionary<,>).MakeGenericType(s_dbConnectionPoolKey, s_dbConnectionPoolGroup); private static Type s_dictPoolIdentityPool = typeof(ConcurrentDictionary<,>).MakeGenericType(s_dbConnectionPoolIdentity, s_dbConnectionPool); - private static PropertyInfo s_dbConnectionPoolCount = s_waitHandleDbConnectionPool.GetProperty("Count", BindingFlags.Instance); + private static PropertyInfo s_dbConnectionPoolCount = s_waitHandleDbConnectionPool.GetProperty("Count", BindingFlags.Instance | BindingFlags.Public); private static PropertyInfo s_dictStringPoolGroupGetKeys = s_dictStringPoolGroup.GetProperty("Keys"); private static PropertyInfo s_dictPoolIdentityPoolValues = s_dictPoolIdentityPool.GetProperty("Values"); private static FieldInfo s_dbConnectionFactoryPoolGroupList = s_dbConnectionFactory.GetField("_connectionPoolGroups", BindingFlags.Instance | BindingFlags.NonPublic); From b799f106c8b9aca5c0a07d8684a25d6296002ba8 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Mon, 19 May 2025 13:08:14 -0700 Subject: [PATCH 14/18] Fix property ordering. Inherit doc comments. --- .../ConnectionPool/ChannelDbConnectionPool.cs | 42 ++++++++++++++++--- .../ConnectionPool/IDbConnectionPool.cs | 32 +++++++------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs index b2feff743d..5a1a901443 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -19,70 +19,100 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// internal sealed class ChannelDbConnectionPool : IDbConnectionPool { - public int Id => throw new NotImplementedException(); + #region Properties + /// + public ConcurrentDictionary AuthenticationContexts => throw new NotImplementedException(); - public DbConnectionPoolState State { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + /// + public DbConnectionFactory ConnectionFactory => throw new NotImplementedException(); + /// public int Count => throw new NotImplementedException(); - public DbConnectionFactory ConnectionFactory => throw new NotImplementedException(); - + /// public bool ErrorOccurred => throw new NotImplementedException(); - public TimeSpan LoadBalanceTimeout => throw new NotImplementedException(); + /// + public int Id => throw new NotImplementedException(); + /// public DbConnectionPoolIdentity Identity => throw new NotImplementedException(); + /// public bool IsRunning => throw new NotImplementedException(); + /// + public TimeSpan LoadBalanceTimeout => throw new NotImplementedException(); + + /// public DbConnectionPoolGroup PoolGroup => throw new NotImplementedException(); + /// public DbConnectionPoolGroupOptions PoolGroupOptions => throw new NotImplementedException(); + /// public DbConnectionPoolProviderInfo ProviderInfo => throw new NotImplementedException(); - public ConcurrentDictionary AuthenticationContexts => throw new NotImplementedException(); + /// + public DbConnectionPoolState State { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + /// public bool UseLoadBalancing => throw new NotImplementedException(); + #endregion + + + #region Methods + /// public void Clear() { throw new NotImplementedException(); } + /// public void PutObjectFromTransactedPool(DbConnectionInternal obj) { throw new NotImplementedException(); } + /// public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) { throw new NotImplementedException(); } + /// public void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) { throw new NotImplementedException(); } + /// public void Shutdown() { throw new NotImplementedException(); } + /// public void Startup() { throw new NotImplementedException(); } + /// public void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) { throw new NotImplementedException(); } + /// public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection) { throw new NotImplementedException(); } + #endregion } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs index 78c9de923f..e0bbcb8f24 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -21,14 +21,14 @@ internal interface IDbConnectionPool { #region Properties /// - /// An id that uniqely identifies this connection pool. + /// Gets the authentication contexts cached by the pool. /// - int Id { get; } + ConcurrentDictionary AuthenticationContexts { get; } /// - /// The current state of the connection pool. + /// Gets the factory used to create database connections. /// - DbConnectionPoolState State { get; set; } + DbConnectionFactory ConnectionFactory { get; } /// /// The number of connections currently managed by the pool. @@ -36,11 +36,6 @@ internal interface IDbConnectionPool /// int Count { get; } - /// - /// Gets the factory used to create database connections. - /// - DbConnectionFactory ConnectionFactory { get; } - /// /// Indicates whether an error has occurred in the pool. /// Primarily used to support the pool blocking period feature. @@ -48,10 +43,9 @@ internal interface IDbConnectionPool bool ErrorOccurred { get; } /// - /// Gets the duration of time to wait before reassigning a connection to a different server in a load-balanced - /// environment. + /// An id that uniqely identifies this connection pool. /// - TimeSpan LoadBalanceTimeout { get; } + int Id { get; } /// /// Gets the identity used by the connection pool when establishing connections. @@ -63,6 +57,12 @@ internal interface IDbConnectionPool /// bool IsRunning { get; } + /// + /// Gets the duration of time to wait before reassigning a connection to a different server in a load-balanced + /// environment. + /// + TimeSpan LoadBalanceTimeout { get; } + /// /// Gets a reference to the connection pool group that this pool belongs to. /// @@ -79,9 +79,9 @@ internal interface IDbConnectionPool DbConnectionPoolProviderInfo ProviderInfo { get; } /// - /// Gets the authentication contexts cached by the pool. + /// The current state of the connection pool. /// - ConcurrentDictionary AuthenticationContexts { get; } + DbConnectionPoolState State { get; set; } /// /// Indicates whether the connection pool is using load balancing. @@ -103,7 +103,7 @@ internal interface IDbConnectionPool /// The internal connection will be set on completion source rather than passed out via the out parameter. /// The user options to use if a new connection must be opened. /// The retrieved connection will be passed out via this parameter. - /// Returns true if a connection was set in the out parameter, otherwise returns false. + /// True if a connection was set in the out parameter, otherwise returns false. bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection); /// @@ -112,7 +112,7 @@ internal interface IDbConnectionPool /// The connection whos internal connection should be replaced. /// The user options to use if a new connection must be opened. /// The internal connection currently associated with the owning object. - /// + /// A reference to the new DbConnectionInternal. DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection); /// From 6ea14918f60716a120b754e36b4490f433143e48 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Mon, 19 May 2025 15:24:05 -0700 Subject: [PATCH 15/18] Defer new pool class to next PR. Add test to boost code coverage. --- .../src/Microsoft.Data.SqlClient.csproj | 3 - .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 - .../ConnectionPool/ChannelDbConnectionPool.cs | 118 ------------------ .../ConnectionPool/DbConnectionPoolGroup.cs | 4 +- .../ConnectionPoolTest/ConnectionPoolTest.cs | 27 ++++ 5 files changed, 29 insertions(+), 126 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 5eea491a04..50bbd9ba3c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -95,9 +95,6 @@ Microsoft\Data\ProviderBase\DbConnectionFactory.cs - - Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContext.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index b9bb2a4011..893b28951a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -279,9 +279,6 @@ Microsoft\Data\ProviderBase\DbConnectionInternal.cs - - Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContext.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs deleted file mode 100644 index 5a1a901443..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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.Concurrent; -using System.Data.Common; -using System.Threading.Tasks; -using System.Transactions; -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; - -#nullable enable - -namespace Microsoft.Data.SqlClient.ConnectionPool -{ - /// - /// A connection pool implementation based on the channel data structure. - /// Provides methods to manage the pool of connections, including acquiring and releasing connections. - /// - internal sealed class ChannelDbConnectionPool : IDbConnectionPool - { - #region Properties - /// - public ConcurrentDictionary AuthenticationContexts => throw new NotImplementedException(); - - /// - public DbConnectionFactory ConnectionFactory => throw new NotImplementedException(); - - /// - public int Count => throw new NotImplementedException(); - - /// - public bool ErrorOccurred => throw new NotImplementedException(); - - /// - public int Id => throw new NotImplementedException(); - - /// - public DbConnectionPoolIdentity Identity => throw new NotImplementedException(); - - /// - public bool IsRunning => throw new NotImplementedException(); - - /// - public TimeSpan LoadBalanceTimeout => throw new NotImplementedException(); - - /// - public DbConnectionPoolGroup PoolGroup => throw new NotImplementedException(); - - /// - public DbConnectionPoolGroupOptions PoolGroupOptions => throw new NotImplementedException(); - - /// - public DbConnectionPoolProviderInfo ProviderInfo => throw new NotImplementedException(); - - /// - public DbConnectionPoolState State { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - /// - public bool UseLoadBalancing => throw new NotImplementedException(); - #endregion - - - - #region Methods - /// - public void Clear() - { - throw new NotImplementedException(); - } - - /// - public void PutObjectFromTransactedPool(DbConnectionInternal obj) - { - throw new NotImplementedException(); - } - - /// - public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) - { - throw new NotImplementedException(); - } - - /// - public void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) - { - throw new NotImplementedException(); - } - - /// - public void Shutdown() - { - throw new NotImplementedException(); - } - - /// - public void Startup() - { - throw new NotImplementedException(); - } - - /// - public void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) - { - throw new NotImplementedException(); - } - - /// - public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection) - { - throw new NotImplementedException(); - } - #endregion - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index d7a84fe732..110f578a85 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -5,6 +5,7 @@ using Microsoft.Data.Common; using Microsoft.Data.ProviderBase; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -189,8 +190,7 @@ internal IDbConnectionPool GetConnectionPool(DbConnectionFactory connectionFacto IDbConnectionPool newPool; if (LocalAppContextSwitches.UseConnectionPoolV2) { - // ChannelDbConnectionPool is the v2 pool implementation - newPool = new ChannelDbConnectionPool(); + throw new NotImplementedException(); } else { 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 478f78a5b9..e6f597aa5b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs @@ -73,6 +73,31 @@ public static void BasicConnectionPoolingTest(string connectionString) } } + /// + /// Tests that when UseConnectionPoolV2 context switch is enabled, opening a connection throws NotImplementedException + /// + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ClassData(typeof(ConnectionPoolConnectionStringProvider))] + public static void UseConnectionPoolV2ThrowsNotImplemented(string connectionString) + { + try + { + // Enable the UseConnectionPoolV2 context switch + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2", true); + + using (SqlConnection connection = new SqlConnection(connectionString)) + { + // This should throw NotImplementedException + Assert.Throws(() => connection.Open()); + } + } + finally + { + // Reset the context switch + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2", false); + } + } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAADPasswordConnStrSetup), nameof(DataTestUtility.IsAADAuthorityURLSetup))] public static void AccessTokenConnectionPoolingTest() { @@ -199,6 +224,8 @@ public static void MaxPoolWaitForConnectionTest(string connectionString) Assert.Equal(TaskStatus.RanToCompletion, waitTask.Status); } + + internal static InternalConnectionWrapper ReplacementConnectionUsesSemaphoreTask(string connectionString, Barrier syncBarrier) { InternalConnectionWrapper internalConnection = null; From 32b31e5fda9af0163e75ea69fad3b360287fed39 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Tue, 20 May 2025 09:14:24 -0700 Subject: [PATCH 16/18] Try to fix app context switch setting. --- .../ConnectionPoolTest/ConnectionPoolTest.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) 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 e6f597aa5b..4d07b45a1a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -73,6 +74,13 @@ public static void BasicConnectionPoolingTest(string connectionString) } } + private enum Tristate : byte + { + NotInitialized = 0, + False = 1, + True = 2 + } + /// /// Tests that when UseConnectionPoolV2 context switch is enabled, opening a connection throws NotImplementedException /// @@ -82,8 +90,9 @@ public static void UseConnectionPoolV2ThrowsNotImplemented(string connectionStri { try { - // Enable the UseConnectionPoolV2 context switch - AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2", true); + Type switchesType = typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); + FieldInfo switchField = switchesType.GetField("s_useConnectionPoolV2", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + switchField.SetValue(null, Tristate.True); using (SqlConnection connection = new SqlConnection(connectionString)) { @@ -94,7 +103,9 @@ public static void UseConnectionPoolV2ThrowsNotImplemented(string connectionStri finally { // Reset the context switch - AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2", false); + Type switchesType = typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); + FieldInfo switchField = switchesType.GetField("s_useConnectionPoolV2", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + switchField.SetValue(null, Tristate.False); } } From d6b8eb5d3054183bf441f85cbd5762d2bda67f6d Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 21 May 2025 11:06:11 -0700 Subject: [PATCH 17/18] Remove flaky test. Planned to replace this in the near future anyways. --- .../ConnectionPoolTest/ConnectionPoolTest.cs | 35 ------------------- 1 file changed, 35 deletions(-) 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 4d07b45a1a..8342a9a63a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs @@ -74,41 +74,6 @@ public static void BasicConnectionPoolingTest(string connectionString) } } - private enum Tristate : byte - { - NotInitialized = 0, - False = 1, - True = 2 - } - - /// - /// Tests that when UseConnectionPoolV2 context switch is enabled, opening a connection throws NotImplementedException - /// - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [ClassData(typeof(ConnectionPoolConnectionStringProvider))] - public static void UseConnectionPoolV2ThrowsNotImplemented(string connectionString) - { - try - { - Type switchesType = typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); - FieldInfo switchField = switchesType.GetField("s_useConnectionPoolV2", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - switchField.SetValue(null, Tristate.True); - - using (SqlConnection connection = new SqlConnection(connectionString)) - { - // This should throw NotImplementedException - Assert.Throws(() => connection.Open()); - } - } - finally - { - // Reset the context switch - Type switchesType = typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); - FieldInfo switchField = switchesType.GetField("s_useConnectionPoolV2", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - switchField.SetValue(null, Tristate.False); - } - } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAADPasswordConnStrSetup), nameof(DataTestUtility.IsAADAuthorityURLSetup))] public static void AccessTokenConnectionPoolingTest() { From 831dc5bc7aaec64622c69a5bb2136dfd8cf88bcc Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 21 May 2025 11:17:56 -0700 Subject: [PATCH 18/18] Clean up test file. --- .../ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs | 3 --- 1 file changed, 3 deletions(-) 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 8342a9a63a..478f78a5b9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -200,8 +199,6 @@ public static void MaxPoolWaitForConnectionTest(string connectionString) Assert.Equal(TaskStatus.RanToCompletion, waitTask.Status); } - - internal static InternalConnectionWrapper ReplacementConnectionUsesSemaphoreTask(string connectionString, Barrier syncBarrier) { InternalConnectionWrapper internalConnection = null;