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 eb556a79ec..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\DbConnectionPool.cs - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContext.cs @@ -125,8 +122,8 @@ Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolState.cs - - Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs + + Microsoft\Data\SqlClient\ConnectionPool\IDbConnectionPool.cs Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolGroupProviderInfo.cs @@ -137,6 +134,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/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index bfda03ae42..3a6ef00793 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 7d6d2b51f6..6d5753baad 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 @@ -211,7 +211,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 e1c3277fb7..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\DbConnectionPool.cs - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContext.cs @@ -312,8 +309,8 @@ Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolState.cs - - Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs + + Microsoft\Data\SqlClient\ConnectionPool\IDbConnectionPool.cs Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolGroupProviderInfo.cs @@ -324,6 +321,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/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 12a6378ddc..d42a0170aa 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 5986736403..721f13195f 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 @@ -211,7 +211,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. @@ -464,7 +464,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/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 70f6b3ff2c..f42fe42b2c 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 @@ - + 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 61c4e18579..6f12c748f2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs @@ -18,7 +18,7 @@ namespace Microsoft.Data.ProviderBase internal abstract class DbConnectionFactory { private Dictionary _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) { @@ -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(); } @@ -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 b2cf6c25af..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(); @@ -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. } @@ -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/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs deleted file mode 100644 index 0d00227469..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 -{ - 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 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 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 af3b2d6bdf..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; @@ -34,7 +35,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 +65,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 +114,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 +143,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 +151,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 @@ -185,7 +186,17 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor if (!_poolCollection.TryGetValue(currentIdentity, out pool)) { DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(ConnectionOptions); - DbConnectionPool newPool = new WaitHandleDbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); + + IDbConnectionPool newPool; + if (LocalAppContextSwitches.UseConnectionPoolV2) + { + throw new NotImplementedException(); + } + else + { + // WaitHandleDbConnectionPool is the v1 pool implementation, and used by default if UseConnectionPoolV2 is off + newPool = new WaitHandleDbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); + } if (MarkPoolGroupAsActive()) { @@ -253,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..e0bbcb8f24 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -0,0 +1,150 @@ +// 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 interface for implementing database connection pools. + /// Implementations are responsible for managing the lifecycle + /// of connections and providing access to database connections. + /// + internal interface IDbConnectionPool + { + #region Properties + /// + /// Gets the authentication contexts cached by the pool. + /// + ConcurrentDictionary AuthenticationContexts { get; } + + /// + /// Gets the factory used to create database connections. + /// + DbConnectionFactory ConnectionFactory { get; } + + /// + /// 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; } + + /// + /// Indicates whether an error has occurred in the pool. + /// Primarily used to support the pool blocking period feature. + /// + bool ErrorOccurred { get; } + + /// + /// An id that uniqely identifies this connection pool. + /// + int Id { 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 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. + /// + 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; } + + /// + /// The current state of the connection pool. + /// + DbConnectionPoolState State { get; set; } + + /// + /// 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(); + + /// + /// 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. + /// 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. + /// A reference to the new DbConnectionInternal. + 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 + } +} 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..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 @@ -19,8 +19,42 @@ namespace Microsoft.Data.SqlClient.ConnectionPool { - internal sealed class WaitHandleDbConnectionPool : DbConnectionPool + /// + /// 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 { + + private static int _objectTypeCount; + + public int Id => 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,18 +94,18 @@ 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?"); _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 @@ -82,7 +116,7 @@ internal int ObjectID } } - internal DbConnectionPool Pool + internal IDbConnectionPool Pool { get { @@ -444,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 @@ -455,15 +489,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 +522,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 +533,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; @@ -534,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) @@ -609,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"); @@ -622,9 +656,9 @@ private void CleanupCallback(object state) QueuePoolCreateRequest(); } - internal override void Clear() + public void Clear() { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Clearing.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Clearing.", Id); DbConnectionInternal obj; // First, quickly doom everything. @@ -662,7 +696,7 @@ internal override 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() => @@ -734,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; @@ -805,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; @@ -934,7 +968,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, @@ -943,11 +977,11 @@ internal override 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) { @@ -958,12 +992,12 @@ internal override 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(); } @@ -971,7 +1005,7 @@ internal override 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(); @@ -1093,7 +1127,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 +1142,12 @@ private void WaitForPendingOpen() } while (_pendingOpens.TryPeek(out next)); } - internal override bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection) + public 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; @@ -1126,7 +1160,7 @@ internal override bool TryGetConnection(DbConnection owningObject, TaskCompletio if (State is not Running) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, DbConnectionInternal State != Running.", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, DbConnectionInternal State != Running.", Id); connection = null; return true; } @@ -1136,7 +1170,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 +1180,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); @@ -1172,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. @@ -1215,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); @@ -1280,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 @@ -1293,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 @@ -1304,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; } @@ -1313,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); } @@ -1376,7 +1410,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; } } @@ -1388,9 +1422,9 @@ 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); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", Id); DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection); if (newConnection != null) @@ -1432,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(); } @@ -1450,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(); @@ -1462,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; } @@ -1486,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) @@ -1580,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) @@ -1593,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 { @@ -1613,11 +1647,11 @@ 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?"); - 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); @@ -1626,7 +1660,7 @@ internal override void PutNewObject(DbConnectionInternal obj) } - internal override void PutObject(DbConnectionInternal obj, object owningObject) + public void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) { Debug.Assert(obj != null, "null obj?"); @@ -1654,7 +1688,7 @@ internal override void PutObject(DbConnectionInternal obj, object owningObject) 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?"); @@ -1669,7 +1703,7 @@ internal override 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) { @@ -1694,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; @@ -1746,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(); @@ -1758,9 +1792,9 @@ private bool ReclaimEmancipatedObjects() return emancipatedObjectFound; } - internal override void Startup() + public void Startup() { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, CleanupWait={1}", ObjectId, _cleanupWait); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, CleanupWait={1}", Id, _cleanupWait); _cleanupTimer = CreateCleanupTimer(); if (NeedToReplenish) @@ -1769,9 +1803,9 @@ internal override void Startup() } } - internal override void Shutdown() + public void Shutdown() { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectId); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", Id); State = ShuttingDown; // deactivate timer callbacks @@ -1787,13 +1821,13 @@ 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?"); // 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 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; + } + } } } 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"); 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..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 @@ -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"); @@ -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 | 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);