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 89fbdaa759..64bea7d5fb 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -99,9 +99,6 @@
Microsoft\Data\ProviderBase\DbConnectionClosed.cs
-
- Microsoft\Data\ProviderBase\DbConnectionFactory.cs
-
Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
index 45df50badb..dae580e0bb 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -1272,7 +1272,7 @@ public override void ChangeDatabase(string database)
///
public static void ClearAllPools()
{
- SqlConnectionFactory.SingletonInstance.ClearAllPools();
+ SqlConnectionFactory.Instance.ClearAllPools();
}
///
@@ -1283,7 +1283,7 @@ public static void ClearPool(SqlConnection connection)
DbConnectionOptions connectionOptions = connection.UserConnectionOptions;
if (connectionOptions != null)
{
- SqlConnectionFactory.SingletonInstance.ClearPool(connection);
+ SqlConnectionFactory.Instance.ClearPool(connection);
}
}
@@ -2265,7 +2265,7 @@ public static void ChangePassword(string connectionString, string newPassword)
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null, accessTokenCallback: null);
- SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key);
+ SqlConnectionString connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key);
if (connectionOptions.IntegratedSecurity)
{
throw SQL.ChangePasswordConflictsWithSSPI();
@@ -2314,7 +2314,7 @@ public static void ChangePassword(string connectionString, SqlCredential credent
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null);
- SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key);
+ SqlConnectionString connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key);
// Check for connection string values incompatible with SqlCredential
if (!string.IsNullOrEmpty(connectionOptions.UserID) || !string.IsNullOrEmpty(connectionOptions.Password))
@@ -2352,7 +2352,7 @@ private static void ChangePassword(string connectionString, SqlConnectionString
}
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null);
- SqlConnectionFactory.SingletonInstance.ClearPool(key);
+ SqlConnectionFactory.Instance.ClearPool(key);
}
internal Task RegisterForConnectionCloseNotification(Task outerTask, object value, int tag)
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs
index 133854c1f0..9095c9b454 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs
@@ -17,7 +17,7 @@ namespace Microsoft.Data.SqlClient
{
public sealed partial class SqlConnection : DbConnection
{
- private static readonly DbConnectionFactory s_connectionFactory = SqlConnectionFactory.SingletonInstance;
+ private static readonly SqlConnectionFactory s_connectionFactory = SqlConnectionFactory.Instance;
private DbConnectionOptions _userConnectionOptions;
private DbConnectionPoolGroup _poolGroup;
@@ -42,7 +42,7 @@ internal int CloseCount
}
}
- internal DbConnectionFactory ConnectionFactory
+ internal SqlConnectionFactory ConnectionFactory
{
get
{
@@ -156,9 +156,7 @@ override protected DbCommand CreateDbCommand()
{
using (TryEventScope.Create(" {0}", ObjectID))
{
- DbCommand command = null;
- DbProviderFactory providerFactory = ConnectionFactory.ProviderFactory;
- command = providerFactory.CreateCommand();
+ DbCommand command = SqlClientFactory.Instance.CreateCommand();
command.Connection = this;
return command;
}
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 0d1da114cc..8e3cb1bef3 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
@@ -3049,9 +3049,13 @@ internal bool ThreadHasParserLockForClose
}
}
- internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions)
+ internal override bool TryReplaceConnection(
+ DbConnection outerConnection,
+ SqlConnectionFactory connectionFactory,
+ TaskCompletionSource retry,
+ DbConnectionOptions userOptions)
{
- return base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
+ return TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
}
}
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 78466acbc2..3904bfa16c 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -285,9 +285,6 @@
Microsoft\Data\ProviderBase\DbConnectionClosed.cs
-
- Microsoft\Data\ProviderBase\DbConnectionFactory.cs
-
Microsoft\Data\ProviderBase\DbConnectionInternal.cs
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
index 9681a6a3ae..ff5efa0ef9 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -1266,7 +1266,7 @@ public override void ChangeDatabase(string database)
public static void ClearAllPools()
{
(new SqlClientPermission(PermissionState.Unrestricted)).Demand();
- SqlConnectionFactory.SingletonInstance.ClearAllPools();
+ SqlConnectionFactory.Instance.ClearAllPools();
}
///
@@ -1278,7 +1278,7 @@ public static void ClearPool(SqlConnection connection)
if (connectionOptions != null)
{
connectionOptions.DemandPermission();
- SqlConnectionFactory.SingletonInstance.ClearPool(connection);
+ SqlConnectionFactory.Instance.ClearPool(connection);
}
}
@@ -2197,7 +2197,7 @@ public static void ChangePassword(string connectionString, string newPassword)
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null, accessTokenCallback: null);
- SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key);
+ SqlConnectionString connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key);
if (connectionOptions.IntegratedSecurity || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated)
{
throw SQL.ChangePasswordConflictsWithSSPI();
@@ -2249,7 +2249,7 @@ public static void ChangePassword(string connectionString, SqlCredential credent
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null);
- SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key);
+ SqlConnectionString connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key);
// Check for connection string values incompatible with SqlCredential
if (!string.IsNullOrEmpty(connectionOptions.UserID) || !string.IsNullOrEmpty(connectionOptions.Password))
@@ -2290,7 +2290,7 @@ private static void ChangePassword(string connectionString, SqlConnectionString
}
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null);
- SqlConnectionFactory.SingletonInstance.ClearPool(key);
+ SqlConnectionFactory.Instance.ClearPool(key);
}
internal Task RegisterForConnectionCloseNotification(Task outerTask, object value, int tag)
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs
index eb5cb511be..68c7d24ea2 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs
@@ -18,7 +18,7 @@ namespace Microsoft.Data.SqlClient
{
public sealed partial class SqlConnection : DbConnection
{
- private static readonly DbConnectionFactory _connectionFactory = SqlConnectionFactory.SingletonInstance;
+ private static readonly SqlConnectionFactory _connectionFactory = SqlConnectionFactory.Instance;
internal static readonly System.Security.CodeAccessPermission ExecutePermission = SqlConnection.CreateExecutePermission();
private DbConnectionOptions _userConnectionOptions;
@@ -69,7 +69,7 @@ internal int CloseCount
}
}
- internal DbConnectionFactory ConnectionFactory
+ internal SqlConnectionFactory ConnectionFactory
{
get
{
@@ -203,8 +203,7 @@ override protected DbCommand CreateDbCommand()
{
using (TryEventScope.Create(" {0}", ObjectID))
{
- DbProviderFactory providerFactory = ConnectionFactory.ProviderFactory;
- DbCommand command = providerFactory.CreateCommand();
+ DbCommand command = SqlClientFactory.Instance.CreateCommand();
command.Connection = this;
return command;
}
@@ -212,8 +211,8 @@ override protected DbCommand CreateDbCommand()
private static System.Security.CodeAccessPermission CreateExecutePermission()
{
- DBDataPermission p = (DBDataPermission)SqlConnectionFactory.SingletonInstance.ProviderFactory.CreatePermission(System.Security.Permissions.PermissionState.None);
- p.Add(String.Empty, String.Empty, KeyRestrictionBehavior.AllowOnly);
+ DBDataPermission p = (DBDataPermission)SqlClientFactory.Instance.CreatePermission(System.Security.Permissions.PermissionState.None);
+ p.Add(string.Empty, string.Empty, KeyRestrictionBehavior.AllowOnly);
return p;
}
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 d460c61619..391c204c60 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
@@ -3081,9 +3081,13 @@ internal bool ThreadHasParserLockForClose
}
}
- internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions)
+ internal override bool TryReplaceConnection(
+ DbConnection outerConnection,
+ SqlConnectionFactory connectionFactory,
+ TaskCompletionSource retry,
+ DbConnectionOptions userOptions)
{
- return base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
+ return TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
index 46e6091ad4..b59e5cb33d 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -130,7 +130,18 @@ internal static Exception ExceptionWithStackTrace(Exception e)
}
}
- internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, int dueTime, int period)
+ internal static Timer UnsafeCreateTimer(
+ TimerCallback callback,
+ object state,
+ int dueTimeMilliseconds,
+ int periodMilliseconds) =>
+ UnsafeCreateTimer(
+ callback,
+ state,
+ TimeSpan.FromMilliseconds(dueTimeMilliseconds),
+ TimeSpan.FromMilliseconds(periodMilliseconds));
+
+ internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
{
// Don't capture the current ExecutionContext and its AsyncLocals onto
// a global timer causing them to live forever
@@ -154,6 +165,7 @@ internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, in
}
}
}
+
#region COM+ exceptions
internal static ArgumentException Argument(string error)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
index d4ea183312..8549c48a8d 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs
@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.Data.Common;
using Microsoft.Data.Common.ConnectionString;
+using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.ConnectionPool;
namespace Microsoft.Data.ProviderBase
@@ -27,7 +28,7 @@ protected DbConnectionClosed(ConnectionState state, bool hidePassword, bool allo
public override void ChangeDatabase(string database) => throw ADP.ClosedConnectionError();
- internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory)
+ internal override void CloseConnection(DbConnection owningObject, SqlConnectionFactory connectionFactory)
{
// not much to do here...
}
@@ -36,13 +37,24 @@ internal override void CloseConnection(DbConnection owningObject, DbConnectionFa
public override void EnlistTransaction(System.Transactions.Transaction transaction) => throw ADP.ClosedConnectionError();
- protected internal override DataTable GetSchema(DbConnectionFactory factory, DbConnectionPoolGroup poolGroup, DbConnection outerConnection, string collectionName, string[] restrictions)
- => throw ADP.ClosedConnectionError();
+ protected internal override DataTable GetSchema(
+ SqlConnectionFactory factory,
+ DbConnectionPoolGroup poolGroup,
+ DbConnection outerConnection,
+ string collectionName,
+ string[] restrictions)
+ {
+ throw ADP.ClosedConnectionError();
+ }
protected override DbReferenceCollection CreateReferenceCollection() => throw ADP.ClosedConnectionError();
- internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions)
- => base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
+ internal override bool TryOpenConnection(
+ DbConnection outerConnection,
+ SqlConnectionFactory connectionFactory,
+ TaskCompletionSource retry,
+ DbConnectionOptions userOptions) =>
+ TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
}
internal abstract class DbConnectionBusy : DbConnectionClosed
@@ -51,7 +63,11 @@ protected DbConnectionBusy(ConnectionState state) : base(state, true, false)
{
}
- internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions)
+ internal override bool TryOpenConnection(
+ DbConnection outerConnection,
+ SqlConnectionFactory connectionFactory,
+ TaskCompletionSource retry,
+ DbConnectionOptions userOptions)
=> throw ADP.ConnectionAlreadyOpen(State);
}
@@ -84,15 +100,23 @@ private DbConnectionClosedConnecting() : base(ConnectionState.Connecting)
{
}
- internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory)
+ internal override void CloseConnection(DbConnection owningObject, SqlConnectionFactory connectionFactory)
{
connectionFactory.SetInnerConnectionTo(owningObject, DbConnectionClosedPreviouslyOpened.SingletonInstance);
}
- internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions)
- => TryOpenConnection(outerConnection, connectionFactory, retry, userOptions);
-
- internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions)
+ internal override bool TryReplaceConnection(
+ DbConnection outerConnection,
+ SqlConnectionFactory connectionFactory,
+ TaskCompletionSource retry,
+ DbConnectionOptions userOptions) =>
+ TryOpenConnection(outerConnection, connectionFactory, retry, userOptions);
+
+ internal override bool TryOpenConnection(
+ DbConnection outerConnection,
+ SqlConnectionFactory connectionFactory,
+ TaskCompletionSource retry,
+ DbConnectionOptions userOptions)
{
if (retry == null || !retry.Task.IsCompleted)
{
@@ -137,7 +161,11 @@ private DbConnectionClosedPreviouslyOpened() : base(ConnectionState.Closed, true
{
}
- internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions)
- => TryOpenConnection(outerConnection, connectionFactory, retry, userOptions);
+ internal override bool TryReplaceConnection(
+ DbConnection outerConnection,
+ SqlConnectionFactory connectionFactory,
+ TaskCompletionSource retry,
+ DbConnectionOptions userOptions) =>
+ TryOpenConnection(outerConnection, connectionFactory, retry, userOptions);
}
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs
deleted file mode 100644
index 399b98435b..0000000000
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs
+++ /dev/null
@@ -1,678 +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.Generic;
-using System.Data;
-using System.Data.Common;
-using System.Diagnostics;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Data.Common;
-using Microsoft.Data.Common.ConnectionString;
-using Microsoft.Data.SqlClient;
-using Microsoft.Data.SqlClient.ConnectionPool;
-
-namespace Microsoft.Data.ProviderBase
-{
- internal abstract class DbConnectionFactory
- {
- private Dictionary _connectionPoolGroups;
- private readonly List _poolsToRelease;
- private readonly List _poolGroupsToRelease;
- private readonly Timer _pruningTimer;
-
- private const int PruningDueTime = 4 * 60 * 1000; // 4 minutes
- private const int PruningPeriod = 30 * 1000; // thirty seconds
-
- private static int _objectTypeCount; // EventSource counter
- internal int ObjectID { get; } = Interlocked.Increment(ref _objectTypeCount);
-
- // s_pendingOpenNonPooled is an array of tasks used to throttle creation of non-pooled connections to
- // a maximum of Environment.ProcessorCount at a time.
- private static uint s_pendingOpenNonPooledNext = 0;
- private static Task[] s_pendingOpenNonPooled = new Task[Environment.ProcessorCount];
- private static Task s_completedTask;
-
- protected DbConnectionFactory()
- {
- _connectionPoolGroups = new Dictionary();
- _poolsToRelease = new List();
- _poolGroupsToRelease = new List();
- _pruningTimer = CreatePruningTimer();
- }
-
- public abstract DbProviderFactory ProviderFactory
- {
- get;
- }
-
- public void ClearAllPools()
- {
- using (TryEventScope.Create(nameof(DbConnectionFactory)))
- {
- Dictionary connectionPoolGroups = _connectionPoolGroups;
- foreach (KeyValuePair entry in connectionPoolGroups)
- {
- DbConnectionPoolGroup poolGroup = entry.Value;
- if (poolGroup != null)
- {
- poolGroup.Clear();
- }
- }
- }
- }
-
- public void ClearPool(DbConnection connection)
- {
- ADP.CheckArgumentNull(connection, nameof(connection));
- using (TryEventScope.Create(" {0}", GetObjectId(connection)))
- {
- DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup(connection);
- if (poolGroup != null)
- {
- poolGroup.Clear();
- }
- }
- }
-
- public void ClearPool(DbConnectionPoolKey key)
- {
- Debug.Assert(key != null, "key cannot be null");
- ADP.CheckArgumentNull(key.ConnectionString, $"{nameof(key)}.{nameof(key.ConnectionString)}");
- using (TryEventScope.Create(" connectionString"))
- {
- Dictionary connectionPoolGroups = _connectionPoolGroups;
- if (connectionPoolGroups.TryGetValue(key, out DbConnectionPoolGroup poolGroup))
- {
- poolGroup.Clear();
- }
- }
- }
-
- internal abstract DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(
- DbConnectionOptions connectionOptions);
-
- protected virtual DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory)
- {
- // providers that support GetSchema must override this with a method that creates a meta data
- // factory appropriate for them.
- cacheMetaDataFactory = false;
- throw ADP.NotSupported();
- }
-
- internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, DbConnectionOptions userOptions)
- {
- Debug.Assert(owningConnection != null, "null owningConnection?");
- Debug.Assert(poolGroup != null, "null poolGroup?");
-
- DbConnectionOptions connectionOptions = poolGroup.ConnectionOptions;
- DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = poolGroup.ProviderInfo;
- DbConnectionPoolKey poolKey = poolGroup.PoolKey;
-
- DbConnectionInternal newConnection = CreateConnection(connectionOptions, poolKey, poolGroupProviderInfo, null, owningConnection, userOptions);
- if (newConnection != null)
- {
- SqlClientEventSource.Metrics.HardConnectRequest();
- newConnection.MakeNonPooledObject(owningConnection);
- }
- SqlClientEventSource.Log.TryTraceEvent(" {0}, Non-pooled database connection created.", ObjectID);
- return newConnection;
- }
-
- internal DbConnectionInternal CreatePooledConnection(IDbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)
- {
- Debug.Assert(pool != null, "null pool?");
- DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool.PoolGroup.ProviderInfo;
- DbConnectionInternal newConnection = CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningObject, userOptions);
-
- if (newConnection != null)
- {
- SqlClientEventSource.Metrics.HardConnectRequest();
-
- newConnection.MakePooledConnection(pool);
- }
- SqlClientEventSource.Log.TryTraceEvent(" {0}, Pooled database connection created.", ObjectID);
- return newConnection;
- }
-
- internal abstract DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(
- DbConnectionOptions connectionOptions);
-
- private Timer CreatePruningTimer() =>
- ADP.UnsafeCreateTimer(
- new TimerCallback(PruneConnectionPoolGroups),
- null,
- PruningDueTime,
- PruningPeriod);
-
- protected DbConnectionOptions FindConnectionOptions(DbConnectionPoolKey key)
- {
- Debug.Assert(key != null, "key cannot be null");
- if (!string.IsNullOrEmpty(key.ConnectionString))
- {
- DbConnectionPoolGroup connectionPoolGroup;
- Dictionary connectionPoolGroups = _connectionPoolGroups;
- if (connectionPoolGroups.TryGetValue(key, out connectionPoolGroup))
- {
- return connectionPoolGroup.ConnectionOptions;
- }
- }
- return null;
- }
-
- private static Task GetCompletedTask()
- {
- Debug.Assert(Monitor.IsEntered(s_pendingOpenNonPooled), $"Expected {nameof(s_pendingOpenNonPooled)} lock to be held.");
- return s_completedTask ?? (s_completedTask = Task.FromResult(null));
- }
-
- internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSource retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection)
- {
- Debug.Assert(owningConnection != null, "null owningConnection?");
-
- DbConnectionPoolGroup poolGroup;
- IDbConnectionPool connectionPool;
- connection = null;
-
- // Work around race condition with clearing the pool between GetConnectionPool obtaining pool
- // and GetConnection on the pool checking the pool state. Clearing the pool in this window
- // will switch the pool into the ShuttingDown state, and GetConnection will return null.
- // There is probably a better solution involving locking the pool/group, but that entails a major
- // re-design of the connection pooling synchronization, so is postponed for now.
-
- // Use retriesLeft to prevent CPU spikes with incremental sleep
- // start with one msec, double the time every retry
- // max time is: 1 + 2 + 4 + ... + 2^(retries-1) == 2^retries -1 == 1023ms (for 10 retries)
- int retriesLeft = 10;
- int timeBetweenRetriesMilliseconds = 1;
-
- do
- {
- poolGroup = GetConnectionPoolGroup(owningConnection);
- // Doing this on the callers thread is important because it looks up the WindowsIdentity from the thread.
- connectionPool = GetConnectionPool(owningConnection, poolGroup);
- if (connectionPool == null)
- {
- // If GetConnectionPool returns null, we can be certain that
- // this connection should not be pooled via DbConnectionPool
- // or have a disabled pool entry.
- poolGroup = GetConnectionPoolGroup(owningConnection); // previous entry have been disabled
-
- if (retry != null)
- {
- Task newTask;
- CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
- lock (s_pendingOpenNonPooled)
- {
- // look for an available task slot (completed or empty)
- int idx;
- for (idx = 0; idx < s_pendingOpenNonPooled.Length; idx++)
- {
- Task task = s_pendingOpenNonPooled[idx];
- if (task == null)
- {
- s_pendingOpenNonPooled[idx] = GetCompletedTask();
- break;
- }
- else if (task.IsCompleted)
- {
- break;
- }
- }
-
- // if didn't find one, pick the next one in round-robin fashion
- if (idx == s_pendingOpenNonPooled.Length)
- {
- idx = (int)(s_pendingOpenNonPooledNext % s_pendingOpenNonPooled.Length);
- unchecked
- {
- s_pendingOpenNonPooledNext++;
- }
- }
-
- // now that we have an antecedent task, schedule our work when it is completed.
- // If it is a new slot or a completed task, this continuation will start right away.
- newTask = CreateReplaceConnectionContinuation(s_pendingOpenNonPooled[idx], owningConnection, retry, userOptions, oldConnection, poolGroup, cancellationTokenSource);
-
- // Place this new task in the slot so any future work will be queued behind it
- s_pendingOpenNonPooled[idx] = newTask;
- }
-
- // Set up the timeout (if needed)
- if (owningConnection.ConnectionTimeout > 0)
- {
- int connectionTimeoutMilliseconds = owningConnection.ConnectionTimeout * 1000;
- cancellationTokenSource.CancelAfter(connectionTimeoutMilliseconds);
- }
-
- // once the task is done, propagate the final results to the original caller
- newTask.ContinueWith(
- continuationAction: TryGetConnectionCompletedContinuation,
- state: Tuple.Create(cancellationTokenSource, retry),
- scheduler: TaskScheduler.Default
- );
-
- return false;
- }
-
- connection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions);
-
- SqlClientEventSource.Metrics.EnterNonPooledConnection();
- }
- else
- {
- if (((SqlClient.SqlConnection)owningConnection).ForceNewConnection)
- {
- Debug.Assert(!(oldConnection is DbConnectionClosed), "Force new connection, but there is no old connection");
- connection = connectionPool.ReplaceConnection(owningConnection, userOptions, oldConnection);
- }
- else
- {
- if (!connectionPool.TryGetConnection(owningConnection, retry, userOptions, out connection))
- {
- return false;
- }
- }
-
- if (connection == null)
- {
- // connection creation failed on semaphore waiting or if max pool reached
- if (connectionPool.IsRunning)
- {
- SqlClientEventSource.Log.TryTraceEvent(" {0}, GetConnection failed because a pool timeout occurred.", ObjectID);
- // If GetConnection failed while the pool is running, the pool timeout occurred.
- throw ADP.PooledOpenTimeout();
- }
- else
- {
- // We've hit the race condition, where the pool was shut down after we got it from the group.
- // Yield time slice to allow shut down activities to complete and a new, running pool to be instantiated
- // before retrying.
- System.Threading.Thread.Sleep(timeBetweenRetriesMilliseconds);
- timeBetweenRetriesMilliseconds *= 2; // double the wait time for next iteration
- }
- }
- }
- } while (connection == null && retriesLeft-- > 0);
-
- if (connection == null)
- {
- SqlClientEventSource.Log.TryTraceEvent(" {0}, GetConnection failed because a pool timeout occurred and all retries were exhausted.", ObjectID);
- // exhausted all retries or timed out - give up
- throw ADP.PooledOpenTimeout();
- }
-
- return true;
- }
-
- private Task CreateReplaceConnectionContinuation(Task task, DbConnection owningConnection, TaskCompletionSource retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionPoolGroup poolGroup, CancellationTokenSource cancellationTokenSource)
- {
- return task.ContinueWith(
- (_) =>
- {
- System.Transactions.Transaction originalTransaction = ADP.GetCurrentTransaction();
- try
- {
- ADP.SetCurrentTransaction(retry.Task.AsyncState as System.Transactions.Transaction);
- var newConnection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions);
- if ((oldConnection != null) && (oldConnection.State == ConnectionState.Open))
- {
- oldConnection.PrepareForReplaceConnection();
- oldConnection.Dispose();
- }
- return newConnection;
- }
- finally
- {
- ADP.SetCurrentTransaction(originalTransaction);
- }
- },
- cancellationTokenSource.Token,
- TaskContinuationOptions.LongRunning,
- TaskScheduler.Default
- );
- }
-
- private void TryGetConnectionCompletedContinuation(Task task, object state)
- {
- Tuple> parameters = (Tuple>)state;
- CancellationTokenSource source = parameters.Item1;
- source.Dispose();
-
- TaskCompletionSource retryCompletionSource = parameters.Item2;
-
- if (task.IsCanceled)
- {
- retryCompletionSource.TrySetException(ADP.ExceptionWithStackTrace(ADP.NonPooledOpenTimeout()));
- }
- else if (task.IsFaulted)
- {
- retryCompletionSource.TrySetException(task.Exception.InnerException);
- }
- else
- {
- if (!retryCompletionSource.TrySetResult(task.Result))
- {
- // The outer TaskCompletionSource was already completed
- // Which means that we don't know if someone has messed with the outer connection in the middle of creation
- // So the best thing to do now is to destroy the newly created connection
- task.Result.DoomThisConnection();
- task.Result.Dispose();
- }
- else
- {
- SqlClientEventSource.Metrics.EnterNonPooledConnection();
- }
- }
- }
-
- private IDbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup)
- {
- // if poolgroup is disabled, it will be replaced with a new entry
-
- Debug.Assert(owningObject != null, "null owningObject?");
- Debug.Assert(connectionPoolGroup != null, "null connectionPoolGroup?");
-
- // It is possible that while the outer connection object has
- // been sitting around in a closed and unused state in some long
- // running app, the pruner may have come along and remove this
- // the pool entry from the master list. If we were to use a
- // pool entry in this state, we would create "unmanaged" pools,
- // which would be bad. To avoid this problem, we automagically
- // re-create the pool entry whenever it's disabled.
-
- // however, don't rebuild connectionOptions if no pooling is involved - let new connections do that work
- if (connectionPoolGroup.IsDisabled && connectionPoolGroup.PoolGroupOptions != null)
- {
- SqlClientEventSource.Log.TryTraceEvent(" {0}, DisabledPoolGroup={1}", ObjectID, connectionPoolGroup?.ObjectID);
-
- // reusing existing pool option in case user originally used SetConnectionPoolOptions
- DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup.PoolGroupOptions;
-
- // get the string to hash on again
- DbConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions;
- Debug.Assert(connectionOptions != null, "prevent expansion of connectionString");
-
- connectionPoolGroup = GetConnectionPoolGroup(connectionPoolGroup.PoolKey, poolOptions, ref connectionOptions);
- Debug.Assert(connectionPoolGroup != null, "null connectionPoolGroup?");
- SetConnectionPoolGroup(owningObject, connectionPoolGroup);
- }
- IDbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this);
- return connectionPool;
- }
-
- internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions)
- {
- if (string.IsNullOrEmpty(key.ConnectionString))
- {
- return (DbConnectionPoolGroup)null;
- }
-
- DbConnectionPoolGroup connectionPoolGroup;
- Dictionary connectionPoolGroups = _connectionPoolGroups;
- if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup) || (connectionPoolGroup.IsDisabled && connectionPoolGroup.PoolGroupOptions != null))
- {
- // If we can't find an entry for the connection string in
- // our collection of pool entries, then we need to create a
- // new pool entry and add it to our collection.
-
- DbConnectionOptions connectionOptions = CreateConnectionOptions(key.ConnectionString, userConnectionOptions);
- if (connectionOptions == null)
- {
- throw ADP.InternalConnectionError(ADP.ConnectionError.ConnectionOptionsMissing);
- }
-
- if (userConnectionOptions == null)
- {
- // we only allow one expansion on the connection string
-
- userConnectionOptions = connectionOptions;
- string expandedConnectionString = connectionOptions.Expand();
-
- // if the expanded string is same instance (default implementation), then use the already created options
- if ((object)expandedConnectionString != (object)key.ConnectionString)
- {
- // CONSIDER: caching the original string to reduce future parsing
- DbConnectionPoolKey newKey = (DbConnectionPoolKey)((ICloneable)key).Clone();
- newKey.ConnectionString = expandedConnectionString;
- return GetConnectionPoolGroup(newKey, null, ref userConnectionOptions);
- }
- }
-
- if (poolOptions == null)
- {
- if (connectionPoolGroup != null)
- {
- // reusing existing pool option in case user originally used SetConnectionPoolOptions
- poolOptions = connectionPoolGroup.PoolGroupOptions;
- }
- else
- {
- // Note: may return null for non-pooled connections
- poolOptions = CreateConnectionPoolGroupOptions(connectionOptions);
- }
- }
-
- lock (this)
- {
- connectionPoolGroups = _connectionPoolGroups;
- if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup))
- {
- DbConnectionPoolGroup newConnectionPoolGroup = new DbConnectionPoolGroup(connectionOptions, key, poolOptions);
- newConnectionPoolGroup.ProviderInfo = CreateConnectionPoolGroupProviderInfo(connectionOptions);
-
- // build new dictionary with space for new connection string
- Dictionary newConnectionPoolGroups = new Dictionary(1 + connectionPoolGroups.Count);
- foreach (KeyValuePair entry in connectionPoolGroups)
- {
- newConnectionPoolGroups.Add(entry.Key, entry.Value);
- }
-
- // lock prevents race condition with PruneConnectionPoolGroups
- newConnectionPoolGroups.Add(key, newConnectionPoolGroup);
-
- SqlClientEventSource.Metrics.EnterActiveConnectionPoolGroup();
- connectionPoolGroup = newConnectionPoolGroup;
- _connectionPoolGroups = newConnectionPoolGroups;
- }
- else
- {
- Debug.Assert(!connectionPoolGroup.IsDisabled, "Disabled pool entry discovered");
- }
- }
- Debug.Assert(connectionPoolGroup != null, "how did we not create a pool entry?");
- Debug.Assert(userConnectionOptions != null, "how did we not have user connection options?");
- }
- else if (userConnectionOptions == null)
- {
- userConnectionOptions = connectionPoolGroup.ConnectionOptions;
- }
- return connectionPoolGroup;
- }
-
- internal DbMetaDataFactory GetMetaDataFactory(DbConnectionPoolGroup connectionPoolGroup, DbConnectionInternal internalConnection)
- {
- Debug.Assert(connectionPoolGroup != null, "connectionPoolGroup may not be null.");
-
- // get the matadatafactory from the pool entry. If it does not already have one
- // create one and save it on the pool entry
- DbMetaDataFactory metaDataFactory = connectionPoolGroup.MetaDataFactory;
-
- // consider serializing this so we don't construct multiple metadata factories
- // if two threads happen to hit this at the same time. One will be GC'd
- if (metaDataFactory == null)
- {
- bool allowCache = false;
- metaDataFactory = CreateMetaDataFactory(internalConnection, out allowCache);
- if (allowCache)
- {
- connectionPoolGroup.MetaDataFactory = metaDataFactory;
- }
- }
- return metaDataFactory;
- }
-
- private void PruneConnectionPoolGroups(object state)
- {
- // when debugging this method, expect multiple threads at the same time
- SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}", ObjectID);
-
- // First, walk the pool release list and attempt to clear each
- // pool, when the pool is finally empty, we dispose of it. If the
- // pool isn't empty, it's because there are active connections or
- // distributed transactions that need it.
- lock (_poolsToRelease)
- {
- if (0 != _poolsToRelease.Count)
- {
- IDbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray();
- foreach (IDbConnectionPool pool in poolsToRelease)
- {
- if (pool != null)
- {
- pool.Clear();
-
- if (0 == pool.Count)
- {
- _poolsToRelease.Remove(pool);
- SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePool={1}", ObjectID, pool.Id);
-
- SqlClientEventSource.Metrics.ExitInactiveConnectionPool();
- }
- }
- }
- }
- }
-
- // Next, walk the pool entry release list and dispose of each
- // pool entry when it is finally empty. If the pool entry isn't
- // empty, it's because there are active pools that need it.
- lock (_poolGroupsToRelease)
- {
- if (0 != _poolGroupsToRelease.Count)
- {
- DbConnectionPoolGroup[] poolGroupsToRelease = _poolGroupsToRelease.ToArray();
- foreach (DbConnectionPoolGroup poolGroup in poolGroupsToRelease)
- {
- if (poolGroup != null)
- {
- int poolsLeft = poolGroup.Clear(); // may add entries to _poolsToRelease
-
- if (0 == poolsLeft)
- {
- _poolGroupsToRelease.Remove(poolGroup);
- SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePoolGroup={1}", ObjectID, poolGroup.ObjectID);
-
- SqlClientEventSource.Metrics.ExitInactiveConnectionPoolGroup();
- }
- }
- }
- }
- }
-
- // Finally, we walk through the collection of connection pool entries
- // and prune each one. This will cause any empty pools to be put
- // into the release list.
- lock (this)
- {
- Dictionary connectionPoolGroups = _connectionPoolGroups;
- Dictionary newConnectionPoolGroups = new Dictionary(connectionPoolGroups.Count);
-
- foreach (KeyValuePair entry in connectionPoolGroups)
- {
- if (entry.Value != null)
- {
- Debug.Assert(!entry.Value.IsDisabled, "Disabled pool entry discovered");
-
- // entries start active and go idle during prune if all pools are gone
- // move idle entries from last prune pass to a queue for pending release
- // otherwise process entry which may move it from active to idle
- if (entry.Value.Prune())
- {
- // may add entries to _poolsToRelease
- QueuePoolGroupForRelease(entry.Value);
- }
- else
- {
- newConnectionPoolGroups.Add(entry.Key, entry.Value);
- }
- }
- }
- _connectionPoolGroups = newConnectionPoolGroups;
- }
- }
-
- 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
- // do it with the pool entry or the pool collection locked.
- Debug.Assert(pool != null, "null pool?");
-
- // set the pool to the shutdown state to force all active
- // connections to be automatically disposed when they
- // are returned to the pool
- pool.Shutdown();
-
- lock (_poolsToRelease)
- {
- if (clearing)
- {
- pool.Clear();
- }
- _poolsToRelease.Add(pool);
- }
- SqlClientEventSource.Metrics.EnterInactiveConnectionPool();
- SqlClientEventSource.Metrics.ExitActiveConnectionPool();
- }
-
- internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup)
- {
- Debug.Assert(poolGroup != null, "null poolGroup?");
- SqlClientEventSource.Log.TryTraceEvent(" {0}, poolGroup={1}", ObjectID, poolGroup.ObjectID);
-
- lock (_poolGroupsToRelease)
- {
- _poolGroupsToRelease.Add(poolGroup);
- }
-
- SqlClientEventSource.Metrics.EnterInactiveConnectionPoolGroup();
- SqlClientEventSource.Metrics.ExitActiveConnectionPoolGroup();
- }
-
- protected abstract DbConnectionInternal CreateConnection(
- DbConnectionOptions options,
- DbConnectionPoolKey poolKey,
- DbConnectionPoolGroupProviderInfo poolGroupProviderInfo,
- IDbConnectionPool pool,
- DbConnection owningConnection,
- DbConnectionOptions userOptions);
-
- abstract protected DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous);
-
- abstract protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions options);
-
- abstract internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection);
-
- abstract internal DbConnectionInternal GetInnerConnection(DbConnection connection);
-
- abstract protected int GetObjectId(DbConnection connection);
-
- abstract internal void PermissionDemand(DbConnection outerConnection);
-
- abstract internal void SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup);
-
- abstract internal void SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to);
-
- abstract internal bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from);
-
- abstract internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to);
-
- virtual internal void Unload()
- {
- _pruningTimer.Dispose();
- }
- }
-}
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 b4dfb4f214..31f207e5ce 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs
@@ -398,7 +398,7 @@ internal void CleanupConnectionOnTransactionCompletion(Transaction transaction)
pool?.TransactionEnded(transaction, this);
}
- internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory)
+ internal virtual void CloseConnection(DbConnection owningObject, SqlConnectionFactory connectionFactory)
{
// The implementation here is the implementation required for the
// "open" internal connections, since our own private "closed"
@@ -708,7 +708,7 @@ internal void MakePooledConnection(IDbConnectionPool connectionPool)
internal void NotifyWeakReference(int message) =>
ReferenceCollection?.Notify(message);
- internal virtual void OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
+ internal virtual void OpenConnection(DbConnection outerConnection, SqlConnectionFactory connectionFactory)
{
if (!TryOpenConnection(outerConnection, connectionFactory, null, null))
{
@@ -814,7 +814,7 @@ internal void SetInStasis()
///
internal virtual bool TryOpenConnection(
DbConnection outerConnection,
- DbConnectionFactory connectionFactory,
+ SqlConnectionFactory connectionFactory,
TaskCompletionSource retry,
DbConnectionOptions userOptions)
{
@@ -823,7 +823,7 @@ internal virtual bool TryOpenConnection(
internal virtual bool TryReplaceConnection(
DbConnection outerConnection,
- DbConnectionFactory connectionFactory,
+ SqlConnectionFactory connectionFactory,
TaskCompletionSource retry,
DbConnectionOptions userOptions)
{
@@ -871,7 +871,7 @@ protected internal void DoomThisConnection()
}
protected internal virtual DataTable GetSchema(
- DbConnectionFactory factory,
+ SqlConnectionFactory factory,
DbConnectionPoolGroup poolGroup,
DbConnection outerConnection,
string collectionName,
@@ -901,7 +901,11 @@ protected virtual void ReleaseAdditionalLocksForClose(bool lockToken)
// No additional locks in default implementation
}
- protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions)
+ protected bool TryOpenConnectionInternal(
+ DbConnection outerConnection,
+ SqlConnectionFactory connectionFactory,
+ TaskCompletionSource retry,
+ DbConnectionOptions userOptions)
{
// ?->Connecting: prevent set_ConnectionString during Open
if (connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this))
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs
index c4772bb736..8b0efe8763 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs
@@ -25,7 +25,7 @@ internal sealed class ChannelDbConnectionPool : IDbConnectionPool
public ConcurrentDictionary AuthenticationContexts => throw new NotImplementedException();
///
- public DbConnectionFactory ConnectionFactory => throw new NotImplementedException();
+ public SqlConnectionFactory ConnectionFactory => throw new NotImplementedException();
///
public int Count => throw new NotImplementedException();
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs
index 805beed2b5..226a5749b4 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
@@ -132,7 +132,7 @@ internal int Clear()
IDbConnectionPool pool = entry.Value;
if (pool != null)
{
- DbConnectionFactory connectionFactory = pool.ConnectionFactory;
+ SqlConnectionFactory connectionFactory = pool.ConnectionFactory;
connectionFactory.QueuePoolForRelease(pool, true);
}
@@ -143,7 +143,7 @@ internal int Clear()
return _poolCollection.Count;
}
- internal IDbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory)
+ internal IDbConnectionPool GetConnectionPool(SqlConnectionFactory connectionFactory)
{
// When this method returns null it indicates that the connection
// factory should not use pooling.
@@ -282,7 +282,7 @@ internal bool Prune()
// to use it while we're processing and finally we put the
// pool into a list of pools to be released when they
// are completely empty.
- DbConnectionFactory connectionFactory = pool.ConnectionFactory;
+ SqlConnectionFactory connectionFactory = pool.ConnectionFactory;
connectionFactory.QueuePoolForRelease(pool, false);
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs
index d6647f832e..066318fce2 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs
@@ -28,7 +28,7 @@ internal interface IDbConnectionPool
///
/// Gets the factory used to create database connections.
///
- DbConnectionFactory ConnectionFactory { get; }
+ SqlConnectionFactory ConnectionFactory { get; }
///
/// The number of connections currently managed by the pool.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs
index 77d2b3bae1..bd6ebf708e 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
@@ -403,7 +403,7 @@ internal WaitHandle[] GetHandles(bool withCreate)
private readonly int _cleanupWait;
private readonly DbConnectionPoolIdentity _identity;
- private readonly DbConnectionFactory _connectionFactory;
+ private readonly SqlConnectionFactory _connectionFactory;
private readonly DbConnectionPoolGroup _connectionPoolGroup;
private readonly DbConnectionPoolGroupOptions _connectionPoolGroupOptions;
private DbConnectionPoolProviderInfo _connectionPoolProviderInfo;
@@ -439,10 +439,10 @@ internal WaitHandle[] GetHandles(bool withCreate)
// only created by DbConnectionPoolGroup.GetConnectionPool
internal WaitHandleDbConnectionPool(
- DbConnectionFactory connectionFactory,
- DbConnectionPoolGroup connectionPoolGroup,
- DbConnectionPoolIdentity identity,
- DbConnectionPoolProviderInfo connectionPoolProviderInfo)
+ SqlConnectionFactory connectionFactory,
+ DbConnectionPoolGroup connectionPoolGroup,
+ DbConnectionPoolIdentity identity,
+ DbConnectionPoolProviderInfo connectionPoolProviderInfo)
{
Debug.Assert(connectionPoolGroup != null, "null connectionPoolGroup");
@@ -492,7 +492,7 @@ private int CreationTimeout
public int Count => _totalObjects;
- public DbConnectionFactory ConnectionFactory => _connectionFactory;
+ public SqlConnectionFactory ConnectionFactory => _connectionFactory;
public bool ErrorOccurred => _errorOccurred;
@@ -746,7 +746,12 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio
try
{
- newObj = _connectionFactory.CreatePooledConnection(this, owningObject, _connectionPoolGroup.ConnectionOptions, _connectionPoolGroup.PoolKey, userOptions);
+ newObj = _connectionFactory.CreatePooledConnection(
+ owningObject,
+ this,
+ _connectionPoolGroup.PoolKey,
+ _connectionPoolGroup.ConnectionOptions,
+ userOptions);
if (newObj == null)
{
throw ADP.InternalError(ADP.InternalErrorCode.CreateObjectReturnedNull); // CreateObject succeeded, but null object
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs
index ae4c7bc98d..fd2633a4b9 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs
@@ -3,10 +3,14 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Generic;
+using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.IO;
using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Data.Common;
using Microsoft.Data.Common.ConnectionString;
using Microsoft.Data.ProviderBase;
@@ -18,26 +22,561 @@
namespace Microsoft.Data.SqlClient
{
- sealed internal class SqlConnectionFactory : DbConnectionFactory
+ // @TODO: Facade pattern (interface, use interface, add constructor overloads for providing non-default interface, reseal)
+ internal sealed class SqlConnectionFactory
{
- public static readonly SqlConnectionFactory SingletonInstance = new SqlConnectionFactory();
+ #region Member Variables
- private SqlConnectionFactory() : base()
+ private static readonly TimeSpan PruningDueTime = TimeSpan.FromMinutes(4);
+ private static readonly TimeSpan PruningPeriod = TimeSpan.FromSeconds(30);
+ private static readonly Task CompletedTask =
+ Task.FromResult(null);
+
+ // s_pendingOpenNonPooled is an array of tasks used to throttle creation of non-pooled
+ // connections to a maximum of Environment.ProcessorCount at a time.
+ private static readonly Task[] s_pendingOpenNonPooled =
+ new Task[Environment.ProcessorCount];
+
+ private static int s_objectTypeCount;
+ private static uint s_pendingOpenNonPooledNext = 0;
+
+ private readonly List _poolGroupsToRelease;
+ private readonly List _poolsToRelease;
+ private readonly Timer _pruningTimer;
+ private Dictionary _connectionPoolGroups;
+
+ #endregion
+
+ #region Constructors
+
+ private SqlConnectionFactory()
{
+ _connectionPoolGroups = new Dictionary();
+ _poolsToRelease = new List();
+ _poolGroupsToRelease = new List();
+ _pruningTimer = ADP.UnsafeCreateTimer(
+ PruneConnectionPoolGroups,
+ state: null,
+ PruningDueTime,
+ PruningPeriod);
+
#if NET
SubscribeToAssemblyLoadContextUnload();
#endif
}
+
+ #endregion
+
+ #region Properties
+
+ internal static SqlConnectionFactory Instance { get; } = new SqlConnectionFactory();
+
+ internal int ObjectId { get; } = Interlocked.Increment(ref s_objectTypeCount);
+
+ #endregion
- override public DbProviderFactory ProviderFactory
+ #region Public Methods
+
+ internal void ClearAllPools()
{
- get
+ using TryEventScope scope = TryEventScope.Create(nameof(SqlConnectionFactory));
+ foreach (DbConnectionPoolGroup group in _connectionPoolGroups.Values)
{
- return SqlClientFactory.Instance;
+ group?.Clear();
}
}
- protected override DbConnectionInternal CreateConnection(
+ internal void ClearPool(DbConnection connection)
+ {
+ ADP.CheckArgumentNull(connection, nameof(connection));
+
+ using TryEventScope scope = TryEventScope.Create(" {0}", GetObjectId(connection));
+ DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup(connection);
+ poolGroup?.Clear();
+ }
+
+ internal void ClearPool(DbConnectionPoolKey key)
+ {
+ ADP.CheckArgumentNull(key.ConnectionString, $"{nameof(key)}.{nameof(key.ConnectionString)}");
+
+ using TryEventScope scope = TryEventScope.Create(" connectionString");
+ if (_connectionPoolGroups.TryGetValue(key, out DbConnectionPoolGroup poolGroup))
+ {
+ poolGroup?.Clear();
+ }
+ }
+
+ internal DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) =>
+ ((SqlConnectionString)connectionOptions).UserInstance
+ ? new SqlConnectionPoolProviderInfo()
+ : null;
+
+ internal SqlInternalConnectionTds CreateNonPooledConnection(
+ DbConnection owningConnection,
+ DbConnectionPoolGroup poolGroup,
+ DbConnectionOptions userOptions)
+ {
+ Debug.Assert(owningConnection is not null, "null owningConnection?");
+ Debug.Assert(poolGroup is not null, "null poolGroup?");
+
+ SqlInternalConnectionTds newConnection = CreateConnection(
+ poolGroup.ConnectionOptions,
+ poolGroup.PoolKey,
+ poolGroup.ProviderInfo,
+ pool: null,
+ owningConnection,
+ userOptions);
+ if (newConnection is not null)
+ {
+ SqlClientEventSource.Metrics.HardConnectRequest();
+ newConnection.MakeNonPooledObject(owningConnection);
+ }
+
+ SqlClientEventSource.Log.TryTraceEvent(" {0}, Non-pooled database connection created.", ObjectId);
+ return newConnection;
+ }
+
+ internal SqlInternalConnectionTds CreatePooledConnection(
+ DbConnection owningConnection,
+ IDbConnectionPool pool,
+ DbConnectionPoolKey poolKey,
+ DbConnectionOptions options,
+ DbConnectionOptions userOptions)
+ {
+ Debug.Assert(pool != null, "null pool?");
+
+ SqlInternalConnectionTds newConnection = CreateConnection(
+ options,
+ poolKey, // @TODO: is pool.PoolGroup.Key the same thing?
+ pool.PoolGroup.ProviderInfo,
+ pool,
+ owningConnection,
+ userOptions);
+ if (newConnection is not null)
+ {
+ SqlClientEventSource.Metrics.HardConnectRequest();
+ newConnection.MakePooledConnection(pool);
+ }
+
+ SqlClientEventSource.Log.TryTraceEvent(" {0}, Pooled database connection created.", ObjectId);
+ return newConnection;
+ }
+
+ internal DbConnectionPoolGroup GetConnectionPoolGroup(
+ DbConnectionPoolKey key,
+ DbConnectionPoolGroupOptions poolOptions,
+ ref DbConnectionOptions userConnectionOptions)
+ {
+ if (string.IsNullOrEmpty(key.ConnectionString))
+ {
+ return null;
+ }
+
+ if (!_connectionPoolGroups.TryGetValue(key, out DbConnectionPoolGroup connectionPoolGroup) ||
+ (connectionPoolGroup.IsDisabled && connectionPoolGroup.PoolGroupOptions != null))
+ {
+ // If we can't find an entry for the connection string in
+ // our collection of pool entries, then we need to create a
+ // new pool entry and add it to our collection.
+
+ SqlConnectionString connectionOptions = new SqlConnectionString(key.ConnectionString);
+
+ if (userConnectionOptions is null)
+ {
+ // We only allow one expansion on the connection string
+ userConnectionOptions = connectionOptions;
+ string expandedConnectionString = connectionOptions.Expand();
+
+ // if the expanded string is same instance (default implementation), then use the already created options
+ if ((object)expandedConnectionString != (object)key.ConnectionString)
+ {
+ // CONSIDER: caching the original string to reduce future parsing
+ DbConnectionPoolKey newKey = (DbConnectionPoolKey)key.Clone();
+ newKey.ConnectionString = expandedConnectionString;
+ return GetConnectionPoolGroup(newKey, null, ref userConnectionOptions);
+ }
+ }
+
+ if (poolOptions is null)
+ {
+ if (connectionPoolGroup is not null)
+ {
+ // reusing existing pool option in case user originally used SetConnectionPoolOptions
+ poolOptions = connectionPoolGroup.PoolGroupOptions;
+ }
+ else
+ {
+ // Note: may return null for non-pooled connections
+ poolOptions = CreateConnectionPoolGroupOptions(connectionOptions);
+ }
+ }
+
+ lock (this)
+ {
+ if (!_connectionPoolGroups.TryGetValue(key, out connectionPoolGroup))
+ {
+ DbConnectionPoolGroup newConnectionPoolGroup =
+ new DbConnectionPoolGroup(connectionOptions, key, poolOptions)
+ {
+ ProviderInfo = CreateConnectionPoolGroupProviderInfo(connectionOptions)
+ };
+
+ // build new dictionary with space for new connection string
+ Dictionary newConnectionPoolGroups =
+ new Dictionary(1 + _connectionPoolGroups.Count);
+ foreach (KeyValuePair entry in _connectionPoolGroups)
+ {
+ newConnectionPoolGroups.Add(entry.Key, entry.Value);
+ }
+
+ // lock prevents race condition with PruneConnectionPoolGroups
+ newConnectionPoolGroups.Add(key, newConnectionPoolGroup);
+
+ SqlClientEventSource.Metrics.EnterActiveConnectionPoolGroup();
+ connectionPoolGroup = newConnectionPoolGroup;
+ _connectionPoolGroups = newConnectionPoolGroups;
+ }
+ else
+ {
+ Debug.Assert(!connectionPoolGroup.IsDisabled, "Disabled pool entry discovered");
+ }
+ }
+
+ Debug.Assert(connectionPoolGroup != null, "how did we not create a pool entry?");
+ Debug.Assert(userConnectionOptions != null, "how did we not have user connection options?");
+ }
+ else if (userConnectionOptions is null)
+ {
+ userConnectionOptions = connectionPoolGroup.ConnectionOptions;
+ }
+
+ return connectionPoolGroup;
+ }
+
+ internal DbMetaDataFactory GetMetaDataFactory(
+ DbConnectionPoolGroup poolGroup,
+ DbConnectionInternal internalConnection)
+ {
+ Debug.Assert(poolGroup is not null, "connectionPoolGroup may not be null.");
+
+ // Get the matadatafactory from the pool entry. If it does not already have one
+ // create one and save it on the pool entry
+ DbMetaDataFactory metaDataFactory = poolGroup.MetaDataFactory;
+
+ // CONSIDER: serializing this so we don't construct multiple metadata factories
+ // if two threads happen to hit this at the same time. One will be GC'd
+ if (metaDataFactory is null)
+ {
+ metaDataFactory = CreateMetaDataFactory(internalConnection, out bool allowCache);
+ if (allowCache)
+ {
+ poolGroup.MetaDataFactory = metaDataFactory;
+ }
+ }
+
+ return metaDataFactory;
+ }
+
+ 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 do it with the pool entry or the pool
+ // collection locked.
+ Debug.Assert(pool != null, "null pool?");
+
+ // Set the pool to the shutdown state to force all active connections to be
+ // automatically disposed when they are returned to the pool
+ pool.Shutdown();
+
+ lock (_poolsToRelease)
+ {
+ if (clearing)
+ {
+ pool.Clear();
+ }
+ _poolsToRelease.Add(pool);
+ }
+
+ SqlClientEventSource.Metrics.EnterInactiveConnectionPool();
+ SqlClientEventSource.Metrics.ExitActiveConnectionPool();
+ }
+
+ internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup)
+ {
+ Debug.Assert(poolGroup != null, "null poolGroup?");
+ SqlClientEventSource.Log.TryTraceEvent(" {0}, poolGroup={1}", ObjectId, poolGroup.ObjectID);
+
+ lock (_poolGroupsToRelease)
+ {
+ _poolGroupsToRelease.Add(poolGroup);
+ }
+
+ SqlClientEventSource.Metrics.EnterInactiveConnectionPoolGroup();
+ SqlClientEventSource.Metrics.ExitActiveConnectionPoolGroup();
+ }
+
+ internal bool TryGetConnection(
+ DbConnection owningConnection,
+ TaskCompletionSource retry,
+ DbConnectionOptions userOptions,
+ DbConnectionInternal oldConnection,
+ out DbConnectionInternal connection)
+ {
+ Debug.Assert(owningConnection is not null, "null owningConnection?");
+
+ connection = null;
+
+ // Work around race condition with clearing the pool between GetConnectionPool obtaining pool
+ // and GetConnection on the pool checking the pool state. Clearing the pool in this window
+ // will switch the pool into the ShuttingDown state, and GetConnection will return null.
+ // There is probably a better solution involving locking the pool/group, but that entails a major
+ // re-design of the connection pooling synchronization, so is postponed for now.
+
+ // Use retriesLeft to prevent CPU spikes with incremental sleep
+ // start with one msec, double the time every retry
+ // max time is: 1 + 2 + 4 + ... + 2^(retries-1) == 2^retries -1 == 1023ms (for 10 retries)
+ int retriesLeft = 10;
+ int timeBetweenRetriesMilliseconds = 1;
+
+ do
+ {
+ DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup(owningConnection);
+
+ // Doing this on the callers thread is important because it looks up the WindowsIdentity from the thread.
+ IDbConnectionPool connectionPool = GetConnectionPool(owningConnection, poolGroup);
+ if (connectionPool == null)
+ {
+ // If GetConnectionPool returns null, we can be certain that this connection
+ // should not be pooled via DbConnectionPool or have a disabled pool entry.
+ poolGroup = GetConnectionPoolGroup(owningConnection); // previous entry have been disabled
+
+ if (retry is not null)
+ {
+ Task newTask;
+ CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
+ lock (s_pendingOpenNonPooled)
+ {
+ // look for an available task slot (completed or empty)
+ int idx;
+ for (idx = 0; idx < s_pendingOpenNonPooled.Length; idx++)
+ {
+ Task task = s_pendingOpenNonPooled[idx];
+ if (task is null)
+ {
+ s_pendingOpenNonPooled[idx] = CompletedTask;
+ break;
+ }
+
+ if (task.IsCompleted)
+ {
+ break;
+ }
+ }
+
+ // if didn't find one, pick the next one in round-robin fashion
+ if (idx == s_pendingOpenNonPooled.Length)
+ {
+ idx = (int)(s_pendingOpenNonPooledNext % s_pendingOpenNonPooled.Length);
+ unchecked
+ {
+ s_pendingOpenNonPooledNext++;
+ }
+ }
+
+ // now that we have an antecedent task, schedule our work when it is completed.
+ // If it is a new slot or a completed task, this continuation will start right away.
+ newTask = CreateReplaceConnectionContinuation(
+ s_pendingOpenNonPooled[idx],
+ owningConnection,
+ retry,
+ userOptions,
+ oldConnection,
+ poolGroup,
+ cancellationTokenSource);
+
+ // Place this new task in the slot so any future work will be queued behind it
+ s_pendingOpenNonPooled[idx] = newTask;
+ }
+
+ // Set up the timeout (if needed)
+ if (owningConnection.ConnectionTimeout > 0)
+ {
+ int connectionTimeoutMilliseconds = owningConnection.ConnectionTimeout * 1000;
+ cancellationTokenSource.CancelAfter(connectionTimeoutMilliseconds);
+ }
+
+ // once the task is done, propagate the final results to the original caller
+ newTask.ContinueWith(
+ continuationAction: TryGetConnectionCompletedContinuation,
+ state: Tuple.Create(cancellationTokenSource, retry),
+ scheduler: TaskScheduler.Default
+ );
+
+ return false;
+ }
+
+ connection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions);
+
+ SqlClientEventSource.Metrics.EnterNonPooledConnection();
+ }
+ else
+ {
+ if (((SqlConnection)owningConnection).ForceNewConnection)
+ {
+ Debug.Assert(oldConnection is not DbConnectionClosed, "Force new connection, but there is no old connection");
+
+ connection = connectionPool.ReplaceConnection(owningConnection, userOptions, oldConnection);
+ }
+ else
+ {
+ if (!connectionPool.TryGetConnection(owningConnection, retry, userOptions, out connection))
+ {
+ return false;
+ }
+ }
+
+ if (connection is null)
+ {
+ // connection creation failed on semaphore waiting or if max pool reached
+ if (connectionPool.IsRunning)
+ {
+ SqlClientEventSource.Log.TryTraceEvent(" {0}, GetConnection failed because a pool timeout occurred.", ObjectId);
+ // If GetConnection failed while the pool is running, the pool timeout occurred.
+ throw ADP.PooledOpenTimeout();
+ }
+
+ // We've hit the race condition, where the pool was shut down after we
+ // got it from the group. Yield time slice to allow shutdown activities
+ // to complete and a new, running pool to be instantiated before
+ // retrying.
+ Thread.Sleep(timeBetweenRetriesMilliseconds);
+ timeBetweenRetriesMilliseconds *= 2; // double the wait time for next iteration
+ }
+ }
+ } while (connection == null && retriesLeft-- > 0);
+
+ if (connection == null)
+ {
+ SqlClientEventSource.Log.TryTraceEvent(" {0}, GetConnection failed because a pool timeout occurred and all retries were exhausted.", ObjectId);
+ // exhausted all retries or timed out - give up
+ throw ADP.PooledOpenTimeout();
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(
+ DbConnectionOptions connectionOptions) =>
+ new SqlConnectionPoolGroupProviderInfo((SqlConnectionString)connectionOptions);
+
+ internal SqlConnectionString FindSqlConnectionOptions(SqlConnectionPoolKey key)
+ {
+ Debug.Assert(key is not null, "Key cannot be null");
+
+ DbConnectionOptions connectionOptions = null;
+
+ if (!string.IsNullOrEmpty(key.ConnectionString) &&
+ _connectionPoolGroups.TryGetValue(key, out DbConnectionPoolGroup poolGroup))
+ {
+ connectionOptions = poolGroup.ConnectionOptions;
+ }
+
+ if (connectionOptions is null)
+ {
+ connectionOptions = new SqlConnectionString(key.ConnectionString);
+ }
+
+ if (connectionOptions.IsEmpty)
+ {
+ throw ADP.NoConnectionString();
+ }
+
+ return (SqlConnectionString)connectionOptions;
+ }
+
+ // @TODO: All these methods seem redundant ... shouldn't we always have a SqlConnection?
+ internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection)
+ {
+ SqlConnection c = (connection as SqlConnection);
+ if (c != null)
+ {
+ return c.PoolGroup;
+ }
+ return null;
+ }
+
+ internal DbConnectionInternal GetInnerConnection(DbConnection connection)
+ {
+ SqlConnection c = (connection as SqlConnection);
+ if (c != null)
+ {
+ return c.InnerConnection;
+ }
+ return null;
+ }
+
+ internal int GetObjectId(DbConnection connection)
+ {
+ SqlConnection c = (connection as SqlConnection);
+ if (c != null)
+ {
+ return c.ObjectID;
+ }
+ return 0;
+ }
+
+ internal void PermissionDemand(DbConnection outerConnection)
+ {
+ SqlConnection c = (outerConnection as SqlConnection);
+ if (c != null)
+ {
+ c.PermissionDemand();
+ }
+ }
+
+ internal void SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup)
+ {
+ SqlConnection c = (outerConnection as SqlConnection);
+ if (c != null)
+ {
+ c.PoolGroup = poolGroup;
+ }
+ }
+
+ internal void SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to)
+ {
+ SqlConnection c = (owningObject as SqlConnection);
+ if (c != null)
+ {
+ c.SetInnerConnectionEvent(to);
+ }
+ }
+
+ internal bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from)
+ {
+ SqlConnection c = (owningObject as SqlConnection);
+ if (c != null)
+ {
+ return c.SetInnerConnectionFrom(to, from);
+ }
+ return false;
+ }
+
+ internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to)
+ {
+ SqlConnection c = (owningObject as SqlConnection);
+ if (c != null)
+ {
+ c.SetInnerConnectionTo(to);
+ }
+ }
+
+ #region Private Methods
+
+ // @TODO: I think this could be broken down into methods more specific to use cases above
+ private static SqlInternalConnectionTds CreateConnection(
DbConnectionOptions options,
DbConnectionPoolKey poolKey,
DbConnectionPoolGroupProviderInfo poolGroupProviderInfo,
@@ -70,10 +609,9 @@ protected override DbConnectionInternal CreateConnection(
bool redirectedUserInstance = false;
DbConnectionPoolIdentity identity = null;
- // Pass DbConnectionPoolIdentity to SqlInternalConnectionTds if using integrated security
- // or active directory integrated security.
+ // Pass DbConnectionPoolIdentity to SqlInternalConnectionTds if using integrated security.
// Used by notifications.
- if (opt.IntegratedSecurity || opt.Authentication is SqlAuthenticationMethod.ActiveDirectoryIntegrated)
+ if (opt.IntegratedSecurity || opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated)
{
if (pool != null)
{
@@ -160,26 +698,7 @@ protected override DbConnectionInternal CreateConnection(
key.AccessTokenCallback);
}
- protected override DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous)
- {
- Debug.Assert(!string.IsNullOrEmpty(connectionString), "empty connectionString");
- SqlConnectionString result = new SqlConnectionString(connectionString);
- return result;
- }
-
- internal override DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions)
- {
- DbConnectionPoolProviderInfo providerInfo = null;
-
- if (((SqlConnectionString)connectionOptions).UserInstance)
- {
- providerInfo = new SqlConnectionPoolProviderInfo();
- }
-
- return providerInfo;
- }
-
- protected override DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions connectionOptions)
+ private static DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(SqlConnectionString connectionOptions)
{
SqlConnectionString opt = (SqlConnectionString)connectionOptions;
@@ -224,123 +743,222 @@ protected override DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions
return poolingOptions;
}
- internal override DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(
- DbConnectionOptions connectionOptions)
+ private static DbMetaDataFactory CreateMetaDataFactory(
+ DbConnectionInternal internalConnection,
+ out bool cacheMetaDataFactory)
{
- return new SqlConnectionPoolGroupProviderInfo((SqlConnectionString)connectionOptions);
- }
+ Debug.Assert(internalConnection is not null, "internalConnection may not be null.");
- internal static SqlConnectionString FindSqlConnectionOptions(SqlConnectionPoolKey key)
- {
- SqlConnectionString connectionOptions = (SqlConnectionString)SingletonInstance.FindConnectionOptions(key);
- if (connectionOptions == null)
- {
- connectionOptions = new SqlConnectionString(key.ConnectionString);
- }
- if (connectionOptions.IsEmpty)
- {
- throw ADP.NoConnectionString();
- }
- return connectionOptions;
+ Stream xmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Data.SqlClient.SqlMetaData.xml");
+ Debug.Assert(xmlStream is not null, $"{nameof(xmlStream)} may not be null.");
+
+ cacheMetaDataFactory = true;
+ return new SqlMetaDataFactory(xmlStream,
+ internalConnection.ServerVersion,
+ internalConnection.ServerVersion);
}
-
- // @TODO: All these methods seem redundant ... shouldn't we always have a SqlConnection?
- override internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection)
+
+ private Task CreateReplaceConnectionContinuation(
+ Task task,
+ DbConnection owningConnection,
+ TaskCompletionSource retry,
+ DbConnectionOptions userOptions,
+ DbConnectionInternal oldConnection,
+ DbConnectionPoolGroup poolGroup,
+ CancellationTokenSource cancellationTokenSource)
{
- SqlConnection c = (connection as SqlConnection);
- if (c != null)
- {
- return c.PoolGroup;
- }
- return null;
+ return task.ContinueWith(
+ _ =>
+ {
+ System.Transactions.Transaction originalTransaction = ADP.GetCurrentTransaction();
+ try
+ {
+ ADP.SetCurrentTransaction(retry.Task.AsyncState as System.Transactions.Transaction);
+
+ DbConnectionInternal newConnection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions);
+
+ if (oldConnection?.State == ConnectionState.Open)
+ {
+ oldConnection.PrepareForReplaceConnection();
+ oldConnection.Dispose();
+ }
+
+ return newConnection;
+ }
+ finally
+ {
+ ADP.SetCurrentTransaction(originalTransaction);
+ }
+ },
+ cancellationTokenSource.Token,
+ TaskContinuationOptions.LongRunning,
+ TaskScheduler.Default
+ );
}
- override internal DbConnectionInternal GetInnerConnection(DbConnection connection)
+ private IDbConnectionPool GetConnectionPool(
+ DbConnection owningObject,
+ DbConnectionPoolGroup connectionPoolGroup)
{
- SqlConnection c = (connection as SqlConnection);
- if (c != null)
+ // If poolgroup is disabled, it will be replaced with a new entry
+
+ Debug.Assert(owningObject != null, "null owningObject?");
+ Debug.Assert(connectionPoolGroup != null, "null connectionPoolGroup?");
+
+ // It is possible that while the outer connection object has been sitting around in a
+ // closed and unused state in some long-running app, the pruner may have come along and
+ // remove this the pool entry from the master list. If we were to use a pool entry in
+ // this state, we would create "unmanaged" pools, which would be bad. To avoid this
+ // problem, we automagically re-create the pool entry whenever it's disabled.
+
+ // however, don't rebuild connectionOptions if no pooling is involved - let new connections do that work
+ if (connectionPoolGroup.IsDisabled && connectionPoolGroup.PoolGroupOptions != null)
{
- return c.InnerConnection;
+ SqlClientEventSource.Log.TryTraceEvent(" {0}, DisabledPoolGroup={1}", ObjectId, connectionPoolGroup.ObjectID);
+
+ // reusing existing pool option in case user originally used SetConnectionPoolOptions
+ DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup.PoolGroupOptions;
+
+ // get the string to hash on again
+ DbConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions;
+ Debug.Assert(connectionOptions != null, "prevent expansion of connectionString");
+
+ connectionPoolGroup = GetConnectionPoolGroup(connectionPoolGroup.PoolKey, poolOptions, ref connectionOptions);
+ Debug.Assert(connectionPoolGroup != null, "null connectionPoolGroup?");
+ SetConnectionPoolGroup(owningObject, connectionPoolGroup);
}
- return null;
+
+ IDbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this);
+ return connectionPool;
}
- override protected int GetObjectId(DbConnection connection)
+ private void PruneConnectionPoolGroups(object state)
{
- SqlConnection c = (connection as SqlConnection);
- if (c != null)
+ // When debugging this method, expect multiple threads at the same time
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}", ObjectId);
+
+ // First, walk the pool release list and attempt to clear each pool, when the pool is
+ // finally empty, we dispose of it. If the pool isn't empty, it's because there are
+ // active connections or distributed transactions that need it.
+ lock (_poolsToRelease)
{
- return c.ObjectID;
+ if (_poolsToRelease.Count != 0)
+ {
+ IDbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray();
+ foreach (IDbConnectionPool pool in poolsToRelease)
+ {
+ if (pool is not null)
+ {
+ pool.Clear();
+
+ if (pool.Count == 0)
+ {
+ _poolsToRelease.Remove(pool);
+
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePool={1}", ObjectId, pool.Id);
+ SqlClientEventSource.Metrics.ExitInactiveConnectionPool();
+ }
+ }
+ }
+ }
}
- return 0;
- }
- override internal void PermissionDemand(DbConnection outerConnection)
- {
- SqlConnection c = (outerConnection as SqlConnection);
- if (c != null)
+ // Next, walk the pool entry release list and dispose of each pool entry when it is
+ // finally empty. If the pool entry isn't empty, it's because there are active pools
+ // that need it.
+ lock (_poolGroupsToRelease)
{
- c.PermissionDemand();
+ if (_poolGroupsToRelease.Count != 0)
+ {
+ DbConnectionPoolGroup[] poolGroupsToRelease = _poolGroupsToRelease.ToArray();
+ foreach (DbConnectionPoolGroup poolGroup in poolGroupsToRelease)
+ {
+ if (poolGroup != null)
+ {
+ int poolsLeft = poolGroup.Clear(); // may add entries to _poolsToRelease
+
+ if (poolsLeft == 0)
+ {
+ _poolGroupsToRelease.Remove(poolGroup);
+ SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePoolGroup={1}", ObjectId, poolGroup.ObjectID);
+
+ SqlClientEventSource.Metrics.ExitInactiveConnectionPoolGroup();
+ }
+ }
+ }
+ }
}
- }
- override internal void SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup)
- {
- SqlConnection c = (outerConnection as SqlConnection);
- if (c != null)
+ // Finally, we walk through the collection of connection pool entries and prune each
+ // one. This will cause any empty pools to be put into the release list.
+ lock (this)
{
- c.PoolGroup = poolGroup;
+ Dictionary connectionPoolGroups = _connectionPoolGroups;
+ Dictionary newConnectionPoolGroups = new Dictionary(connectionPoolGroups.Count);
+
+ foreach (KeyValuePair entry in connectionPoolGroups)
+ {
+ if (entry.Value != null)
+ {
+ Debug.Assert(!entry.Value.IsDisabled, "Disabled pool entry discovered");
+
+ // entries start active and go idle during prune if all pools are gone
+ // move idle entries from last prune pass to a queue for pending release
+ // otherwise process entry which may move it from active to idle
+ if (entry.Value.Prune())
+ {
+ // may add entries to _poolsToRelease
+ QueuePoolGroupForRelease(entry.Value);
+ }
+ else
+ {
+ newConnectionPoolGroups.Add(entry.Key, entry.Value);
+ }
+ }
+ }
+ _connectionPoolGroups = newConnectionPoolGroups;
}
}
-
- override internal void SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to)
+
+ private void TryGetConnectionCompletedContinuation(Task task, object state)
{
- SqlConnection c = (owningObject as SqlConnection);
- if (c != null)
+ // Decompose the state into the parameters we want
+ (CancellationTokenSource cts, TaskCompletionSource tcs) =
+ (Tuple>)state;
+
+ cts.Dispose();
+
+ if (task.IsCanceled)
{
- c.SetInnerConnectionEvent(to);
+ tcs.TrySetException(ADP.ExceptionWithStackTrace(ADP.NonPooledOpenTimeout()));
}
- }
-
- override internal bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from)
- {
- SqlConnection c = (owningObject as SqlConnection);
- if (c != null)
+ else if (task.IsFaulted)
{
- return c.SetInnerConnectionFrom(to, from);
+ tcs.TrySetException(task.Exception.InnerException);
}
- return false;
- }
-
- override internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to)
- {
- SqlConnection c = (owningObject as SqlConnection);
- if (c != null)
+ else
{
- c.SetInnerConnectionTo(to);
+ if (!tcs.TrySetResult(task.Result))
+ {
+ // The outer TaskCompletionSource was already completed
+ // Which means that we don't know if someone has messed with the outer connection in the middle of creation
+ // So the best thing to do now is to destroy the newly created connection
+ task.Result.DoomThisConnection();
+ task.Result.Dispose();
+ }
+ else
+ {
+ SqlClientEventSource.Metrics.EnterNonPooledConnection();
+ }
}
}
-
- protected override DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory)
- {
- Debug.Assert(internalConnection != null, "internalConnection may not be null.");
-
- Stream xmlStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Data.SqlClient.SqlMetaData.xml");
- cacheMetaDataFactory = true;
-
- Debug.Assert(xmlStream != null, nameof(xmlStream) + " may not be null.");
-
- return new SqlMetaDataFactory(xmlStream,
- internalConnection.ServerVersion,
- internalConnection.ServerVersion);
- }
-
+
#if NET
private void Unload(object sender, EventArgs e)
{
try
{
- Unload();
+ _pruningTimer.Dispose();
}
finally
{
@@ -359,6 +977,8 @@ private void SubscribeToAssemblyLoadContextUnload()
SqlConnectionFactoryAssemblyLoadContext_Unloading;
}
#endif
+
+ #endregion
}
}
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 6c828b188b..34b9070946 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
@@ -18,7 +18,6 @@ internal static class ConnectionPoolHelper
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");
- private static Type s_dbConnectionFactory = s_MicrosoftDotData.GetType("Microsoft.Data.ProviderBase.DbConnectionFactory");
private static Type s_sqlConnectionFactory = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.SqlConnectionFactory");
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);
@@ -26,9 +25,9 @@ internal static class ConnectionPoolHelper
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);
+ private static PropertyInfo s_sqlConnectionFactorySingleton = s_sqlConnectionFactory.GetProperty("Instance", BindingFlags.Static | BindingFlags.NonPublic);
+ private static FieldInfo s_dbConnectionFactoryPoolGroupList = s_sqlConnectionFactory.GetField("_connectionPoolGroups", BindingFlags.Instance | BindingFlags.NonPublic);
private static FieldInfo s_dbConnectionPoolGroupPoolCollection = s_dbConnectionPoolGroup.GetField("_poolCollection", BindingFlags.Instance | BindingFlags.NonPublic);
- private static FieldInfo s_sqlConnectionFactorySingleton = s_sqlConnectionFactory.GetField("SingletonInstance", BindingFlags.Static | BindingFlags.Public);
private static FieldInfo s_dbConnectionPoolStackOld = s_waitHandleDbConnectionPool.GetField("_stackOld", BindingFlags.Instance | BindingFlags.NonPublic);
private static FieldInfo s_dbConnectionPoolStackNew = s_waitHandleDbConnectionPool.GetField("_stackNew", BindingFlags.Instance | BindingFlags.NonPublic);
private static MethodInfo s_dbConnectionPoolCleanup = s_waitHandleDbConnectionPool.GetMethod("CleanupCallback", BindingFlags.Instance | BindingFlags.NonPublic);