From 8feb331416d91da15938e24e4518ea3b332414c1 Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Fri, 23 Aug 2024 21:25:13 +0100
Subject: [PATCH] Merged DbConnectionPool
---
.../src/Microsoft.Data.SqlClient.csproj | 4 +-
.../Data/ProviderBase/DbConnectionPool.cs | 1801 -----------------
.../src/Microsoft/Data/SqlClient/TdsParser.cs | 10 +
.../netfx/src/Microsoft.Data.SqlClient.csproj | 4 +-
.../src/Microsoft/Data/Common/AdapterUtil.cs | 6 -
.../Data/ProviderBase/DbConnectionPool.cs | 235 ++-
6 files changed, 161 insertions(+), 1899 deletions(-)
delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs
rename src/Microsoft.Data.SqlClient/{netfx => }/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs (95%)
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..25c61ffbf7 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -111,6 +111,9 @@
Microsoft\Data\OperationAbortedException.cs
+
+ Microsoft\Data\ProviderBase\DbConnectionPool.cs
+
Microsoft\Data\ProviderBase\DbConnectionPoolAuthenticationContext.cs
@@ -636,7 +639,6 @@
-
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs
deleted file mode 100644
index 5694d8c05f..0000000000
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs
+++ /dev/null
@@ -1,1801 +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.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 sealed class DbConnectionPool
- {
- private enum State
- {
- Initializing,
- Running,
- ShuttingDown,
- }
-
- // This class is a way to stash our cloned Tx key for later disposal when it's no longer needed.
- // We can't get at the key in the dictionary without enumerating entries, so we stash an extra
- // copy as part of the value.
- sealed private class TransactedConnectionList : List
- {
- private Transaction _transaction;
- internal TransactedConnectionList(int initialAllocation, Transaction tx) : base(initialAllocation)
- {
- _transaction = tx;
- }
-
- internal void Dispose()
- {
- if (_transaction != null)
- {
- _transaction.Dispose();
- }
- }
- }
-
- private sealed class PendingGetConnection
- {
- public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSource completion, DbConnectionOptions userOptions)
- {
- DueTime = dueTime;
- Owner = owner;
- Completion = completion;
- UserOptions = userOptions;
- }
- public long DueTime { get; private set; }
- public DbConnection Owner { get; private set; }
- public TaskCompletionSource Completion { get; private set; }
- public DbConnectionOptions UserOptions { get; private set; }
- }
-
- sealed private class TransactedConnectionPool
- {
- Dictionary _transactedCxns;
-
- DbConnectionPool _pool;
-
- private static int _objectTypeCount; // EventSource Counter
- internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
-
- internal TransactedConnectionPool(DbConnectionPool pool)
- {
- Debug.Assert(pool != null, "null pool?");
-
- _pool = pool;
- _transactedCxns = new Dictionary();
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed for connection pool {1}", ObjectID, _pool.ObjectID);
- }
-
- internal int ObjectID
- {
- get
- {
- return _objectID;
- }
- }
-
- internal DbConnectionPool Pool
- {
- get
- {
- return _pool;
- }
- }
-
- internal DbConnectionInternal GetTransactedObject(Transaction transaction)
- {
- Debug.Assert(transaction != null, "null transaction?");
-
- DbConnectionInternal transactedObject = null;
-
- TransactedConnectionList connections;
- bool txnFound = false;
-
- lock (_transactedCxns)
- {
- txnFound = _transactedCxns.TryGetValue(transaction, out connections);
- }
-
- // NOTE: GetTransactedObject is only used when AutoEnlist = True and the ambient transaction
- // (Sys.Txns.Txn.Current) is still valid/non-null. This, in turn, means that we don't need
- // to worry about a pending asynchronous TransactionCompletedEvent to trigger processing in
- // TransactionEnded below and potentially wipe out the connections list underneath us. It
- // is similarly alright if a pending addition to the connections list in PutTransactedObject
- // below is not completed prior to the lock on the connections object here...getting a new
- // connection is probably better than unnecessarily locking
- if (txnFound)
- {
- Debug.Assert(connections != null);
-
- // synchronize multi-threaded access with PutTransactedObject (TransactionEnded should
- // not be a concern, see comments above)
- lock (connections)
- {
- int i = connections.Count - 1;
- if (0 <= i)
- {
- transactedObject = connections[i];
- connections.RemoveAt(i);
- }
- }
- }
-
- if (transactedObject != null)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Popped.", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID);
- }
- return transactedObject;
- }
-
- internal void PutTransactedObject(Transaction transaction, DbConnectionInternal transactedObject)
- {
- Debug.Assert(transaction != null, "null transaction?");
- Debug.Assert(transactedObject != null, "null transactedObject?");
-
- TransactedConnectionList connections;
- bool txnFound = false;
-
- // NOTE: because TransactionEnded is an asynchronous notification, there's no guarantee
- // around the order in which PutTransactionObject and TransactionEnded are called.
-
- lock (_transactedCxns)
- {
- // Check if a transacted pool has been created for this transaction
- if (txnFound = _transactedCxns.TryGetValue(transaction, out connections))
- {
- Debug.Assert(connections != null);
-
- // synchronize multi-threaded access with GetTransactedObject
- lock (connections)
- {
- Debug.Assert(0 > connections.IndexOf(transactedObject), "adding to pool a second time?");
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Pushing.", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID);
- connections.Add(transactedObject);
- }
- }
- }
-
- // CONSIDER: the following code is more complicated than it needs to be to avoid cloning the
- // transaction and allocating memory within a lock. Is that complexity really necessary?
- if (!txnFound)
- {
- // create the transacted pool, making sure to clone the associated transaction
- // for use as a key in our internal dictionary of transactions and connections
- Transaction transactionClone = null;
- TransactedConnectionList newConnections = null;
-
- try
- {
- transactionClone = transaction.Clone();
- newConnections = new TransactedConnectionList(2, transactionClone); // start with only two connections in the list; most times we won't need that many.
-
- lock (_transactedCxns)
- {
- // NOTE: in the interim between the locks on the transacted pool (this) during
- // execution of this method, another thread (threadB) may have attempted to
- // add a different connection to the transacted pool under the same
- // transaction. As a result, threadB may have completed creating the
- // transacted pool while threadA was processing the above instructions.
- if (txnFound = _transactedCxns.TryGetValue(transaction, out connections))
- {
- Debug.Assert(connections != null);
-
- // synchronize multi-threaded access with GetTransactedObject
- lock (connections)
- {
- Debug.Assert(0 > connections.IndexOf(transactedObject), "adding to pool a second time?");
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Pushing.", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID);
- connections.Add(transactedObject);
- }
- }
- else
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Adding List to transacted pool.", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID);
-
- // add the connection/transacted object to the list
- newConnections.Add(transactedObject);
-
- _transactedCxns.Add(transactionClone, newConnections);
- transactionClone = null; // we've used it -- don't throw it or the TransactedConnectionList that references it away.
- }
- }
- }
- finally
- {
- if (transactionClone != null)
- {
- if (newConnections != null)
- {
- // another thread created the transaction pool and thus the new
- // TransactedConnectionList was not used, so dispose of it and
- // the transaction clone that it incorporates.
- newConnections.Dispose();
- }
- else
- {
- // memory allocation for newConnections failed...clean up unused transactionClone
- transactionClone.Dispose();
- }
- }
- }
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Added.", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID);
- }
- SqlClientEventSource.Log.EnterFreeConnection();
- }
-
- internal void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Transaction Completed", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID);
- TransactedConnectionList connections;
- int entry = -1;
-
- // NOTE: because TransactionEnded is an asynchronous notification, there's no guarantee
- // around the order in which PutTransactionObject and TransactionEnded are called. As
- // such, it is possible that the transaction does not yet have a pool created.
-
- // TODO: is this a plausible and/or likely scenario? Do we need to have a mechanism to ensure
- // TODO: that the pending creation of a transacted pool for this transaction is aborted when
- // TODO: PutTransactedObject finally gets some CPU time?
-
- lock (_transactedCxns)
- {
- if (_transactedCxns.TryGetValue(transaction, out connections))
- {
- Debug.Assert(connections != null);
-
- bool shouldDisposeConnections = false;
-
- // Lock connections to avoid conflict with GetTransactionObject
- lock (connections)
- {
- entry = connections.IndexOf(transactedObject);
-
- if (entry >= 0)
- {
- connections.RemoveAt(entry);
- }
-
- // Once we've completed all the ended notifications, we can
- // safely remove the list from the transacted pool.
- if (0 >= connections.Count)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Removing List from transacted pool.", ObjectID, transaction.GetHashCode());
- _transactedCxns.Remove(transaction);
-
- // we really need to dispose our connection list; it may have
- // native resources via the tx and GC may not happen soon enough.
- shouldDisposeConnections = true;
- }
- }
-
- if (shouldDisposeConnections)
- {
- connections.Dispose();
- }
- }
- else
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Transacted pool not yet created prior to transaction completing. Connection may be leaked.", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID);
- }
- }
-
- // If (and only if) we found the connection in the list of
- // connections, we'll put it back...
- if (0 <= entry)
- {
- SqlClientEventSource.Log.ExitFreeConnection();
- Pool.PutObjectFromTransactedPool(transactedObject);
- }
- }
-
- }
-
- private sealed class PoolWaitHandles
- {
- private readonly Semaphore _poolSemaphore;
- private readonly ManualResetEvent _errorEvent;
-
- // Using a Mutex requires ThreadAffinity because SQL CLR can swap
- // the underlying Win32 thread associated with a managed thread in preemptive mode.
- // Using an AutoResetEvent does not have that complication.
- private readonly Semaphore _creationSemaphore;
-
- private readonly WaitHandle[] _handlesWithCreate;
- private readonly WaitHandle[] _handlesWithoutCreate;
-
- internal PoolWaitHandles()
- {
- _poolSemaphore = new Semaphore(0, MAX_Q_SIZE);
- _errorEvent = new ManualResetEvent(false);
- _creationSemaphore = new Semaphore(1, 1);
-
- _handlesWithCreate = new WaitHandle[] { _poolSemaphore, _errorEvent, _creationSemaphore };
- _handlesWithoutCreate = new WaitHandle[] { _poolSemaphore, _errorEvent };
- }
-
-
- internal Semaphore CreationSemaphore
- {
- get { return _creationSemaphore; }
- }
-
- internal ManualResetEvent ErrorEvent
- {
- get { return _errorEvent; }
- }
-
- internal Semaphore PoolSemaphore
- {
- get { return _poolSemaphore; }
- }
-
- internal WaitHandle[] GetHandles(bool withCreate)
- {
- return withCreate ? _handlesWithCreate : _handlesWithoutCreate;
- }
- }
-
- private const int MAX_Q_SIZE = (int)0x00100000;
-
- // The order of these is important; we want the WaitAny call to be signaled
- // for a free object before a creation signal. Only the index first signaled
- // object is returned from the WaitAny call.
- private const int SEMAPHORE_HANDLE = (int)0x0;
- private const int ERROR_HANDLE = (int)0x1;
- private const int CREATION_HANDLE = (int)0x2;
- private const int BOGUS_HANDLE = (int)0x3;
-
-
- private const int ERROR_WAIT_DEFAULT = 5 * 1000; // 5 seconds
-
- // we do want a testable, repeatable set of generated random numbers
- private static readonly Random s_random = new Random(5101977); // Value obtained from Dave Driver
-
- private readonly int _cleanupWait;
- private readonly DbConnectionPoolIdentity _identity;
-
- private readonly DbConnectionFactory _connectionFactory;
- private readonly DbConnectionPoolGroup _connectionPoolGroup;
- private readonly DbConnectionPoolGroupOptions _connectionPoolGroupOptions;
- private DbConnectionPoolProviderInfo _connectionPoolProviderInfo;
-
- ///
- /// The private member which carries the set of authenticationcontexts for this pool (based on the user's identity).
- ///
- private readonly ConcurrentDictionary _pooledDbAuthenticationContexts;
-
- private State _state;
-
- private readonly ConcurrentStack _stackOld = new ConcurrentStack();
- private readonly ConcurrentStack _stackNew = new ConcurrentStack();
-
- private readonly ConcurrentQueue _pendingOpens = new ConcurrentQueue();
- private int _pendingOpensWaiting = 0;
-
- private readonly WaitCallback _poolCreateRequest;
-
- private int _waitCount;
- private readonly PoolWaitHandles _waitHandles;
-
- private Exception _resError;
- private volatile bool _errorOccurred;
-
- private int _errorWait;
- private Timer _errorTimer;
-
- private Timer _cleanupTimer;
-
- private readonly TransactedConnectionPool _transactedConnectionPool;
-
- private readonly List _objectList;
- private int _totalObjects;
-
- private static int _objectTypeCount; // EventSource counter
- internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
-
- // only created by DbConnectionPoolGroup.GetConnectionPool
- internal DbConnectionPool(
- DbConnectionFactory connectionFactory,
- DbConnectionPoolGroup connectionPoolGroup,
- DbConnectionPoolIdentity identity,
- DbConnectionPoolProviderInfo connectionPoolProviderInfo)
- {
- Debug.Assert(connectionPoolGroup != null, "null connectionPoolGroup");
-
- if (identity != null && identity.IsRestricted)
- {
- throw ADP.InternalError(ADP.InternalErrorCode.AttemptingToPoolOnRestrictedToken);
- }
-
- _state = State.Initializing;
-
- lock (s_random)
- { // Random.Next is not thread-safe
- _cleanupWait = s_random.Next(12, 24) * 10 * 1000; // 2-4 minutes in 10 sec intervals
- }
-
- _connectionFactory = connectionFactory;
- _connectionPoolGroup = connectionPoolGroup;
- _connectionPoolGroupOptions = connectionPoolGroup.PoolGroupOptions;
- _connectionPoolProviderInfo = connectionPoolProviderInfo;
- _identity = identity;
-
- _waitHandles = new PoolWaitHandles();
-
- _errorWait = ERROR_WAIT_DEFAULT;
- _errorTimer = null; // No error yet.
-
- _objectList = new List(MaxPoolSize);
-
- _pooledDbAuthenticationContexts = new ConcurrentDictionary(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/,
- capacity: 2);
-
- _transactedConnectionPool = new TransactedConnectionPool(this); // initialize irrespective of platform
-
- _poolCreateRequest = new WaitCallback(PoolCreateRequest); // used by CleanupCallback
- _state = State.Running;
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed.", ObjectID);
-
- //_cleanupTimer & QueuePoolCreateRequest is delayed until DbConnectionPoolGroup calls
- // StartBackgroundCallbacks after pool is actually in the collection
- }
-
- private int CreationTimeout
- {
- get { return PoolGroupOptions.CreationTimeout; }
- }
-
- internal int Count
- {
- get { return _totalObjects; }
- }
-
- internal DbConnectionFactory ConnectionFactory
- {
- get { return _connectionFactory; }
- }
-
- internal bool ErrorOccurred
- {
- get { return _errorOccurred; }
- }
-
- private bool HasTransactionAffinity
- {
- get { return PoolGroupOptions.HasTransactionAffinity; }
- }
-
- internal TimeSpan LoadBalanceTimeout
- {
- get { return PoolGroupOptions.LoadBalanceTimeout; }
- }
-
- private bool NeedToReplenish
- {
- get
- {
- if (State.Running != _state) // Don't allow connection create when not running.
- return false;
-
- int totalObjects = Count;
-
- if (totalObjects >= MaxPoolSize)
- return false;
-
- if (totalObjects < MinPoolSize)
- return true;
-
- int freeObjects = (_stackNew.Count + _stackOld.Count);
- int waitingRequests = _waitCount;
- bool needToReplenish = (freeObjects < waitingRequests) || ((freeObjects == waitingRequests) && (totalObjects > 1));
-
- return needToReplenish;
- }
- }
-
- internal DbConnectionPoolIdentity Identity
- {
- get { return _identity; }
- }
-
- internal bool IsRunning
- {
- get { return State.Running == _state; }
- }
-
- private int MaxPoolSize
- {
- get { return PoolGroupOptions.MaxPoolSize; }
- }
-
- private int MinPoolSize
- {
- get { return PoolGroupOptions.MinPoolSize; }
- }
-
- internal int ObjectID
- {
- get
- {
- return _objectID;
- }
- }
-
- internal DbConnectionPoolGroup PoolGroup
- {
- get { return _connectionPoolGroup; }
- }
-
- internal DbConnectionPoolGroupOptions PoolGroupOptions
- {
- get { return _connectionPoolGroupOptions; }
- }
-
- internal DbConnectionPoolProviderInfo ProviderInfo
- {
- get { return _connectionPoolProviderInfo; }
- }
-
- ///
- /// Return the pooled authentication contexts.
- ///
- internal ConcurrentDictionary AuthenticationContexts
- {
- get
- {
- return _pooledDbAuthenticationContexts;
- }
- }
-
- internal bool UseLoadBalancing
- {
- get { return PoolGroupOptions.UseLoadBalancing; }
- }
-
- private bool UsingIntegrateSecurity
- {
- get { return _identity != null && DbConnectionPoolIdentity.NoIdentity != _identity; }
- }
-
- private void CleanupCallback(object state)
- {
- // Called when the cleanup-timer ticks over.
-
- // This is the automatic pruning method. Every period, we will
- // perform a two-step process:
- //
- // First, for each free object above MinPoolSize, we will obtain a
- // semaphore representing one object and destroy one from old stack.
- // We will continue this until we either reach MinPoolSize, we are
- // unable to obtain a free object, or we have exhausted all the
- // objects on the old stack.
- //
- // Second we move all free objects on the new stack to the old stack.
- // So, every period the objects on the old stack are destroyed and
- // the objects on the new stack are pushed to the old stack. All
- // objects that are currently out and in use are not on either stack.
- //
- // With this logic, objects are pruned from the pool if unused for
- // at least one period but not more than two periods.
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectID);
-
- // Destroy free objects that put us above MinPoolSize from old stack.
- while (Count > MinPoolSize)
- { // While above MinPoolSize...
- if (_waitHandles.PoolSemaphore.WaitOne(0))
- {
- // We obtained a objects from the semaphore.
- DbConnectionInternal obj;
-
- if (_stackOld.TryPop(out obj))
- {
- Debug.Assert(obj != null, "null connection is not expected");
- // If we obtained one from the old stack, destroy it.
- SqlClientEventSource.Log.ExitFreeConnection();
-
- // Transaction roots must survive even aging out (TxEnd event will clean them up).
- bool shouldDestroy = true;
- lock (obj)
- { // Lock to prevent race condition window between IsTransactionRoot and shouldDestroy assignment
- if (obj.IsTransactionRoot)
- {
- shouldDestroy = false;
- }
- }
-
- // !!!!!!!!!! WARNING !!!!!!!!!!!!!
- // ONLY touch obj after lock release if shouldDestroy is false!!! Otherwise, it may be destroyed
- // by transaction-end thread!
-
- // Note that there is a minor race condition between this task and the transaction end event, if the latter runs
- // between the lock above and the SetInStasis call below. The result is that the stasis counter may be
- // incremented without a corresponding decrement (the transaction end task is normally expected
- // to decrement, but will only do so if the stasis flag is set when it runs). I've minimized the size
- // of the window, but we aren't totally eliminating it due to SetInStasis needing to do bid tracing, which
- // we don't want to do under this lock, if possible. It should be possible to eliminate this race condition with
- // more substantial re-architecture of the pool, but we don't have the time to do that work for the current release.
-
- if (shouldDestroy)
- {
- DestroyObject(obj);
- }
- else
- {
- obj.SetInStasis();
- }
- }
- else
- {
- // Else we exhausted the old stack (the object the
- // semaphore represents is on the new stack), so break.
- _waitHandles.PoolSemaphore.Release(1);
- break;
- }
- }
- else
- {
- break;
- }
- }
-
- // Push to the old-stack. For each free object, move object from
- // new stack to old stack.
- if (_waitHandles.PoolSemaphore.WaitOne(0))
- {
- for (; ; )
- {
- DbConnectionInternal obj;
-
- if (!_stackNew.TryPop(out obj))
- break;
-
- Debug.Assert(obj != null, "null connection is not expected");
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, ChangeStacks={1}", ObjectID, obj.ObjectID);
- Debug.Assert(!obj.IsEmancipated, "pooled object not in pool");
- Debug.Assert(obj.CanBePooled, "pooled object is not poolable");
-
- _stackOld.Push(obj);
- }
- _waitHandles.PoolSemaphore.Release(1);
- }
-
- // Queue up a request to bring us up to MinPoolSize
- QueuePoolCreateRequest();
- }
-
- internal void Clear()
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Clearing.", ObjectID);
- DbConnectionInternal obj;
-
- // First, quickly doom everything.
- lock (_objectList)
- {
- int count = _objectList.Count;
-
- for (int i = 0; i < count; ++i)
- {
- obj = _objectList[i];
-
- if (obj != null)
- {
- obj.DoNotPoolThisConnection();
- }
- }
- }
-
- // Second, dispose of all the free connections.
- while (_stackNew.TryPop(out obj))
- {
- Debug.Assert(obj != null, "null connection is not expected");
- SqlClientEventSource.Log.ExitFreeConnection();
- DestroyObject(obj);
- }
- while (_stackOld.TryPop(out obj))
- {
- Debug.Assert(obj != null, "null connection is not expected");
- SqlClientEventSource.Log.ExitFreeConnection();
- DestroyObject(obj);
- }
-
- // Finally, reclaim everything that's emancipated (which, because
- // it's been doomed, will cause it to be disposed of as well)
- ReclaimEmancipatedObjects();
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Cleared.", ObjectID);
- }
-
- private Timer CreateCleanupTimer() =>
- ADP.UnsafeCreateTimer(
- new TimerCallback(CleanupCallback),
- null,
- _cleanupWait,
- _cleanupWait);
-
- private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
- {
- DbConnectionInternal newObj = null;
-
- try
- {
- newObj = _connectionFactory.CreatePooledConnection(this, owningObject, _connectionPoolGroup.ConnectionOptions, _connectionPoolGroup.PoolKey, userOptions);
- if (newObj == null)
- {
- throw ADP.InternalError(ADP.InternalErrorCode.CreateObjectReturnedNull); // CreateObject succeeded, but null object
- }
- if (!newObj.CanBePooled)
- {
- throw ADP.InternalError(ADP.InternalErrorCode.NewObjectCannotBePooled); // CreateObject succeeded, but non-poolable object
- }
- newObj.PrePush(null);
-
- lock (_objectList)
- {
- if ((oldConnection != null) && (oldConnection.Pool == this))
- {
- _objectList.Remove(oldConnection);
- }
- _objectList.Add(newObj);
- _totalObjects = _objectList.Count;
- SqlClientEventSource.Log.EnterPooledConnection();
- }
-
- // If the old connection belonged to another pool, we need to remove it from that
- if (oldConnection != null)
- {
- var oldConnectionPool = oldConnection.Pool;
- if (oldConnectionPool != null && oldConnectionPool != this)
- {
- Debug.Assert(oldConnectionPool._state == State.ShuttingDown, "Old connections pool should be shutting down");
- lock (oldConnectionPool._objectList)
- {
- oldConnectionPool._objectList.Remove(oldConnection);
- oldConnectionPool._totalObjects = oldConnectionPool._objectList.Count;
- }
- }
- }
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Added to pool.", ObjectID, newObj?.ObjectID);
-
- // Reset the error wait:
- _errorWait = ERROR_WAIT_DEFAULT;
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
-
-#if NET6_0_OR_GREATER
- if (!IsBlockingPeriodEnabled())
- {
- throw;
- }
-#endif
-
- // Close associated Parser if connection already established.
- if (newObj?.IsConnectionAlive() == true)
- {
- newObj.Dispose();
- }
-
- newObj = null; // set to null, so we do not return bad new object
-
- // Failed to create instance
- _resError = e;
-
- // Make sure the timer starts even if ThreadAbort occurs after setting the ErrorEvent.
-
- // timer allocation has to be done out of CER block
- Timer t = new Timer(new TimerCallback(this.ErrorCallback), null, Timeout.Infinite, Timeout.Infinite);
-
- bool timerIsNotDisposed;
- try
- { }
- finally
- {
- _waitHandles.ErrorEvent.Set();
- _errorOccurred = true;
-
- // Enable the timer.
- // Note that the timer is created to allow periodic invocation. If ThreadAbort occurs in the middle of ErrorCallback,
- // the timer will restart. Otherwise, the timer callback (ErrorCallback) destroys the timer after resetting the error to avoid second callback.
- _errorTimer = t;
- timerIsNotDisposed = t.Change(_errorWait, _errorWait);
- }
-
- Debug.Assert(timerIsNotDisposed, "ErrorCallback timer has been disposed");
-
- if (30000 < _errorWait)
- {
- _errorWait = 60000;
- }
- else
- {
- _errorWait *= 2;
- }
- throw;
- }
- return newObj;
- }
-
- private void DeactivateObject(DbConnectionInternal obj)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Deactivating.", ObjectID, obj.ObjectID);
- obj.DeactivateConnection();
-
- bool returnToGeneralPool = false;
- bool destroyObject = false;
- bool rootTxn = false;
-
- if (obj.IsConnectionDoomed)
- {
- // the object is not fit for reuse -- just dispose of it.
- destroyObject = true;
- }
- else
- {
- // NOTE: constructor should ensure that current state cannot be State.Initializing, so it can only
- // be State.Running or State.ShuttingDown
- Debug.Assert(_state == State.Running || _state == State.ShuttingDown);
-
- lock (obj)
- {
- // A connection with a delegated transaction cannot currently
- // be returned to a different customer until the transaction
- // actually completes, so we send it into Stasis -- the SysTx
- // transaction object will ensure that it is owned (not lost),
- // and it will be certain to put it back into the pool.
-
- if (_state == State.ShuttingDown)
- {
- if (obj.IsTransactionRoot)
- {
- // SQLHotfix# 50003503 - connections that are affiliated with a
- // root transaction and that also happen to be in a connection
- // pool that is being shutdown need to be put in stasis so that
- // the root transaction isn't effectively orphaned with no
- // means to promote itself to a full delegated transaction or
- // Commit or Rollback
- obj.SetInStasis();
- rootTxn = true;
- }
- else
- {
- // connection is being closed and the pool has been marked as shutting
- // down, so destroy this object.
- destroyObject = true;
- }
- }
- else
- {
- if (obj.IsNonPoolableTransactionRoot)
- {
- obj.SetInStasis();
- rootTxn = true;
- }
- else if (obj.CanBePooled)
- {
- // We must put this connection into the transacted pool
- // while inside a lock to prevent a race condition with
- // the transaction asynchronously completing on a second
- // thread.
-
- Transaction transaction = obj.EnlistedTransaction;
- if (transaction != null)
- {
- // NOTE: we're not locking on _state, so it's possible that its
- // value could change between the conditional check and here.
- // Although perhaps not ideal, this is OK because the
- // DelegatedTransactionEnded event will clean up the
- // connection appropriately regardless of the pool state.
- Debug.Assert(_transactedConnectionPool != null, "Transacted connection pool was not expected to be null.");
- _transactedConnectionPool.PutTransactedObject(transaction, obj);
- rootTxn = true;
- }
- else
- {
- // return to general pool
- returnToGeneralPool = true;
- }
- }
- else
- {
- if (obj.IsTransactionRoot && !obj.IsConnectionDoomed)
- {
- // SQLHotfix# 50003503 - if the object cannot be pooled but is a transaction
- // root, then we must have hit one of two race conditions:
- // 1) PruneConnectionPoolGroups shutdown the pool and marked this connection
- // as non-poolable while we were processing within this lock
- // 2) The LoadBalancingTimeout expired on this connection and marked this
- // connection as DoNotPool.
- //
- // This connection needs to be put in stasis so that the root transaction isn't
- // effectively orphaned with no means to promote itself to a full delegated
- // transaction or Commit or Rollback
- obj.SetInStasis();
- rootTxn = true;
- }
- else
- {
- // object is not fit for reuse -- just dispose of it
- destroyObject = true;
- }
- }
- }
- }
- }
-
- if (returnToGeneralPool)
- {
- // Only push the connection into the general pool if we didn't
- // already push it onto the transacted pool, put it into stasis,
- // or want to destroy it.
- Debug.Assert(destroyObject == false);
- PutNewObject(obj);
- }
- else if (destroyObject)
- {
- DestroyObject(obj);
- QueuePoolCreateRequest();
- }
-
- //-------------------------------------------------------------------------------------
- // postcondition
-
- // ensure that the connection was processed
- Debug.Assert(rootTxn == true || returnToGeneralPool == true || destroyObject == true);
- }
-
- internal void DestroyObject(DbConnectionInternal obj)
- {
- // A connection with a delegated transaction cannot be disposed of
- // until the delegated transaction has actually completed. Instead,
- // we simply leave it alone; when the transaction completes, it will
- // come back through PutObjectFromTransactedPool, which will call us
- // again.
- if (obj.IsTxRootWaitingForTxEnd)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Has Delegated Transaction, waiting to Dispose.", ObjectID, obj.ObjectID);
- }
- else
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removing from pool.", ObjectID, obj.ObjectID);
- bool removed = false;
- lock (_objectList)
- {
- removed = _objectList.Remove(obj);
- Debug.Assert(removed, "attempt to DestroyObject not in list");
- _totalObjects = _objectList.Count;
- }
-
- if (removed)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removed from pool.", ObjectID, obj.ObjectID);
- SqlClientEventSource.Log.ExitPooledConnection();
- }
- obj.Dispose();
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Disposed.", ObjectID, obj.ObjectID);
- SqlClientEventSource.Log.HardDisconnectRequest();
- }
- }
-
- private void ErrorCallback(object state)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Resetting Error handling.", ObjectID);
- _errorOccurred = false;
- _waitHandles.ErrorEvent.Reset();
-
- // the error state is cleaned, destroy the timer to avoid periodic invocation
- Timer t = _errorTimer;
- _errorTimer = null;
- if (t != null)
- {
- t.Dispose(); // Cancel timer request.
- }
- }
-
-
- private Exception TryCloneCachedException()
- // Cached exception can be of any type, so is not always cloneable.
- // This functions clones SqlException
- // OleDb and Odbc connections are not passing throw this code
- {
- if (_resError == null)
- return null;
-
- var sqlError = _resError as SqlClient.SqlException;
- if (sqlError != null)
- return sqlError.InternalClone();
-
- return _resError;
- }
-
- private void WaitForPendingOpen()
- {
- PendingGetConnection next;
-
- do
- {
- bool started = false;
-
- try
- {
- try
- { }
- finally
- {
- started = Interlocked.CompareExchange(ref _pendingOpensWaiting, 1, 0) == 0;
- }
-
- if (!started)
- {
- return;
- }
-
- while (_pendingOpens.TryDequeue(out next))
- {
- if (next.Completion.Task.IsCompleted)
- {
- continue;
- }
-
- uint delay;
- if (next.DueTime == Timeout.Infinite)
- {
- delay = unchecked((uint)Timeout.Infinite);
- }
- else
- {
- delay = (uint)Math.Max(ADP.TimerRemainingMilliseconds(next.DueTime), 0);
- }
-
- DbConnectionInternal connection = null;
- bool timeout = false;
- Exception caughtException = null;
-
- try
- {
- bool allowCreate = true;
- bool onlyOneCheckConnection = false;
- ADP.SetCurrentTransaction(next.Completion.Task.AsyncState as System.Transactions.Transaction);
- timeout = !TryGetConnection(next.Owner, delay, allowCreate, onlyOneCheckConnection, next.UserOptions, out connection);
- }
- catch (Exception e)
- {
- caughtException = e;
- }
-
- if (caughtException != null)
- {
- next.Completion.TrySetException(caughtException);
- }
- else if (timeout)
- {
- next.Completion.TrySetException(ADP.ExceptionWithStackTrace(ADP.PooledOpenTimeout()));
- }
- else
- {
- Debug.Assert(connection != null, "connection should never be null in success case");
- if (!next.Completion.TrySetResult(connection))
- {
- // if the completion was cancelled, lets try and get this connection back for the next try
- PutObject(connection, next.Owner);
- }
- }
- }
- }
- finally
- {
- if (started)
- {
- Interlocked.Exchange(ref _pendingOpensWaiting, 0);
- }
- }
- } while (_pendingOpens.TryPeek(out next));
- }
-
- internal bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection)
- {
- uint waitForMultipleObjectsTimeout = 0;
- bool allowCreate = false;
-
- if (retry == null)
- {
- waitForMultipleObjectsTimeout = (uint)CreationTimeout;
-
- // Set the wait timeout to INFINITE (-1) if the SQL connection timeout is 0 (== infinite)
- if (waitForMultipleObjectsTimeout == 0)
- waitForMultipleObjectsTimeout = unchecked((uint)Timeout.Infinite);
-
- allowCreate = true;
- }
-
- if (_state != State.Running)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, DbConnectionInternal State != Running.", ObjectID);
- connection = null;
- return true;
- }
-
- bool onlyOneCheckConnection = true;
- if (TryGetConnection(owningObject, waitForMultipleObjectsTimeout, allowCreate, onlyOneCheckConnection, userOptions, out connection))
- {
- return true;
- }
- else if (retry == null)
- {
- // timed out on a sync call
- return true;
- }
-
- var pendingGetConnection =
- new PendingGetConnection(
- CreationTimeout == 0 ? Timeout.Infinite : ADP.TimerCurrent() + ADP.TimerFromSeconds(CreationTimeout / 1000),
- owningObject,
- retry,
- userOptions);
- _pendingOpens.Enqueue(pendingGetConnection);
-
- // it is better to StartNew too many times than not enough
- if (_pendingOpensWaiting == 0)
- {
- Thread waitOpenThread = new Thread(WaitForPendingOpen);
- waitOpenThread.IsBackground = true;
- waitOpenThread.Start();
- }
-
- connection = null;
- return false;
- }
-
- private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObjectsTimeout, bool allowCreate, bool onlyOneCheckConnection, DbConnectionOptions userOptions, out DbConnectionInternal connection)
- {
- DbConnectionInternal obj = null;
- Transaction transaction = null;
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Getting connection.", ObjectID);
-
- // If automatic transaction enlistment is required, then we try to
- // get the connection from the transacted connection pool first.
- if (HasTransactionAffinity)
- {
- obj = GetFromTransactedPool(out transaction);
- }
-
- if (obj == null)
- {
- Interlocked.Increment(ref _waitCount);
-
- do
- {
- int waitResult = BOGUS_HANDLE;
- try
- {
- try
- {
- }
- finally
- {
- waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(allowCreate), unchecked((int)waitForMultipleObjectsTimeout));
- }
-
- // From the WaitAny docs: "If more than one object became signaled during
- // the call, this is the array index of the signaled object with the
- // smallest index value of all the signaled objects." This is important
- // so that the free object signal will be returned before a creation
- // signal.
-
- switch (waitResult)
- {
- case WaitHandle.WaitTimeout:
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", ObjectID);
- Interlocked.Decrement(ref _waitCount);
- connection = null;
- return false;
-
- case ERROR_HANDLE:
- // Throw the error that PoolCreateRequest stashed.
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Errors are set.", ObjectID);
- Interlocked.Decrement(ref _waitCount);
- throw TryCloneCachedException();
-
- case CREATION_HANDLE:
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectID);
- try
- {
- obj = UserCreateRequest(owningObject, userOptions);
- }
- catch
- {
- if (obj == null)
- {
- Interlocked.Decrement(ref _waitCount);
- }
- throw;
- }
- finally
- {
- // Ensure that we release this waiter, regardless
- // of any exceptions that may be thrown.
- if (obj != null)
- {
- Interlocked.Decrement(ref _waitCount);
- }
- }
-
- if (obj == null)
- {
- // If we were not able to create an object, check to see if
- // we reached MaxPoolSize. If so, we will no longer wait on
- // the CreationHandle, but instead wait for a free object or
- // the timeout.
- if (Count >= MaxPoolSize && 0 != MaxPoolSize)
- {
- if (!ReclaimEmancipatedObjects())
- {
- // modify handle array not to wait on creation mutex anymore
- Debug.Assert(2 == CREATION_HANDLE, "creation handle changed value");
- allowCreate = false;
- }
- }
- }
- break;
-
- case SEMAPHORE_HANDLE:
- //
- // guaranteed available inventory
- //
- Interlocked.Decrement(ref _waitCount);
- obj = GetFromGeneralPool();
-
- if ((obj != null) && (!obj.IsConnectionAlive()))
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", ObjectID, obj.ObjectID);
- DestroyObject(obj);
- obj = null; // Setting to null in case creating a new object fails
-
- if (onlyOneCheckConnection)
- {
- if (_waitHandles.CreationSemaphore.WaitOne(unchecked((int)waitForMultipleObjectsTimeout)))
- {
- try
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectID);
- obj = UserCreateRequest(owningObject, userOptions);
- }
- finally
- {
- _waitHandles.CreationSemaphore.Release(1);
- }
- }
- else
- {
- // Timeout waiting for creation semaphore - return null
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", ObjectID);
- connection = null;
- return false;
- }
- }
- }
- break;
-
- default:
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, WaitForMultipleObjects={1}", ObjectID, waitResult);
- Interlocked.Decrement(ref _waitCount);
- throw ADP.InternalError(ADP.InternalErrorCode.UnexpectedWaitAnyResult);
- }
- }
- finally
- {
- if (CREATION_HANDLE == waitResult)
- {
- _waitHandles.CreationSemaphore.Release(1);
- }
- }
-
- // Do not use this pooled connection if access token is about to expire soon before we can connect.
- if (obj != null && obj.IsAccessTokenExpired)
- {
- DestroyObject(obj);
- obj = null;
- }
- } while (obj == null);
- }
-
- if (obj != null)
- {
- PrepareConnection(owningObject, obj, transaction);
- }
-
- connection = obj;
- SqlClientEventSource.Log.SoftConnectRequest();
- return true;
- }
-
- private void PrepareConnection(DbConnection owningObject, DbConnectionInternal obj, Transaction transaction)
- {
- lock (obj)
- { // Protect against Clear and ReclaimEmancipatedObjects, which call IsEmancipated, which is affected by PrePush and PostPop
- obj.PostPop(owningObject);
- }
- try
- {
- obj.ActivateConnection(transaction);
- }
- catch
- {
- // if Activate throws an exception
- // put it back in the pool or have it properly disposed of
- this.PutObject(obj, owningObject);
- throw;
- }
- }
-
- ///
- /// Creates a new connection to replace an existing connection
- ///
- /// Outer connection that currently owns
- /// Options used to create the new connection
- /// Inner connection that will be replaced
- /// A new inner connection that is attached to the
- internal DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", ObjectID);
- DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection);
-
- if (newConnection != null)
- {
- SqlClientEventSource.Log.SoftConnectRequest();
- PrepareConnection(owningObject, newConnection, oldConnection.EnlistedTransaction);
- oldConnection.PrepareForReplaceConnection();
- oldConnection.DeactivateConnection();
- oldConnection.Dispose();
- }
-
- return newConnection;
- }
-
- private DbConnectionInternal GetFromGeneralPool()
- {
- DbConnectionInternal obj = null;
-
- if (!_stackNew.TryPop(out obj))
- {
- if (!_stackOld.TryPop(out obj))
- {
- obj = null;
- }
- else
- {
- Debug.Assert(obj != null, "null connection is not expected");
- }
- }
- else
- {
- Debug.Assert(obj != null, "null connection is not expected");
- }
-
- // When another thread is clearing this pool,
- // it will remove all connections in this pool which causes the
- // following assert to fire, which really mucks up stress against
- // checked bits.
-
- if (obj != null)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from general pool.", ObjectID, obj.ObjectID);
- SqlClientEventSource.Log.ExitFreeConnection();
- }
- return (obj);
- }
-
- private DbConnectionInternal GetFromTransactedPool(out Transaction transaction)
- {
- transaction = ADP.GetCurrentTransaction();
- DbConnectionInternal obj = null;
-
- if (transaction != null && _transactedConnectionPool != null)
- {
- obj = _transactedConnectionPool.GetTransactedObject(transaction);
-
- if (obj != null)
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from transacted pool.", ObjectID, obj.ObjectID);
- SqlClientEventSource.Log.ExitFreeConnection();
-
- if (obj.IsTransactionRoot)
- {
- try
- {
- obj.IsConnectionAlive(true);
- }
- catch
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", ObjectID, obj.ObjectID);
- DestroyObject(obj);
- throw;
- }
- }
- else if (!obj.IsConnectionAlive())
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", ObjectID, obj.ObjectID);
- DestroyObject(obj);
- obj = null;
- }
- }
- }
- return obj;
- }
-
- private void PoolCreateRequest(object state)
- {
- // called by pooler to ensure pool requests are currently being satisfied -
- // creation mutex has not been obtained
- long scopeID = SqlClientEventSource.Log.TryPoolerScopeEnterEvent(" {0}", ObjectID);
- try
- {
- if (State.Running == _state)
- {
- // in case WaitForPendingOpen ever failed with no subsequent OpenAsync calls,
- // start it back up again
- if (!_pendingOpens.IsEmpty && _pendingOpensWaiting == 0)
- {
- Thread waitOpenThread = new Thread(WaitForPendingOpen);
- waitOpenThread.IsBackground = true;
- waitOpenThread.Start();
- }
-
- // Before creating any new objects, reclaim any released objects that were
- // not closed.
- ReclaimEmancipatedObjects();
-
- if (!ErrorOccurred)
- {
- if (NeedToReplenish)
- {
- // Check to see if pool was created using integrated security and if so, make
- // sure the identity of current user matches that of user that created pool.
- // If it doesn't match, do not create any objects on the ThreadPool thread,
- // since either Open will fail or we will open a object for this pool that does
- // not belong in this pool. The side effect of this is that if using integrated
- // security min pool size cannot be guaranteed.
- if (UsingIntegrateSecurity && !_identity.Equals(DbConnectionPoolIdentity.GetCurrent()))
- {
- return;
- }
- int waitResult = BOGUS_HANDLE;
- try
- {
- try
- { }
- finally
- {
- waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(withCreate: true), CreationTimeout);
- }
- if (CREATION_HANDLE == waitResult)
- {
- DbConnectionInternal newObj;
-
- // Check ErrorOccurred again after obtaining mutex
- if (!ErrorOccurred)
- {
- while (NeedToReplenish)
- {
- try
- {
- // Don't specify any user options because there is no outer connection associated with the new connection
- newObj = CreateObject(owningObject: null, userOptions: null, oldConnection: null);
- }
- catch
- {
- // Catch all the exceptions occuring during CreateObject so that they
- // don't emerge as unhandled on the thread pool and don't crash applications
- // The error is handled in CreateObject and surfaced to the caller of the Connection Pool
- // using the ErrorEvent. Hence it is OK to swallow all exceptions here.
- break;
- }
- // We do not need to check error flag here, since we know if
- // CreateObject returned null, we are in error case.
- if (newObj != null)
- {
- PutNewObject(newObj);
- }
- else
- {
- break;
- }
- }
- }
- }
- else if (WaitHandle.WaitTimeout == waitResult)
- {
- // do not wait forever and potential block this worker thread
- // instead wait for a period of time and just requeue to try again
- QueuePoolCreateRequest();
- }
- else
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called WaitForSingleObject failed {1}", ObjectID, waitResult);
- }
- }
- catch (Exception e)
- {
- if (!ADP.IsCatchableExceptionType(e))
- {
- throw;
- }
-
- // Now that CreateObject can throw, we need to catch the exception and discard it.
- // There is no further action we can take beyond tracing. The error will be
- // thrown to the user the next time they request a connection.
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", ObjectID, e);
- }
- finally
- {
- if (CREATION_HANDLE == waitResult)
- {
- // reuse waitResult and ignore its value
- _waitHandles.CreationSemaphore.Release(1);
- }
- }
- }
- }
- }
- }
- finally
- {
- SqlClientEventSource.Log.TryPoolerScopeLeaveEvent(scopeID);
- }
- }
-
- internal void PutNewObject(DbConnectionInternal obj)
- {
- Debug.Assert(obj != null, "why are we adding a null object to the pool?");
- // Debug.Assert(obj.CanBePooled, "non-poolable object in pool");
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Pushing to general pool.", ObjectID, obj.ObjectID);
-
- _stackNew.Push(obj);
- _waitHandles.PoolSemaphore.Release(1);
- SqlClientEventSource.Log.EnterFreeConnection();
- }
-
- internal void PutObject(DbConnectionInternal obj, object owningObject)
- {
- Debug.Assert(obj != null, "null obj?");
- SqlClientEventSource.Log.SoftDisconnectRequest();
-
- // Once a connection is closing (which is the state that we're in at
- // this point in time) you cannot delegate a transaction to or enlist
- // a transaction in it, so we can correctly presume that if there was
- // not a delegated or enlisted transaction to start with, that there
- // will not be a delegated or enlisted transaction once we leave the
- // lock.
-
- lock (obj)
- {
- // Calling PrePush prevents the object from being reclaimed
- // once we leave the lock, because it sets _pooledCount such
- // that it won't appear to be out of the pool. What that
- // means, is that we're now responsible for this connection:
- // it won't get reclaimed if it gets lost.
- obj.PrePush(owningObject);
- }
-
- DeactivateObject(obj);
- }
-
- internal void PutObjectFromTransactedPool(DbConnectionInternal obj)
- {
- Debug.Assert(obj != null, "null pooledObject?");
- Debug.Assert(obj.EnlistedTransaction == null, "pooledObject is still enlisted?");
-
- // called by the transacted connection pool , once it's removed the
- // connection from it's list. We put the connection back in general
- // circulation.
-
- // NOTE: there is no locking required here because if we're in this
- // method, we can safely presume that the caller is the only person
- // that is using the connection, and that all pre-push logic has been
- // done and all transactions are ended.
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Transaction has ended.", ObjectID, obj.ObjectID);
-
- if (_state == State.Running && obj.CanBePooled)
- {
- PutNewObject(obj);
- }
- else
- {
- DestroyObject(obj);
- QueuePoolCreateRequest();
- }
- }
-
- private void QueuePoolCreateRequest()
- {
- if (State.Running == _state)
- {
- // Make sure we're at quota by posting a callback to the threadpool.
- ThreadPool.QueueUserWorkItem(_poolCreateRequest);
- }
- }
-
- private bool ReclaimEmancipatedObjects()
- {
- bool emancipatedObjectFound = false;
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectID);
- List reclaimedObjects = new List();
- int count;
-
- lock (_objectList)
- {
- count = _objectList.Count;
-
- for (int i = 0; i < count; ++i)
- {
- DbConnectionInternal obj = _objectList[i];
-
- if (obj != null)
- {
- bool locked = false;
-
- try
- {
- Monitor.TryEnter(obj, ref locked);
-
- if (locked)
- { // avoid race condition with PrePush/PostPop and IsEmancipated
- if (obj.IsEmancipated)
- {
- // Inside the lock, we want to do as little
- // as possible, so we simply mark the object
- // as being in the pool, but hand it off to
- // an out of pool list to be deactivated,
- // etc.
- obj.PrePush(null);
- reclaimedObjects.Add(obj);
- }
- }
- }
- finally
- {
- if (locked)
- Monitor.Exit(obj);
- }
- }
- }
- }
-
- // NOTE: we don't want to call DeactivateObject while we're locked,
- // because it can make roundtrips to the server and this will block
- // object creation in the pooler. Instead, we queue things we need
- // to do up, and process them outside the lock.
- count = reclaimedObjects.Count;
-
- for (int i = 0; i < count; ++i)
- {
- DbConnectionInternal obj = reclaimedObjects[i];
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Reclaiming.", ObjectID, obj.ObjectID);
- SqlClientEventSource.Log.ReclaimedConnectionRequest();
-
- emancipatedObjectFound = true;
-
- obj.DetachCurrentTransactionIfEnded();
- DeactivateObject(obj);
- }
- return emancipatedObjectFound;
- }
-
- internal void Startup()
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, CleanupWait={1}", ObjectID, _cleanupWait);
- _cleanupTimer = CreateCleanupTimer();
- if (NeedToReplenish)
- {
- QueuePoolCreateRequest();
- }
- }
-
- internal void Shutdown()
- {
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectID);
- _state = State.ShuttingDown;
-
- // deactivate timer callbacks
- Timer t = _cleanupTimer;
- _cleanupTimer = null;
- if (t != null)
- {
- t.Dispose();
- }
- }
-
- // TransactionEnded merely provides the plumbing for DbConnectionInternal to access the transacted pool
- // that is implemented inside DbConnectionPool. This method's counterpart (PutTransactedObject) should
- // only be called from DbConnectionPool.DeactivateObject and thus the plumbing to provide access to
- // other objects is unnecessary (hence the asymmetry of Ended but no Begin)
- internal void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject)
- {
- Debug.Assert(transaction != null, "null transaction?");
- Debug.Assert(transactedObject != null, "null transactedObject?");
-
- // Note: connection may still be associated with transaction due to Explicit Unbinding requirement.
- SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Transaction Completed", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID);
-
- // called by the internal connection when it get's told that the
- // transaction is completed. We tell the transacted pool to remove
- // the connection from it's list, then we put the connection back in
- // general circulation.
- TransactedConnectionPool transactedConnectionPool = _transactedConnectionPool;
- if (transactedConnectionPool != null)
- {
- transactedConnectionPool.TransactionEnded(transaction, transactedObject);
- }
- }
-
- private DbConnectionInternal UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection = null)
- {
- // called by user when they were not able to obtain a free object but
- // instead obtained creation mutex
-
- DbConnectionInternal obj = null;
- if (ErrorOccurred)
- {
- throw TryCloneCachedException();
- }
- else
- {
- if ((oldConnection != null) || (Count < MaxPoolSize) || (0 == MaxPoolSize))
- {
- // If we have an odd number of total objects, reclaim any dead objects.
- // If we did not find any objects to reclaim, create a new one.
- if ((oldConnection != null) || (Count & 0x1) == 0x1 || !ReclaimEmancipatedObjects())
- obj = CreateObject(owningObject, userOptions, oldConnection);
- }
- return obj;
- }
- }
-
-#if NET6_0_OR_GREATER
- private bool IsBlockingPeriodEnabled()
- {
- var poolGroupConnectionOptions = _connectionPoolGroup.ConnectionOptions as SqlConnectionString;
- if (poolGroupConnectionOptions == null)
- {
- return true;
- }
- var policy = poolGroupConnectionOptions.PoolBlockingPeriod;
-
- switch (policy)
- {
- case PoolBlockingPeriod.Auto:
- {
- return !ADP.IsAzureSqlServerEndpoint(poolGroupConnectionOptions.DataSource);
- }
- case PoolBlockingPeriod.AlwaysBlock:
- {
- return true; //Enabled
- }
- case PoolBlockingPeriod.NeverBlock:
- {
- return false; //Disabled
- }
- default:
- {
- //we should never get into this path.
- Debug.Fail("Unknown PoolBlockingPeriod. Please specify explicit results in above switch case statement.");
- return true;
- }
- }
- }
-#endif
- }
-}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
index 4cee4ff95c..32229e78f2 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -50,6 +50,16 @@ internal struct ReliabilitySection
internal static void Assert(string message)
{
}
+
+ [Conditional("NETFRAMEWORK")]
+ internal void Start()
+ {
+ }
+
+ [Conditional("NETFRAMEWORK")]
+ internal void Stop()
+ {
+ }
}
private static int _objectTypeCount; // EventSource counter
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..84be24540a 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,9 @@
Microsoft\Data\OperationAbortedException.cs
+
+ Microsoft\Data\ProviderBase\DbConnectionPool.cs
+
Microsoft\Data\ProviderBase\DbConnectionPoolAuthenticationContext.cs
@@ -671,7 +674,6 @@
-
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 6245fd15a4..ca3798e428 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -1330,12 +1330,6 @@ internal static Task CreatedTaskWithCancellation()
return completion.Task;
}
- internal static void TraceExceptionForCapture(Exception e)
- {
- Debug.Assert(ADP.IsCatchableExceptionType(e), "Invalid exception type, should have been re-thrown!");
- TraceException(" '{0}'", e);
- }
-
//
// Helper Functions
//
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs
similarity index 95%
rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs
index 061f60d3f1..336f690776 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs
@@ -2,26 +2,25 @@
// 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.Data.Common;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Transactions;
+using Microsoft.Data.Common;
+using Microsoft.Data.SqlClient;
+
namespace Microsoft.Data.ProviderBase
{
-
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.Data.Common;
- using System.Diagnostics;
- using System.Diagnostics.CodeAnalysis;
- using System.Runtime.CompilerServices;
- using System.Runtime.ConstrainedExecution;
- using System.Runtime.InteropServices;
- using System.Runtime.Versioning;
- using System.Threading;
- using System.Threading.Tasks;
- using Microsoft.Data.Common;
- using Microsoft.Data.SqlClient;
- using System.Transactions;
-
- sealed internal class DbConnectionPool
+ internal sealed class DbConnectionPool
{
private enum State
{
@@ -33,7 +32,7 @@ private enum State
// This class is a way to stash our cloned Tx key for later disposal when it's no longer needed.
// We can't get at the key in the dictionary without enumerating entries, so we stash an extra
// copy as part of the value.
- sealed private class TransactedConnectionList : List
+ private sealed class TransactedConnectionList : List
{
private Transaction _transaction;
internal TransactedConnectionList(int initialAllocation, Transaction tx) : base(initialAllocation)
@@ -50,7 +49,7 @@ internal void Dispose()
}
}
- sealed class PendingGetConnection
+ private sealed class PendingGetConnection
{
public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSource completion, DbConnectionOptions userOptions)
{
@@ -65,9 +64,8 @@ public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSour
public DbConnectionOptions UserOptions { get; private set; }
}
- sealed private class TransactedConnectionPool
+ private sealed class TransactedConnectionPool
{
-
Dictionary _transactedCxns;
DbConnectionPool _pool;
@@ -78,6 +76,7 @@ sealed private class TransactedConnectionPool
internal TransactedConnectionPool(DbConnectionPool pool)
{
Debug.Assert(pool != null, "null pool?");
+
_pool = pool;
_transactedCxns = new Dictionary();
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed for connection pool {1}", ObjectID, _pool.ObjectID);
@@ -237,9 +236,11 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal
}
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Added.", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID);
}
-
+#if NET
+ SqlClientEventSource.Log.EnterFreeConnection();
+#else
Pool.PerformanceCounters.NumberOfFreeConnections.Increment();
-
+#endif
}
internal void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject)
@@ -294,7 +295,6 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra
}
else
{
- //Debug.Assert ( false, "TransactionCompletedEvent fired before PutTransactedObject put the connection in the transacted pool." );
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Transacted pool not yet created prior to transaction completing. Connection may be leaked.", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID);
}
}
@@ -303,7 +303,11 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra
// connections, we'll put it back...
if (0 <= entry)
{
+#if NET
+ SqlClientEventSource.Log.ExitFreeConnection();
+#else
Pool.PerformanceCounters.NumberOfFreeConnections.Decrement();
+#endif
Pool.PutObjectFromTransactedPool(transactedObject);
}
}
@@ -323,8 +327,10 @@ private sealed class PoolWaitHandles
private readonly WaitHandle[] _handlesWithCreate;
private readonly WaitHandle[] _handlesWithoutCreate;
+#if NETFRAMEWORK
[ResourceExposure(ResourceScope.None)] // SxS: this method does not create named objects
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
+#endif
internal PoolWaitHandles()
{
_poolSemaphore = new Semaphore(0, MAX_Q_SIZE);
@@ -371,7 +377,7 @@ internal WaitHandle[] GetHandles(bool withCreate)
private const int ERROR_WAIT_DEFAULT = 5 * 1000; // 5 seconds
// we do want a testable, repeatable set of generated random numbers
- private static readonly Random _random = new Random(5101977); // Value obtained from Dave Driver
+ private static readonly Random s_random = new Random(5101977); // Value obtained from Dave Driver
private readonly int _cleanupWait;
private readonly DbConnectionPoolIdentity _identity;
@@ -422,7 +428,6 @@ internal DbConnectionPool(
DbConnectionPoolIdentity identity,
DbConnectionPoolProviderInfo connectionPoolProviderInfo)
{
- Debug.Assert(ADP.s_isWindowsNT, "Attempting to construct a connection pool on Win9x?");
Debug.Assert(connectionPoolGroup != null, "null connectionPoolGroup");
if (identity != null && identity.IsRestricted)
@@ -432,9 +437,10 @@ internal DbConnectionPool(
_state = State.Initializing;
- lock (_random)
- { // Random.Next is not thread-safe
- _cleanupWait = _random.Next(12, 24) * 10 * 1000; // 2-4 minutes in 10 sec intervals, WebData 103603
+ lock (s_random)
+ {
+ // Random.Next is not thread-safe
+ _cleanupWait = s_random.Next(12, 24) * 10 * 1000; // 2-4 minutes in 10 sec intervals, WebData 103603
}
_connectionFactory = connectionFactory;
@@ -453,10 +459,7 @@ internal DbConnectionPool(
_pooledDbAuthenticationContexts = new ConcurrentDictionary(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/,
capacity: 2);
- if (ADP.s_isPlatformNT5)
- {
- _transactedConnectionPool = new TransactedConnectionPool(this);
- }
+ _transactedConnectionPool = new TransactedConnectionPool(this);
_poolCreateRequest = new WaitCallback(PoolCreateRequest); // used by CleanupCallback
_state = State.Running;
@@ -500,7 +503,7 @@ private bool NeedToReplenish
{
get
{
- if (State.Running != _state) // SQL BU DT 364595 - don't allow connection create when not running.
+ if (State.Running != _state) // Don't allow connection create when not running.
return false;
int totalObjects = Count;
@@ -547,10 +550,12 @@ internal int ObjectID
}
}
+#if NETFRAMEWORK
internal DbConnectionPoolCounters PerformanceCounters
{
get { return _connectionFactory.PerformanceCounters; }
}
+#endif
internal DbConnectionPoolGroup PoolGroup
{
@@ -588,7 +593,7 @@ private bool UsingIntegrateSecurity
get { return _identity != null && DbConnectionPoolIdentity.NoIdentity != _identity; }
}
- private void CleanupCallback(Object state)
+ private void CleanupCallback(object state)
{
// Called when the cleanup-timer ticks over.
@@ -612,9 +617,9 @@ private void CleanupCallback(Object state)
// Destroy free objects that put us above MinPoolSize from old stack.
while (Count > MinPoolSize)
- { // While above MinPoolSize...
-
- if (_waitHandles.PoolSemaphore.WaitOne(0, false) /* != WAIT_TIMEOUT */)
+ {
+ // While above MinPoolSize...
+ if (_waitHandles.PoolSemaphore.WaitOne(0, false))
{
// We obtained a objects from the semaphore.
DbConnectionInternal obj;
@@ -623,7 +628,11 @@ private void CleanupCallback(Object state)
{
Debug.Assert(obj != null, "null connection is not expected");
// If we obtained one from the old stack, destroy it.
+#if NET
+ SqlClientEventSource.Log.ExitFreeConnection();
+#else
PerformanceCounters.NumberOfFreeConnections.Decrement();
+#endif
// Transaction roots must survive even aging out (TxEnd event will clean them up).
bool shouldDestroy = true;
@@ -672,7 +681,7 @@ private void CleanupCallback(Object state)
// Push to the old-stack. For each free object, move object from
// new stack to old stack.
- if (_waitHandles.PoolSemaphore.WaitOne(0, false) /* != WAIT_TIMEOUT */)
+ if (_waitHandles.PoolSemaphore.WaitOne(0, false))
{
for (; ; )
{
@@ -720,13 +729,21 @@ internal void Clear()
while (_stackNew.TryPop(out obj))
{
Debug.Assert(obj != null, "null connection is not expected");
+#if NET
+ SqlClientEventSource.Log.ExitFreeConnection();
+#else
PerformanceCounters.NumberOfFreeConnections.Decrement();
+#endif
DestroyObject(obj);
}
while (_stackOld.TryPop(out obj))
{
Debug.Assert(obj != null, "null connection is not expected");
+#if NET
+ SqlClientEventSource.Log.ExitFreeConnection();
+#else
PerformanceCounters.NumberOfFreeConnections.Decrement();
+#endif
DestroyObject(obj);
}
@@ -736,10 +753,12 @@ internal void Clear()
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Cleared.", ObjectID);
}
- private Timer CreateCleanupTimer()
- {
- return (new Timer(new TimerCallback(this.CleanupCallback), null, _cleanupWait, _cleanupWait));
- }
+ private Timer CreateCleanupTimer() =>
+ ADP.UnsafeCreateTimer(
+ new TimerCallback(CleanupCallback),
+ null,
+ _cleanupWait,
+ _cleanupWait);
private bool IsBlockingPeriodEnabled()
{
@@ -755,14 +774,7 @@ private bool IsBlockingPeriodEnabled()
{
case PoolBlockingPeriod.Auto:
{
- if (ADP.IsAzureSqlServerEndpoint(poolGroupConnectionOptions.DataSource))
- {
- return false; // in Azure it will be Disabled
- }
- else
- {
- return true; // in Non Azure, it will be Enabled
- }
+ return !ADP.IsAzureSqlServerEndpoint(poolGroupConnectionOptions.DataSource);
}
case PoolBlockingPeriod.AlwaysBlock:
{
@@ -806,7 +818,11 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio
}
_objectList.Add(newObj);
_totalObjects = _objectList.Count;
+#if NET
+ SqlClientEventSource.Log.EnterPooledConnection();
+#else
PerformanceCounters.NumberOfPooledConnections.Increment(); // TODO: Performance: Consider moving outside of lock?
+#endif
}
// If the old connection belonged to another pool, we need to remove it from that
@@ -836,7 +852,7 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio
throw;
}
- ADP.TraceExceptionForCapture(e);
+ ADP.TraceExceptionWithoutRethrow(e);
if (!IsBlockingPeriodEnabled())
{
@@ -854,12 +870,15 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio
// Failed to create instance
_resError = e;
- // VSTFDEVDIV 479561: Make sure the timer starts even if ThreadAbort occurs after setting the ErrorEvent.
+ // Make sure the timer starts even if ThreadAbort occurs after setting the ErrorEvent.
// timer allocation has to be done out of CER block
Timer t = new Timer(new TimerCallback(this.ErrorCallback), null, Timeout.Infinite, Timeout.Infinite);
+
bool timerIsNotDisposed;
+#if NETFRAMEWORK
RuntimeHelpers.PrepareConstrainedRegions();
+#endif
try
{ }
finally
@@ -892,7 +911,7 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio
private void DeactivateObject(DbConnectionInternal obj)
{
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Deactivating.", ObjectID, obj.ObjectID);
- obj.DeactivateConnection(); // we presume this operation is safe outside of a lock...
+ obj.DeactivateConnection();
bool returnToGeneralPool = false;
bool destroyObject = false;
@@ -1006,9 +1025,9 @@ private void DeactivateObject(DbConnectionInternal obj)
}
else if (destroyObject)
{
- // VSTFDEVDIV# 479556 - connections that have been marked as no longer
- // poolable (e.g. exceeded their connection lifetime) are not, in fact,
- // returned to the general pool
+ // Connections that have been marked as no longer
+ // poolable (e.g. exceeded their connection lifetime) are not, in fact,
+ // returned to the general pool
DestroyObject(obj);
QueuePoolCreateRequest();
}
@@ -1018,8 +1037,6 @@ private void DeactivateObject(DbConnectionInternal obj)
// ensure that the connection was processed
Debug.Assert(rootTxn == true || returnToGeneralPool == true || destroyObject == true);
-
- // TODO: BID trace processing state?
}
internal void DestroyObject(DbConnectionInternal obj)
@@ -1047,15 +1064,23 @@ internal void DestroyObject(DbConnectionInternal obj)
if (removed)
{
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removed from pool.", ObjectID, obj.ObjectID);
+#if NET
+ SqlClientEventSource.Log.ExitPooledConnection();
+#else
PerformanceCounters.NumberOfPooledConnections.Decrement();
+#endif
}
obj.Dispose();
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Disposed.", ObjectID, obj.ObjectID);
+#if NET
+ SqlClientEventSource.Log.HardDisconnectRequest();
+#else
PerformanceCounters.HardDisconnectsPerSecond.Increment();
+#endif
}
}
- private void ErrorCallback(Object state)
+ private void ErrorCallback(object state)
{
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Resetting Error handling.", ObjectID);
_errorOccurred = false;
@@ -1075,17 +1100,10 @@ private Exception TryCloneCachedException()
// Cached exception can be of any type, so is not always cloneable.
// This functions clones SqlException
// OleDb and Odbc connections are not passing throw this code
- {
- if (_resError == null)
- return null;
- if (_resError.GetType() == typeof(SqlClient.SqlException))
- return ((SqlClient.SqlException)_resError).InternalClone();
- return _resError;
- }
+ => _resError is SqlException sqlEx ? sqlEx.InternalClone() : _resError;
- void WaitForPendingOpen()
+ private void WaitForPendingOpen()
{
-
Debug.Assert(!Thread.CurrentThread.IsThreadPoolThread, "This thread may block for a long time. Threadpool threads should not be used.");
PendingGetConnection next;
@@ -1094,11 +1112,14 @@ void WaitForPendingOpen()
{
bool started = false;
+#if NETFRAMEWORK
RuntimeHelpers.PrepareConstrainedRegions();
+#endif
try
{
-
+#if NETFRAMEWORK
RuntimeHelpers.PrepareConstrainedRegions();
+#endif
try
{ }
finally
@@ -1113,7 +1134,6 @@ void WaitForPendingOpen()
while (_pendingOpens.TryDequeue(out next))
{
-
if (next.Completion.Task.IsCompleted)
{
continue;
@@ -1133,20 +1153,23 @@ void WaitForPendingOpen()
bool timeout = false;
Exception caughtException = null;
+#if NETFRAMEWORK
RuntimeHelpers.PrepareConstrainedRegions();
+#endif
try
{
#if DEBUG
Microsoft.Data.SqlClient.TdsParser.ReliabilitySection tdsReliabilitySection = new Microsoft.Data.SqlClient.TdsParser.ReliabilitySection();
+#if NETFRAMEWORK
RuntimeHelpers.PrepareConstrainedRegions();
+#endif
try
{
tdsReliabilitySection.Start();
#else
{
#endif //DEBUG
-
bool allowCreate = true;
bool onlyOneCheckConnection = false;
ADP.SetCurrentTransaction(next.Completion.Task.AsyncState as Transaction);
@@ -1208,13 +1231,11 @@ void WaitForPendingOpen()
Interlocked.Exchange(ref _pendingOpensWaiting, 0);
}
}
-
} while (_pendingOpens.TryPeek(out next));
}
internal bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection)
{
-
uint waitForMultipleObjectsTimeout = 0;
bool allowCreate = false;
@@ -1222,7 +1243,7 @@ internal bool TryGetConnection(DbConnection owningObject, TaskCompletionSource {0}, Getting connection.", ObjectID);
// If automatic transaction enlistment is required, then we try to
@@ -1292,13 +1316,16 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj
do
{
int waitResult = BOGUS_HANDLE;
-
+#if NETFRAMEWORK
RuntimeHelpers.PrepareConstrainedRegions();
+#endif
try
{
+#if NETFRAMEWORK
// We absolutely must have the value of waitResult set,
// or we may leak the mutex in async abort cases.
RuntimeHelpers.PrepareConstrainedRegions();
+#endif
try
{
}
@@ -1343,7 +1370,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj
}
finally
{
- // SQLBUDT #386664 - ensure that we release this waiter, regardless
+ // Ensure that we release this waiter, regardless
// of any exceptions that may be thrown.
if (obj != null)
{
@@ -1360,7 +1387,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj
// BUG - if we receive the CreationHandle midway into the wait
// period and re-wait, we will be waiting on the full period
- if (Count >= MaxPoolSize && 0 != MaxPoolSize)
+ if (Count >= MaxPoolSize && MaxPoolSize != 0)
{
if (!ReclaimEmancipatedObjects())
{
@@ -1389,7 +1416,9 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj
{
if (_waitHandles.CreationSemaphore.WaitOne(unchecked((int)waitForMultipleObjectsTimeout)))
{
+#if NETFRAMEWORK
RuntimeHelpers.PrepareConstrainedRegions();
+#endif
try
{
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectID);
@@ -1455,6 +1484,9 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj
}
connection = obj;
+#if NET
+ SqlClientEventSource.Log.SoftConnectRequest();
+#endif
return true;
}
@@ -1486,12 +1518,17 @@ private void PrepareConnection(DbConnection owningObject, DbConnectionInternal o
/// A new inner connection that is attached to the
internal DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
{
+#if NETFRAMEWORK
PerformanceCounters.SoftConnectsPerSecond.Increment();
+#endif
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", ObjectID);
DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection);
if (newConnection != null)
{
+#if NET
+ SqlClientEventSource.Log.SoftConnectRequest();
+#endif
PrepareConnection(owningObject, newConnection, oldConnection.EnlistedTransaction);
oldConnection.PrepareForReplaceConnection();
oldConnection.DeactivateConnection();
@@ -1521,16 +1558,19 @@ private DbConnectionInternal GetFromGeneralPool()
Debug.Assert(obj != null, "null connection is not expected");
}
- // SQLBUDT #356870 -- When another thread is clearing this pool,
+ // When another thread is clearing this pool,
// it will remove all connections in this pool which causes the
// following assert to fire, which really mucks up stress against
- // checked bits. The assert is benign, so we're commenting it out.
- //Debug.Assert(obj != null, "GetFromGeneralPool called with nothing in the pool!");
+ // checked bits.
if (obj != null)
{
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from general pool.", ObjectID, obj.ObjectID);
+#if NET
+ SqlClientEventSource.Log.ExitFreeConnection();
+#else
PerformanceCounters.NumberOfFreeConnections.Decrement();
+#endif
}
return (obj);
}
@@ -1547,7 +1587,11 @@ private DbConnectionInternal GetFromTransactedPool(out Transaction transaction)
if (obj != null)
{
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from transacted pool.", ObjectID, obj.ObjectID);
+#if NET
+ SqlClientEventSource.Log.ExitFreeConnection();
+#else
PerformanceCounters.NumberOfFreeConnections.Decrement();
+#endif
if (obj.IsTransactionRoot)
{
@@ -1573,8 +1617,10 @@ private DbConnectionInternal GetFromTransactedPool(out Transaction transaction)
return obj;
}
+#if NETFRAMEWORK
[ResourceExposure(ResourceScope.None)] // SxS: this method does not expose resources
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
+#endif
private void PoolCreateRequest(object state)
{
// called by pooler to ensure pool requests are currently being satisfied -
@@ -1584,7 +1630,6 @@ private void PoolCreateRequest(object state)
{
if (State.Running == _state)
{
-
// in case WaitForPendingOpen ever failed with no subsequent OpenAsync calls,
// start it back up again
if (!_pendingOpens.IsEmpty && _pendingOpensWaiting == 0)
@@ -1614,12 +1659,16 @@ private void PoolCreateRequest(object state)
}
int waitResult = BOGUS_HANDLE;
+#if NETFRAMEWORK
RuntimeHelpers.PrepareConstrainedRegions();
+#endif
try
{
// Obtain creation mutex so we're the only one creating objects
// and we must have the wait result
+#if NETFRAMEWORK
RuntimeHelpers.PrepareConstrainedRegions();
+#endif
try
{ }
finally
@@ -1675,7 +1724,6 @@ private void PoolCreateRequest(object state)
}
catch (Exception e)
{
- // UNDONE - should not be catching all exceptions!!!
if (!ADP.IsCatchableExceptionType(e))
{
throw;
@@ -1708,16 +1756,15 @@ internal void PutNewObject(DbConnectionInternal obj)
{
Debug.Assert(obj != null, "why are we adding a null object to the pool?");
- // VSTFDEVDIV 742887 - When another thread is clearing this pool, it
- // will set _cannotBePooled for all connections in this pool without prejudice which
- // causes the following assert to fire, which really mucks up stress
- // against checked bits.
- // Debug.Assert(obj.CanBePooled, "non-poolable object in pool");
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Pushing to general pool.", ObjectID, obj.ObjectID);
_stackNew.Push(obj);
_waitHandles.PoolSemaphore.Release(1);
+#if NET
+ SqlClientEventSource.Log.EnterFreeConnection();
+#else
PerformanceCounters.NumberOfFreeConnections.Increment();
+#endif
}
@@ -1725,7 +1772,11 @@ internal void PutObject(DbConnectionInternal obj, object owningObject)
{
Debug.Assert(obj != null, "null obj?");
+#if NET
+ SqlClientEventSource.Log.SoftDisconnectRequest();
+#else
PerformanceCounters.SoftDisconnectsPerSecond.Increment();
+#endif
// Once a connection is closing (which is the state that we're in at
// this point in time) you cannot delegate a transaction to or enlist
@@ -1840,7 +1891,11 @@ private bool ReclaimEmancipatedObjects()
{
DbConnectionInternal obj = reclaimedObjects[i];
SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Reclaiming.", ObjectID, obj.ObjectID);
+#if NET
+ SqlClientEventSource.Log.ReclaimedConnectionRequest();
+#else
PerformanceCounters.NumberOfReclaimedConnections.Increment();
+#endif
emancipatedObjectFound = true;