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);