diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs deleted file mode 100644 index 065035113d..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs +++ /dev/null @@ -1,477 +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.Collections.Generic; -using System.Diagnostics; -using System.Data.Common; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Data.Common; -using System; -using Microsoft.Data.SqlClient; - -namespace Microsoft.Data.ProviderBase -{ - internal abstract partial 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(); - } - - - abstract public 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 virtual DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) - { - return null; - } - - - 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.Log.HardConnectRequest(); - newConnection.MakeNonPooledObject(owningConnection); - } - SqlClientEventSource.Log.TryTraceEvent(" {0}, Non-pooled database connection created.", ObjectID); - return newConnection; - } - - internal DbConnectionInternal CreatePooledConnection(DbConnectionPool 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.Log.HardConnectRequest(); - newConnection.MakePooledConnection(pool); - } - SqlClientEventSource.Log.TryTraceEvent(" {0}, Pooled database connection created.", ObjectID); - return newConnection; - } - - virtual internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions) - { - return null; - } - - 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)); - } - - private DbConnectionPool 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); - } - DbConnectionPool 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); - } - } - - // We don't support connection pooling on Win9x - 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.Log.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; - } - - - 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) - { - DbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray(); - foreach (DbConnectionPool pool in poolsToRelease) - { - if (pool != null) - { - pool.Clear(); - - if (0 == pool.Count) - { - _poolsToRelease.Remove(pool); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePool={1}", ObjectID, pool.ObjectID); - SqlClientEventSource.Log.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.Log.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(DbConnectionPool 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.Log.EnterInactiveConnectionPool(); - SqlClientEventSource.Log.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.Log.EnterInactiveConnectionPoolGroup(); - SqlClientEventSource.Log.ExitActiveConnectionPoolGroup(); - } - - virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) - { - return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection); - } - - 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; - } - - 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(); - } - - abstract protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection); - - 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 conne); - - 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/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 494bd0b96c..b9f6de4113 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -41,12 +41,6 @@ Common\CoreLib\System\Threading\Tasks\TaskToApm.cs - - Common\Microsoft\Data\ProviderBase\DbConnectionClosed.cs - - - Common\Microsoft\Data\ProviderBase\DbConnectionFactory.cs - Common\Microsoft\Data\ProviderBase\DbConnectionInternal.cs @@ -111,6 +105,12 @@ Microsoft\Data\OperationAbortedException.cs + + Microsoft\Data\ProviderBase\DbConnectionClosed.cs + + + Microsoft\Data\ProviderBase\DbConnectionFactory.cs + Microsoft\Data\ProviderBase\DbConnectionPoolAuthenticationContext.cs @@ -348,6 +348,9 @@ Microsoft\Data\SqlClient\SqlAuthenticationProvider.cs + + Microsoft\Data\SqlClient\SqlAuthenticationProviderManager.cs + Microsoft\Data\SqlClient\SqlAuthenticationToken.cs @@ -633,8 +636,6 @@ - - @@ -660,8 +661,6 @@ - - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs deleted file mode 100644 index 89663d2f90..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ /dev/null @@ -1,16 +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.Transactions; -using Microsoft.Data.Common; - -namespace Microsoft.Data.ProviderBase -{ - internal abstract partial class DbConnectionClosed : DbConnectionInternal - { - protected override void Activate(Transaction transaction) => throw ADP.ClosedConnectionError(); - - public override void EnlistTransaction(Transaction transaction) => throw ADP.ClosedConnectionError(); - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs deleted file mode 100644 index ba14e4ffb7..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs +++ /dev/null @@ -1,220 +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.Data; -using System.Data.Common; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using System.Transactions; -using Microsoft.Data.Common; -using Microsoft.Data.SqlClient; - -namespace Microsoft.Data.ProviderBase -{ - internal abstract partial class DbConnectionFactory - { - private static readonly Action, object> s_tryGetConnectionCompletedContinuation = TryGetConnectionCompletedContinuation; - - internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSource retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection) - { - Debug.Assert(owningConnection != null, "null owningConnection?"); - - DbConnectionPoolGroup poolGroup; - DbConnectionPool 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: s_tryGetConnectionCompletedContinuation, - state: Tuple.Create(cancellationTokenSource, retry), - scheduler: TaskScheduler.Default - ); - - return false; - } - - connection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions); - SqlClientEventSource.Log.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( - (_) => - { - Transaction originalTransaction = ADP.GetCurrentTransaction(); - try - { - ADP.SetCurrentTransaction(retry.Task.AsyncState as 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 static 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.Log.EnterNonPooledConnection(); - } - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetCoreApp.cs deleted file mode 100644 index 3775daf9ad..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.NetCoreApp.cs +++ /dev/null @@ -1,197 +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.Collections.Generic; -using System.Configuration; - -namespace Microsoft.Data.SqlClient -{ - internal partial class SqlAuthenticationProviderManager - { - private readonly SqlAuthenticationInitializer _initializer; - - static SqlAuthenticationProviderManager() - { - SqlAuthenticationProviderConfigurationSection configurationSection = null; - - try - { - // New configuration section "SqlClientAuthenticationProviders" for Microsoft.Data.SqlClient accepted to avoid conflicts with older one. - configurationSection = FetchConfigurationSection(SqlClientAuthenticationProviderConfigurationSection.Name); - if (configurationSection == null) - { - // If configuration section is not yet found, try with old Configuration Section name for backwards compatibility - configurationSection = FetchConfigurationSection(SqlAuthenticationProviderConfigurationSection.Name); - } - } - catch (ConfigurationErrorsException e) - { - // Don't throw an error for invalid config files - SqlClientEventSource.Log.TryTraceEvent("static SqlAuthenticationProviderManager: Unable to load custom SqlAuthenticationProviders or SqlClientAuthenticationProviders. ConfigurationManager failed to load due to configuration errors: {0}", e); - } - - Instance = new SqlAuthenticationProviderManager(configurationSection); - SetDefaultAuthProviders(Instance); - } - - /// - /// Constructor. - /// - public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSection configSection = null) - { - var methodName = "Ctor"; - _providers = new ConcurrentDictionary(); - var authenticationsWithAppSpecifiedProvider = new HashSet(); - _authenticationsWithAppSpecifiedProvider = authenticationsWithAppSpecifiedProvider; - - if (configSection == null) - { - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "Neither SqlClientAuthenticationProviders nor SqlAuthenticationProviders configuration section found."); - return; - } - - if (!string.IsNullOrEmpty(configSection.ApplicationClientId)) - { - _applicationClientId = configSection.ApplicationClientId; - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "Received user-defined Application Client Id"); - } - else - { - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "No user-defined Application Client Id found."); - } - - // Create user-defined auth initializer, if any. - if (!string.IsNullOrEmpty(configSection.InitializerType)) - { - try - { - var initializerType = Type.GetType(configSection.InitializerType, true); - _initializer = (SqlAuthenticationInitializer)Activator.CreateInstance(initializerType); - _initializer.Initialize(); - } - catch (Exception e) - { - throw SQL.CannotCreateSqlAuthInitializer(configSection.InitializerType, e); - } - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "Created user-defined SqlAuthenticationInitializer."); - } - else - { - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "No user-defined SqlAuthenticationInitializer found."); - } - - // add user-defined providers, if any. - if (configSection.Providers != null && configSection.Providers.Count > 0) - { - foreach (ProviderSettings providerSettings in configSection.Providers) - { - SqlAuthenticationMethod authentication = AuthenticationEnumFromString(providerSettings.Name); - SqlAuthenticationProvider provider; - try - { - var providerType = Type.GetType(providerSettings.Type, true); - provider = (SqlAuthenticationProvider)Activator.CreateInstance(providerType); - } - catch (Exception e) - { - throw SQL.CannotCreateAuthProvider(authentication.ToString(), providerSettings.Type, e); - } - if (!provider.IsSupported(authentication)) - { - throw SQL.UnsupportedAuthenticationByProvider(authentication.ToString(), providerSettings.Type); - } - - _providers[authentication] = provider; - authenticationsWithAppSpecifiedProvider.Add(authentication); - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, string.Format("Added user-defined auth provider: {0} for authentication {1}.", providerSettings?.Type, authentication)); - } - } - else - { - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "No user-defined auth providers."); - } - } - - private static T FetchConfigurationSection(string name) - { - Type t = typeof(T); - object section = ConfigurationManager.GetSection(name); - if (section != null) - { - if (section is ConfigurationSection configSection && configSection.GetType() == t) - { - return (T)section; - } - else - { - SqlClientEventSource.Log.TraceEvent("Found a custom {0} configuration but it is not of type {1}.", name, t.FullName); - } - } - return default; - } - - private static SqlAuthenticationMethod AuthenticationEnumFromString(string authentication) - { - switch (authentication.ToLowerInvariant()) - { - case ActiveDirectoryIntegrated: - return SqlAuthenticationMethod.ActiveDirectoryIntegrated; - case ActiveDirectoryPassword: - return SqlAuthenticationMethod.ActiveDirectoryPassword; - case ActiveDirectoryInteractive: - return SqlAuthenticationMethod.ActiveDirectoryInteractive; - case ActiveDirectoryServicePrincipal: - return SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; - case ActiveDirectoryDeviceCodeFlow: - return SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; - case ActiveDirectoryManagedIdentity: - return SqlAuthenticationMethod.ActiveDirectoryManagedIdentity; - case ActiveDirectoryMSI: - return SqlAuthenticationMethod.ActiveDirectoryMSI; - case ActiveDirectoryDefault: - return SqlAuthenticationMethod.ActiveDirectoryDefault; - case ActiveDirectoryWorkloadIdentity: - return SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; - default: - throw SQL.UnsupportedAuthentication(authentication); - } - } - - /// - /// The configuration section definition for reading app.config. - /// - internal class SqlAuthenticationProviderConfigurationSection : ConfigurationSection - { - public const string Name = "SqlAuthenticationProviders"; - - /// - /// User-defined auth providers. - /// - [ConfigurationProperty("providers")] - public ProviderSettingsCollection Providers => (ProviderSettingsCollection)this["providers"]; - - /// - /// User-defined initializer. - /// - [ConfigurationProperty("initializerType")] - public string InitializerType => this["initializerType"] as string; - - /// - /// Application Client Id - /// - [ConfigurationProperty("applicationClientId", IsRequired = false)] - public string ApplicationClientId => this["applicationClientId"] as string; - } - - /// - /// The configuration section definition for reading app.config. - /// - internal class SqlClientAuthenticationProviderConfigurationSection : SqlAuthenticationProviderConfigurationSection - { - public new const string Name = "SqlClientAuthenticationProviders"; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs deleted file mode 100644 index 0cb7a9b54c..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ /dev/null @@ -1,127 +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.Collections.Concurrent; -using System.Collections.Generic; - -namespace Microsoft.Data.SqlClient -{ - /// - /// Authentication provider manager. - /// - internal partial class SqlAuthenticationProviderManager - { - private const string ActiveDirectoryPassword = "active directory password"; - private const string ActiveDirectoryIntegrated = "active directory integrated"; - private const string ActiveDirectoryInteractive = "active directory interactive"; - private const string ActiveDirectoryServicePrincipal = "active directory service principal"; - private const string ActiveDirectoryDeviceCodeFlow = "active directory device code flow"; - private const string ActiveDirectoryManagedIdentity = "active directory managed identity"; - private const string ActiveDirectoryMSI = "active directory msi"; - private const string ActiveDirectoryDefault = "active directory default"; - private const string ActiveDirectoryWorkloadIdentity = "active directory workload identity"; - - private readonly IReadOnlyCollection _authenticationsWithAppSpecifiedProvider; - private readonly ConcurrentDictionary _providers; - private readonly SqlClientLogger _sqlAuthLogger = new SqlClientLogger(); - private readonly string _applicationClientId = ActiveDirectoryAuthentication.AdoClientId; - - public static readonly SqlAuthenticationProviderManager Instance; - - /// - /// Sets default supported Active Directory Authentication providers by the driver - /// on the SqlAuthenticationProviderManager instance. - /// - private static void SetDefaultAuthProviders(SqlAuthenticationProviderManager instance) - { - if (instance != null) - { - var activeDirectoryAuthProvider = new ActiveDirectoryAuthenticationProvider(instance._applicationClientId); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, activeDirectoryAuthProvider); - } - } - /// - /// Constructor. - /// - public SqlAuthenticationProviderManager() - { - _providers = new ConcurrentDictionary(); - _authenticationsWithAppSpecifiedProvider = new HashSet(); - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), "Ctor", "No SqlAuthProviders configuration section found."); - } - - /// - /// Get an authentication provider by method. - /// - /// Authentication method. - /// Authentication provider or null if not found. - public SqlAuthenticationProvider GetProvider(SqlAuthenticationMethod authenticationMethod) - { - SqlAuthenticationProvider value; - return _providers.TryGetValue(authenticationMethod, out value) ? value : null; - } - - /// - /// Set an authentication provider by method. - /// - /// Authentication method. - /// Authentication provider. - /// True if succeeded, false otherwise, e.g., the existing provider disallows overriding. - public bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider) - { - if (!provider.IsSupported(authenticationMethod)) - { - throw SQL.UnsupportedAuthenticationByProvider(authenticationMethod.ToString(), provider.GetType().Name); - } - var methodName = "SetProvider"; - if (_authenticationsWithAppSpecifiedProvider.Count > 0) - { - foreach (SqlAuthenticationMethod candidateMethod in _authenticationsWithAppSpecifiedProvider) - { - if (candidateMethod == authenticationMethod) - { - _sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(_providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); - break; - } - } - } - _providers.AddOrUpdate(authenticationMethod, provider, (key, oldProvider) => - { - if (oldProvider != null) - { - oldProvider.BeforeUnload(authenticationMethod); - } - if (provider != null) - { - provider.BeforeLoad(authenticationMethod); - } - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, $"Added auth provider {GetProviderType(provider)}, overriding existed provider {GetProviderType(oldProvider)} for authentication {authenticationMethod}."); - return provider; - }); - return true; - } - - private static string GetProviderType(SqlAuthenticationProvider provider) - { - if (provider == null) - return "null"; - return provider.GetType().FullName; - } - } - - /// - public abstract class SqlAuthenticationInitializer - { - /// - public abstract void Initialize(); - } -} 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 2a15e3245a..730a747261 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -135,6 +135,12 @@ Microsoft\Data\OperationAbortedException.cs + + Microsoft\Data\ProviderBase\DbConnectionClosed.cs + + + Microsoft\Data\ProviderBase\DbConnectionFactory.cs + Microsoft\Data\ProviderBase\DbConnectionPoolAuthenticationContext.cs @@ -399,6 +405,9 @@ Microsoft\Data\SqlClient\SqlAuthenticationProvider.cs + + Microsoft\Data\SqlClient\SqlAuthenticationProviderManager.cs + Microsoft\Data\SqlClient\SqlBuffer.cs @@ -668,8 +677,6 @@ - - @@ -688,7 +695,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs deleted file mode 100644 index 3ec932cdc0..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ /dev/null @@ -1,180 +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. - -namespace Microsoft.Data.ProviderBase -{ - using System.Data; - using System.Data.Common; - using System.Diagnostics; - using System.Threading.Tasks; - using Microsoft.Data.Common; - using System.Transactions; - - abstract internal class DbConnectionClosed : DbConnectionInternal - { - // Construct an "empty" connection - protected DbConnectionClosed(ConnectionState state, bool hidePassword, bool allowSetConnectionString) : base(state, hidePassword, allowSetConnectionString) - { - } - - override public string ServerVersion - { - get - { - throw ADP.ClosedConnectionError(); - } - } - - override protected void Activate(Transaction transaction) - { - throw ADP.ClosedConnectionError(); - } - - override public DbTransaction BeginTransaction(System.Data.IsolationLevel il) - { - throw ADP.ClosedConnectionError(); - } - - override public void ChangeDatabase(string database) - { - throw ADP.ClosedConnectionError(); - } - - internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) - { - // not much to do here... - } - - override protected void Deactivate() - { - throw ADP.ClosedConnectionError(); - } - - override public void EnlistTransaction(Transaction transaction) - { - throw ADP.ClosedConnectionError(); - } - - override protected internal DataTable GetSchema(DbConnectionFactory 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) - { - return base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions); - } - } - - abstract internal class DbConnectionBusy : DbConnectionClosed - { - - protected DbConnectionBusy(ConnectionState state) : base(state, true, false) - { - } - - internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - throw ADP.ConnectionAlreadyOpen(State); - } - } - - sealed internal class DbConnectionClosedBusy : DbConnectionBusy - { - // Closed Connection, Currently Busy - changing connection string - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedBusy(); // singleton object - - private DbConnectionClosedBusy() : base(ConnectionState.Closed) - { - } - } - - sealed internal class DbConnectionOpenBusy : DbConnectionBusy - { - // Open Connection, Currently Busy - closing connection - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionOpenBusy(); // singleton object - - private DbConnectionOpenBusy() : base(ConnectionState.Open) - { - } - } - - sealed internal class DbConnectionClosedConnecting : DbConnectionBusy - { - // Closed Connection, Currently Connecting - - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedConnecting(); // singleton object - - private DbConnectionClosedConnecting() : base(ConnectionState.Connecting) - { - } - - internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) - { - connectionFactory.SetInnerConnectionTo(owningObject, DbConnectionClosedPreviouslyOpened.SingletonInstance); - } - - internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - return TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); - } - - internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - - if (retry == null || !retry.Task.IsCompleted) - { - // retry is null if this is a synchronous call - - // if someone calls Open or OpenAsync while in this state, - // then the retry task will not be completed - - throw ADP.ConnectionAlreadyOpen(State); - } - - // we are completing an asynchronous open - Debug.Assert(retry.Task.Status == TaskStatus.RanToCompletion, "retry task must be completed successfully"); - DbConnectionInternal openConnection = retry.Task.Result; - if (openConnection == null) - { - connectionFactory.SetInnerConnectionTo(outerConnection, this); - throw ADP.InternalConnectionError(ADP.ConnectionError.GetConnectionReturnsNull); - } - connectionFactory.SetInnerConnectionEvent(outerConnection, openConnection); - - return true; - } - } - - sealed internal class DbConnectionClosedNeverOpened : DbConnectionClosed - { - // Closed Connection, Has Never Been Opened - - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedNeverOpened(); // singleton object - - private DbConnectionClosedNeverOpened() : base(ConnectionState.Closed, false, true) - { - } - } - - sealed internal class DbConnectionClosedPreviouslyOpened : DbConnectionClosed - { - // Closed Connection, Has Previously Been Opened - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedPreviouslyOpened(); // singleton object - - private DbConnectionClosedPreviouslyOpened() : base(ConnectionState.Closed, true, true) - { - } - - internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - return TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs similarity index 89% rename from src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs index a8d9dcc13f..b25f9a2d3b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs @@ -10,7 +10,7 @@ namespace Microsoft.Data.ProviderBase { - abstract internal partial class DbConnectionClosed : DbConnectionInternal + internal abstract class DbConnectionClosed : DbConnectionInternal { // Construct an "empty" connection protected DbConnectionClosed(ConnectionState state, bool hidePassword, bool allowSetConnectionString) : base(state, hidePassword, allowSetConnectionString) @@ -19,6 +19,8 @@ protected DbConnectionClosed(ConnectionState state, bool hidePassword, bool allo public override string ServerVersion => throw ADP.ClosedConnectionError(); + protected override void Activate(System.Transactions.Transaction transaction) => throw ADP.ClosedConnectionError(); + public override DbTransaction BeginTransaction(IsolationLevel il) => throw ADP.ClosedConnectionError(); public override void ChangeDatabase(string database) => throw ADP.ClosedConnectionError(); @@ -30,6 +32,8 @@ internal override void CloseConnection(DbConnection owningObject, DbConnectionFa protected override void Deactivate() => ADP.ClosedConnectionError(); + 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(); @@ -39,7 +43,7 @@ internal override bool TryOpenConnection(DbConnection outerConnection, DbConnect => base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions); } - abstract internal class DbConnectionBusy : DbConnectionClosed + internal abstract class DbConnectionBusy : DbConnectionClosed { protected DbConnectionBusy(ConnectionState state) : base(state, true, false) { @@ -49,7 +53,7 @@ internal override bool TryOpenConnection(DbConnection outerConnection, DbConnect => throw ADP.ConnectionAlreadyOpen(State); } - sealed internal class DbConnectionClosedBusy : DbConnectionBusy + internal sealed class DbConnectionClosedBusy : DbConnectionBusy { // Closed Connection, Currently Busy - changing connection string internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedBusy(); // singleton object @@ -59,7 +63,7 @@ private DbConnectionClosedBusy() : base(ConnectionState.Closed) } } - sealed internal class DbConnectionOpenBusy : DbConnectionBusy + internal sealed class DbConnectionOpenBusy : DbConnectionBusy { // Open Connection, Currently Busy - closing connection internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionOpenBusy(); // singleton object @@ -69,10 +73,9 @@ private DbConnectionOpenBusy() : base(ConnectionState.Open) } } - sealed internal class DbConnectionClosedConnecting : DbConnectionBusy + internal sealed class DbConnectionClosedConnecting : DbConnectionBusy { // Closed Connection, Currently Connecting - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedConnecting(); // singleton object private DbConnectionClosedConnecting() : base(ConnectionState.Connecting) @@ -113,10 +116,9 @@ internal override bool TryOpenConnection(DbConnection outerConnection, DbConnect } } - sealed internal class DbConnectionClosedNeverOpened : DbConnectionClosed + internal sealed class DbConnectionClosedNeverOpened : DbConnectionClosed { // Closed Connection, Has Never Been Opened - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedNeverOpened(); // singleton object private DbConnectionClosedNeverOpened() : base(ConnectionState.Closed, false, true) @@ -124,10 +126,9 @@ private DbConnectionClosedNeverOpened() : base(ConnectionState.Closed, false, tr } } - sealed internal class DbConnectionClosedPreviouslyOpened : DbConnectionClosed + internal sealed class DbConnectionClosedPreviouslyOpened : DbConnectionClosed { // Closed Connection, Has Previously Been Opened - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedPreviouslyOpened(); // singleton object private DbConnectionClosedPreviouslyOpened() : base(ConnectionState.Closed, true, true) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs similarity index 79% rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs index d310981a4d..0fdd85c4bf 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs @@ -2,37 +2,39 @@ // 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.SqlClient; + namespace Microsoft.Data.ProviderBase { - 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.SqlClient; - internal abstract class DbConnectionFactory { private Dictionary _connectionPoolGroups; private readonly List _poolsToRelease; private readonly List _poolGroupsToRelease; - private readonly DbConnectionPoolCounters _performanceCounters; 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 readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); + 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. - static int s_pendingOpenNonPooledNext = 0; - static Task[] s_pendingOpenNonPooled = new Task[Environment.ProcessorCount]; - static Task s_completedTask; + private static uint s_pendingOpenNonPooledNext = 0; + private static Task[] s_pendingOpenNonPooled = new Task[Environment.ProcessorCount]; + private static Task s_completedTask; + +#if NETFRAMEWORK + private readonly DbConnectionPoolCounters _performanceCounters; protected DbConnectionFactory() : this(DbConnectionPoolCountersNoCounters.SingletonInstance) { } @@ -49,18 +51,19 @@ internal DbConnectionPoolCounters PerformanceCounters { get { return _performanceCounters; } } - - abstract public DbProviderFactory ProviderFactory +#else + protected DbConnectionFactory() { - get; + _connectionPoolGroups = new Dictionary(); + _poolsToRelease = new List(); + _poolGroupsToRelease = new List(); + _pruningTimer = CreatePruningTimer(); } +#endif - internal int ObjectID + public abstract DbProviderFactory ProviderFactory { - get - { - return _objectID; - } + get; } public void ClearAllPools() @@ -77,12 +80,11 @@ public void ClearAllPools() } } } - } public void ClearPool(DbConnection connection) { - ADP.CheckArgumentNull(connection, "connection"); + ADP.CheckArgumentNull(connection, nameof(connection)); using (TryEventScope.Create(" {0}", GetObjectId(connection))) { DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup(connection); @@ -96,12 +98,11 @@ public void ClearPool(DbConnection connection) public void ClearPool(DbConnectionPoolKey key) { Debug.Assert(key != null, "key cannot be null"); - ADP.CheckArgumentNull(key.ConnectionString, "key.ConnectionString"); + ADP.CheckArgumentNull(key.ConnectionString, $"{nameof(key)}.{nameof(key.ConnectionString)}"); using (TryEventScope.Create(" connectionString")) { - DbConnectionPoolGroup poolGroup; Dictionary connectionPoolGroups = _connectionPoolGroups; - if (connectionPoolGroups.TryGetValue(key, out poolGroup)) + if (connectionPoolGroups.TryGetValue(key, out DbConnectionPoolGroup poolGroup)) { poolGroup.Clear(); } @@ -113,7 +114,7 @@ internal virtual DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(D return null; } - virtual protected DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory) + 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. @@ -133,8 +134,13 @@ internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConne DbConnectionInternal newConnection = CreateConnection(connectionOptions, poolKey, poolGroupProviderInfo, null, owningConnection, userOptions); if (newConnection != null) { +#if NETFRAMEWORK PerformanceCounters.HardConnectsPerSecond.Increment(); newConnection.MakeNonPooledObject(owningConnection, PerformanceCounters); +#else + SqlClientEventSource.Log.HardConnectRequest(); + newConnection.MakeNonPooledObject(owningConnection); +#endif } SqlClientEventSource.Log.TryTraceEvent(" {0}, Non-pooled database connection created.", ObjectID); return newConnection; @@ -144,33 +150,37 @@ internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbCo { Debug.Assert(pool != null, "null pool?"); DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool.PoolGroup.ProviderInfo; - DbConnectionInternal newConnection = CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningObject, userOptions); if (newConnection != null) { +#if NETFRAMEWORK PerformanceCounters.HardConnectsPerSecond.Increment(); +#else + SqlClientEventSource.Log.HardConnectRequest(); +#endif newConnection.MakePooledConnection(pool); } SqlClientEventSource.Log.TryTraceEvent(" {0}, Pooled database connection created.", ObjectID); return newConnection; } - virtual internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions) + internal virtual DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions) { return null; } - private Timer CreatePruningTimer() - { - TimerCallback callback = new TimerCallback(PruneConnectionPoolGroups); - return new Timer(callback, null, PruningDueTime, PruningPeriod); - } + 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 (!ADP.IsEmpty(key.ConnectionString)) + if (!string.IsNullOrEmpty(key.ConnectionString)) { DbConnectionPoolGroup connectionPoolGroup; Dictionary connectionPoolGroups = _connectionPoolGroups; @@ -182,16 +192,10 @@ protected DbConnectionOptions FindConnectionOptions(DbConnectionPoolKey key) return null; } - // GetCompletedTask must be called from within s_pendingOpenPooled lock - static Task GetCompletedTask() + private static Task GetCompletedTask() { - if (s_completedTask == null) - { - TaskCompletionSource source = new TaskCompletionSource(); - source.SetResult(null); - s_completedTask = source.Task; - } - return s_completedTask; + 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) @@ -202,14 +206,13 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour DbConnectionPool connectionPool; connection = null; - // SQLBU 431251: - // Work around race condition with clearing the pool between GetConnectionPool obtaining pool + // 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. - // VSDD 674236: use retriesLeft to prevent CPU spikes with incremental sleep + // 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; @@ -233,7 +236,6 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour 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++) @@ -250,36 +252,19 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour } } - // if didn't find one, pick the next one in round-robbin fashion + // if didn't find one, pick the next one in round-robin fashion if (idx == s_pendingOpenNonPooled.Length) { - idx = s_pendingOpenNonPooledNext++ % 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. - // BUG? : If we have timed out task on top of running task, then new task could be started - // on top of that, since we are only checking the top task. This will lead to starting more threads - // than intended. - newTask = s_pendingOpenNonPooled[idx].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); + 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; @@ -293,39 +278,21 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour } // once the task is done, propagate the final results to the original caller - newTask.ContinueWith((task) => - { - cancellationTokenSource.Dispose(); - if (task.IsCanceled) - { - retry.TrySetException(ADP.ExceptionWithStackTrace(ADP.NonPooledOpenTimeout())); - } - else if (task.IsFaulted) - { - retry.TrySetException(task.Exception.InnerException); - } - else - { - if (retry.TrySetResult(task.Result)) - { - PerformanceCounters.NumberOfNonPooledConnections.Increment(); - } - else - { - // 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(); - } - } - }, TaskScheduler.Default); + newTask.ContinueWith( + continuationAction: TryGetConnectionCompletedContinuation, + state: Tuple.Create(cancellationTokenSource, retry), + scheduler: TaskScheduler.Default + ); return false; } connection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions); +#if NETFRAMEWORK PerformanceCounters.NumberOfNonPooledConnections.Increment(); +#else + SqlClientEventSource.Log.EnterNonPooledConnection(); +#endif } else { @@ -348,7 +315,6 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour 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(); } @@ -367,7 +333,6 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour 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(); } @@ -375,6 +340,71 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour 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 + { +#if NETFRAMEWORK + PerformanceCounters.NumberOfNonPooledConnections.Increment(); +#else + SqlClientEventSource.Log.EnterNonPooledConnection(); +#endif + } + } + } + private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup) { // if poolgroup is disabled, it will be replaced with a new entry @@ -393,7 +423,7 @@ private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnecti // 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); + SqlClientEventSource.Log.TryTraceEvent(" {0}, DisabledPoolGroup={1}", ObjectID, connectionPoolGroup?.ObjectID); // reusing existing pool option in case user originally used SetConnectionPoolOptions DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup.PoolGroupOptions; @@ -412,7 +442,7 @@ private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnecti internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions) { - if (ADP.IsEmpty(key.ConnectionString)) + if (string.IsNullOrEmpty(key.ConnectionString)) { return (DbConnectionPoolGroup)null; } @@ -431,12 +461,12 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, D throw ADP.InternalConnectionError(ADP.ConnectionError.ConnectionOptionsMissing); } - string expandedConnectionString = key.ConnectionString; if (userConnectionOptions == null) - { // we only allow one expansion on the connection string + { + // we only allow one expansion on the connection string userConnectionOptions = connectionOptions; - expandedConnectionString = connectionOptions.Expand(); + 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) @@ -448,8 +478,7 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, D } } - // We don't support connection pooling on Win9x; it lacks too many of the APIs we require. - if (poolOptions == null && ADP.s_isWindowsNT) + if (poolOptions == null) { if (connectionPoolGroup != null) { @@ -480,7 +509,11 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, D // lock prevents race condition with PruneConnectionPoolGroups newConnectionPoolGroups.Add(key, newConnectionPoolGroup); +#if NETFRAMEWORK PerformanceCounters.NumberOfActiveConnectionPoolGroups.Increment(); +#else + SqlClientEventSource.Log.EnterActiveConnectionPoolGroup(); +#endif connectionPoolGroup = newConnectionPoolGroup; _connectionPoolGroups = newConnectionPoolGroups; } @@ -545,7 +578,11 @@ private void PruneConnectionPoolGroups(object state) { _poolsToRelease.Remove(pool); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePool={1}", ObjectID, pool.ObjectID); +#if NETFRAMEWORK PerformanceCounters.NumberOfInactiveConnectionPools.Decrement(); +#else + SqlClientEventSource.Log.ExitInactiveConnectionPool(); +#endif } } } @@ -570,7 +607,11 @@ private void PruneConnectionPoolGroups(object state) { _poolGroupsToRelease.Remove(poolGroup); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePoolGroup={1}", ObjectID, poolGroup.ObjectID); +#if NETFRAMEWORK PerformanceCounters.NumberOfInactiveConnectionPoolGroups.Decrement(); +#else + SqlClientEventSource.Log.ExitInactiveConnectionPoolGroup(); +#endif } } } @@ -595,8 +636,11 @@ private void PruneConnectionPoolGroups(object state) // 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 + { + // may add entries to _poolsToRelease +#if NETFRAMEWORK PerformanceCounters.NumberOfActiveConnectionPoolGroups.Decrement(); +#endif QueuePoolGroupForRelease(entry.Value); } else @@ -629,7 +673,12 @@ internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing) } _poolsToRelease.Add(pool); } +#if NETFRAMEWORK PerformanceCounters.NumberOfInactiveConnectionPools.Increment(); +#else + SqlClientEventSource.Log.EnterInactiveConnectionPool(); + SqlClientEventSource.Log.ExitActiveConnectionPool(); +#endif } internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) @@ -641,7 +690,12 @@ internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) { _poolGroupsToRelease.Add(poolGroup); } +#if NETFRAMEWORK PerformanceCounters.NumberOfInactiveConnectionPoolGroups.Increment(); +#else + SqlClientEventSource.Log.EnterInactiveConnectionPoolGroup(); + SqlClientEventSource.Log.ExitActiveConnectionPoolGroup(); +#endif } virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) @@ -670,5 +724,10 @@ virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions opti 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/netfx/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs similarity index 75% rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index fff42157fd..e29746dc6c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -3,8 +3,8 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Configuration; namespace Microsoft.Data.SqlClient @@ -12,7 +12,7 @@ namespace Microsoft.Data.SqlClient /// /// Authentication provider manager. /// - internal class SqlAuthenticationProviderManager + internal sealed class SqlAuthenticationProviderManager { private const string ActiveDirectoryPassword = "active directory password"; private const string ActiveDirectoryIntegrated = "active directory integrated"; @@ -27,6 +27,7 @@ internal class SqlAuthenticationProviderManager static SqlAuthenticationProviderManager() { SqlAuthenticationProviderConfigurationSection configurationSection = null; + try { // New configuration section "SqlClientAuthenticationProviders" for Microsoft.Data.SqlClient accepted to avoid conflicts with older one. @@ -42,22 +43,34 @@ static SqlAuthenticationProviderManager() // Don't throw an error for invalid config files SqlClientEventSource.Log.TryTraceEvent("static SqlAuthenticationProviderManager: Unable to load custom SqlAuthenticationProviders or SqlClientAuthenticationProviders. ConfigurationManager failed to load due to configuration errors: {0}", e); } + Instance = new SqlAuthenticationProviderManager(configurationSection); + SetDefaultAuthProviders(Instance); + } - var activeDirectoryAuthProvider = new ActiveDirectoryAuthenticationProvider(Instance._applicationClientId); - Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, activeDirectoryAuthProvider); - Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, activeDirectoryAuthProvider); - Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, activeDirectoryAuthProvider); - Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, activeDirectoryAuthProvider); - Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, activeDirectoryAuthProvider); - Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, activeDirectoryAuthProvider); - Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, activeDirectoryAuthProvider); - Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, activeDirectoryAuthProvider); - Instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, activeDirectoryAuthProvider); + /// + /// Sets default supported Active Directory Authentication providers by the driver + /// on the SqlAuthenticationProviderManager instance. + /// + private static void SetDefaultAuthProviders(SqlAuthenticationProviderManager instance) + { + if (instance != null) + { + var activeDirectoryAuthProvider = new ActiveDirectoryAuthenticationProvider(instance._applicationClientId); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, activeDirectoryAuthProvider); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, activeDirectoryAuthProvider); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, activeDirectoryAuthProvider); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, activeDirectoryAuthProvider); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, activeDirectoryAuthProvider); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, activeDirectoryAuthProvider); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, activeDirectoryAuthProvider); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, activeDirectoryAuthProvider); + instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, activeDirectoryAuthProvider); + } } + public static readonly SqlAuthenticationProviderManager Instance; - private readonly string _typeName; private readonly SqlAuthenticationInitializer _initializer; private readonly IReadOnlyCollection _authenticationsWithAppSpecifiedProvider; private readonly ConcurrentDictionary _providers; @@ -67,9 +80,8 @@ static SqlAuthenticationProviderManager() /// /// Constructor. /// - public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSection configSection) + public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSection configSection = null) { - _typeName = GetType().Name; var methodName = "Ctor"; _providers = new ConcurrentDictionary(); var authenticationsWithAppSpecifiedProvider = new HashSet(); @@ -77,18 +89,18 @@ public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSe if (configSection == null) { - _sqlAuthLogger.LogInfo(_typeName, methodName, "Neither SqlClientAuthenticationProviders nor SqlAuthenticationProviders configuration section found."); + _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "Neither SqlClientAuthenticationProviders nor SqlAuthenticationProviders configuration section found."); return; } if (!string.IsNullOrEmpty(configSection.ApplicationClientId)) { _applicationClientId = configSection.ApplicationClientId; - _sqlAuthLogger.LogInfo(_typeName, methodName, "Received user-defined Application Client Id"); + _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "Received user-defined Application Client Id"); } else { - _sqlAuthLogger.LogInfo(_typeName, methodName, "No user-defined Application Client Id found."); + _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "No user-defined Application Client Id found."); } // Create user-defined auth initializer, if any. @@ -104,15 +116,14 @@ public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSe { throw SQL.CannotCreateSqlAuthInitializer(configSection.InitializerType, e); } - _sqlAuthLogger.LogInfo(_typeName, methodName, "Created user-defined SqlAuthenticationInitializer."); + _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "Created user-defined SqlAuthenticationInitializer."); } else { - _sqlAuthLogger.LogInfo(_typeName, methodName, "No user-defined SqlAuthenticationInitializer found."); + _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "No user-defined SqlAuthenticationInitializer found."); } // add user-defined providers, if any. - // if (configSection.Providers != null && configSection.Providers.Count > 0) { foreach (ProviderSettings providerSettings in configSection.Providers) @@ -129,16 +140,18 @@ public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSe throw SQL.CannotCreateAuthProvider(authentication.ToString(), providerSettings.Type, e); } if (!provider.IsSupported(authentication)) + { throw SQL.UnsupportedAuthenticationByProvider(authentication.ToString(), providerSettings.Type); + } _providers[authentication] = provider; authenticationsWithAppSpecifiedProvider.Add(authentication); - _sqlAuthLogger.LogInfo(_typeName, methodName, string.Format("Added user-defined auth provider: {0} for authentication {1}.", providerSettings?.Type, authentication)); + _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, string.Format("Added user-defined auth provider: {0} for authentication {1}.", providerSettings?.Type, authentication)); } } else { - _sqlAuthLogger.LogInfo(_typeName, methodName, "No user-defined auth providers."); + _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "No user-defined auth providers."); } } @@ -165,16 +178,14 @@ public bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthent { throw SQL.UnsupportedAuthenticationByProvider(authenticationMethod.ToString(), provider.GetType().Name); } - var methodName = "SetProvider"; - if (_authenticationsWithAppSpecifiedProvider.Count > 0) { foreach (SqlAuthenticationMethod candidateMethod in _authenticationsWithAppSpecifiedProvider) { if (candidateMethod == authenticationMethod) { - _sqlAuthLogger.LogError(_typeName, methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(_providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); + _sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(_providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); break; } } @@ -189,7 +200,7 @@ public bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthent { provider.BeforeLoad(authenticationMethod); } - _sqlAuthLogger.LogInfo(_typeName, methodName, $"Added auth provider {GetProviderType(provider)}, overriding existed provider {GetProviderType(oldProvider)} for authentication {authenticationMethod}."); + _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, $"Added auth provider {GetProviderType(provider)}, overriding existed provider {GetProviderType(oldProvider)} for authentication {authenticationMethod}."); return provider; }); return true; @@ -282,10 +293,10 @@ internal class SqlClientAuthenticationProviderConfigurationSection : SqlAuthenti public new const string Name = "SqlClientAuthenticationProviders"; } - /// + /// public abstract class SqlAuthenticationInitializer { - /// + /// public abstract void Initialize(); } }