Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Data / System / Data / ProviderBase / DbConnectionPool.cs / 1305376 / DbConnectionPool.cs
//------------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //[....] //[....] //----------------------------------------------------------------------------- namespace System.Data.ProviderBase { using System; using System.Collections; using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using System.Security.Principal; using System.Threading; using SysTx = System.Transactions; using System.Runtime.Versioning; using System.Diagnostics.CodeAnalysis; sealed internal class DbConnectionPool { private enum State { Initializing, Running, ShuttingDown, } internal const Bid.ApiGroup PoolerTracePoints = Bid.ApiGroup.Pooling; // 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 SysTx.Transaction _transaction; internal TransactedConnectionList(int initialAllocation, SysTx.Transaction tx) : base(initialAllocation) { _transaction = tx; } internal void Dispose() { if (null != _transaction) { _transaction.Dispose(); } } } sealed private class TransactedConnectionPool { Dictionary _transactedCxns; DbConnectionPool _pool; private static int _objectTypeCount; // Bid counter internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); internal TransactedConnectionPool(DbConnectionPool pool) { Debug.Assert(null != pool, "null pool?"); _pool = pool; _transactedCxns = new Dictionary (); Bid.PoolerTrace(" %d#, Constructed for connection pool %d#\n", ObjectID, _pool.ObjectID); } internal int ObjectID { get { return _objectID; } } internal DbConnectionPool Pool { get { return _pool; } } internal DbConnectionInternal GetTransactedObject(SysTx.Transaction transaction) { Debug.Assert(null != transaction, "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 (null != transactedObject) { Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Popped.\n", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID); } return transactedObject; } internal void PutTransactedObject(SysTx.Transaction transaction, DbConnectionInternal transactedObject) { Debug.Assert(null != transaction, "null transaction?"); Debug.Assert(null != transactedObject, "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?"); Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Pushing.\n", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID); connections.Add(transactedObject); } } } // 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 SysTx.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?"); Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Pushing.\n", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID); connections.Add(transactedObject); } } else { Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Adding List to transacted pool.\n", 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 (null != transactionClone) { 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(); } } } Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Added.\n", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID ); } Pool.PerformanceCounters.NumberOfFreeConnections.Increment(); } internal void TransactionEnded(SysTx.Transaction transaction, DbConnectionInternal transactedObject) { Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Transaction Completed\n", 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. // lock ( _transactedCxns ) { if (_transactedCxns.TryGetValue(transaction, out connections)) { Debug.Assert ( connections != null ); // access to connections within PutTransactedObject is only synchronized via the lock // on this object. As per comments in GetTransactedObject above, multi-threaded access // with GetTransactedObject should not be possible, so there is no need to synchronize // on the connections object. // 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) { Bid.PoolerTrace(" %d#, Transaction %d#, Removing List from transacted pool.\n", 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. connections.Dispose(); } } else { //Debug.Assert ( false, "TransactionCompletedEvent fired before PutTransactedObject put the connection in the transacted pool." ); Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Transacted pool not yet created prior to transaction completing. Connection may be leaked.\n", 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) { Pool.PerformanceCounters.NumberOfFreeConnections.Decrement(); Pool.PutObjectFromTransactedPool(transactedObject); } } } private sealed class PoolWaitHandles : DbBuffer { 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 SafeHandle _poolHandle; private readonly SafeHandle _errorHandle; private readonly SafeHandle _creationHandle; private readonly int _releaseFlags; [ResourceExposure(ResourceScope.None)] // SxS: this method does not create named objects [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] internal PoolWaitHandles() : base(3*IntPtr.Size) { bool mustRelease1 = false, mustRelease2 = false, mustRelease3 = false; _poolSemaphore = new Semaphore(0, MAX_Q_SIZE); _errorEvent = new ManualResetEvent(false); _creationSemaphore = new Semaphore(1, 1); RuntimeHelpers.PrepareConstrainedRegions(); try { // because SafeWaitHandle doesn't have reliability contract _poolHandle = _poolSemaphore.SafeWaitHandle; _errorHandle = _errorEvent.SafeWaitHandle; _creationHandle = _creationSemaphore.SafeWaitHandle; _poolHandle.DangerousAddRef(ref mustRelease1); _errorHandle.DangerousAddRef(ref mustRelease2); _creationHandle.DangerousAddRef(ref mustRelease3); Debug.Assert(0 == SEMAPHORE_HANDLE, "SEMAPHORE_HANDLE"); Debug.Assert(1 == ERROR_HANDLE, "ERROR_HANDLE"); Debug.Assert(2 == CREATION_HANDLE, "CREATION_HANDLE"); WriteIntPtr(SEMAPHORE_HANDLE*IntPtr.Size, _poolHandle.DangerousGetHandle()); WriteIntPtr(ERROR_HANDLE*IntPtr.Size, _errorHandle.DangerousGetHandle()); WriteIntPtr(CREATION_HANDLE*IntPtr.Size, _creationHandle.DangerousGetHandle()); } finally { if (mustRelease1) { _releaseFlags |= 1; } if (mustRelease2) { _releaseFlags |= 2; } if (mustRelease3) { _releaseFlags |= 4; } } } internal SafeHandle CreationHandle { [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] get { return _creationHandle; } } internal Semaphore CreationSemaphore { get { return _creationSemaphore; } } internal ManualResetEvent ErrorEvent { get { return _errorEvent; } } internal Semaphore PoolSemaphore { get { return _poolSemaphore; } } protected override bool ReleaseHandle() { // NOTE: The SafeHandle class guarantees this will be called exactly once. // we know we can touch these other managed objects because of our original DangerousAddRef if (0 != (1 & _releaseFlags)) { _poolHandle.DangerousRelease(); } if (0 != (2 & _releaseFlags)) { _errorHandle.DangerousRelease(); } if (0 != (4 & _releaseFlags)) { _creationHandle.DangerousRelease(); } return base.ReleaseHandle(); } } 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 WAIT_OBJECT_0 = 0; private const int WAIT_TIMEOUT = (int)0x102; private const int WAIT_ABANDONED = (int)0x80; private const int WAIT_FAILED = -1; 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 readonly int _cleanupWait; private readonly DbConnectionPoolIdentity _identity; private readonly DbConnectionFactory _connectionFactory; private readonly DbConnectionPoolGroup _connectionPoolGroup; private readonly DbConnectionPoolGroupOptions _connectionPoolGroupOptions; private DbConnectionPoolProviderInfo _connectionPoolProviderInfo; private State _state; private readonly DbConnectionInternalListStack _stackOld = new DbConnectionInternalListStack(); private readonly DbConnectionInternalListStack _stackNew = new DbConnectionInternalListStack(); private readonly WaitCallback _poolCreateRequest; private readonly Queue _deactivateQueue; private readonly WaitCallback _deactivateCallback; 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; // Bid 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(ADP.IsWindowsNT, "Attempting to construct a connection pool on Win9x?"); Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup"); if ((null != identity) && identity.IsRestricted) { throw ADP.InternalError(ADP.InternalErrorCode.AttemptingToPoolOnRestrictedToken); } _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 } _connectionFactory = connectionFactory; _connectionPoolGroup = connectionPoolGroup; _connectionPoolGroupOptions = connectionPoolGroup.PoolGroupOptions; _connectionPoolProviderInfo = connectionPoolProviderInfo; _identity = identity; if (UseDeactivateQueue) { _deactivateQueue = new Queue(); _deactivateCallback = new WaitCallback(ProcessDeactivateQueue); } _waitHandles = new PoolWaitHandles(); _errorWait = ERROR_WAIT_DEFAULT; _errorTimer = null; // No error yet. _objectList = new List (MaxPoolSize); if(ADP.IsPlatformNT5) { _transactedConnectionPool = new TransactedConnectionPool(this); } _poolCreateRequest = new WaitCallback(PoolCreateRequest); // used by CleanupCallback _state = State.Running; Bid.PoolerTrace(" %d#, Constructed.\n", 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) // SQL BU DT 364595 - 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 DbConnectionPoolCounters PerformanceCounters { get { return _connectionFactory.PerformanceCounters; } } internal DbConnectionPoolGroup PoolGroup { get { return _connectionPoolGroup; } } internal DbConnectionPoolGroupOptions PoolGroupOptions { get { return _connectionPoolGroupOptions; } } internal DbConnectionPoolProviderInfo ProviderInfo { get { return _connectionPoolProviderInfo; } } private bool UseDeactivateQueue { get { return PoolGroupOptions.UseDeactivateQueue; } } internal bool UseLoadBalancing { get { return PoolGroupOptions.UseLoadBalancing; } } private bool UsingIntegrateSecurity { get { return (null != _identity && DbConnectionPoolIdentity.NoIdentity != _identity); } } private void CleanupCallback(Object state) { // Called when the cleanup-timer ticks over. // This is the automatic prunning 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. Bid.PoolerTrace(" %d#\n", ObjectID); // 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 */) { // We obtained a objects from the semaphore. DbConnectionInternal obj = _stackOld.SynchronizedPop(); if (null != obj) { // If we obtained one from the old stack, destroy it. PerformanceCounters.NumberOfFreeConnections.Decrement(); // Transaction roots must survive even aging out (TxEnd event will clean them up). bool shouldDestroy = true; lock (obj) { // Lock to prevent ---- 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 reslult 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 ---- 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, false) /* != WAIT_TIMEOUT */) { for(;;) { DbConnectionInternal obj = _stackNew.SynchronizedPop(); if (null == obj) break; Bid.PoolerTrace(" %d#, ChangeStacks=%d#\n", ObjectID, obj.ObjectID); Debug.Assert(!obj.IsEmancipated, "pooled object not in pool"); Debug.Assert(obj.CanBePooled, "pooled object is not poolable"); _stackOld.SynchronizedPush(obj); } _waitHandles.PoolSemaphore.Release(1); } // Queue up a request to bring us up to MinPoolSize QueuePoolCreateRequest(); } internal void Clear() { Bid.PoolerTrace(" %d#, Clearing.\n", ObjectID); DbConnectionInternal obj; // First, quickly doom everything. lock(_objectList) { int count = _objectList.Count; for (int i = 0; i < count; ++i) { obj = _objectList[i]; if (null != obj) { obj.DoNotPoolThisConnection(); } } } // Second, dispose of all the free connections. while (null != (obj = _stackNew.SynchronizedPop())) { PerformanceCounters.NumberOfFreeConnections.Decrement(); DestroyObject(obj); } while (null != (obj = _stackOld.SynchronizedPop())) { PerformanceCounters.NumberOfFreeConnections.Decrement(); DestroyObject(obj); } // Finally, reclaim everything that's emancipated (which, because // it's been doomed, will cause it to be disposed of as well) ReclaimEmancipatedObjects(); Bid.PoolerTrace(" %d#, Cleared.\n", ObjectID); } private Timer CreateCleanupTimer() { return (new Timer(new TimerCallback(this.CleanupCallback), null, _cleanupWait, _cleanupWait)); } private DbConnectionInternal CreateObject(DbConnection owningObject) { DbConnectionInternal newObj = null; try { newObj = _connectionFactory.CreatePooledConnection(owningObject, this, _connectionPoolGroup.ConnectionOptions); if (null == newObj) { 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) { _objectList.Add(newObj); _totalObjects = _objectList.Count; PerformanceCounters.NumberOfPooledConnections.Increment(); // } Bid.PoolerTrace(" %d#, Connection %d#, Added to pool.\n", ObjectID, newObj.ObjectID); // Reset the error wait: _errorWait = ERROR_WAIT_DEFAULT; } catch(Exception e) { // if (!ADP.IsCatchableExceptionType(e)) { throw; } ADP.TraceExceptionForCapture(e); newObj = null; // set to null, so we do not return bad new object // Failed to create instance _resError = e; // VSTFDEVDIV 479561: 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; RuntimeHelpers.PrepareConstrainedRegions(); 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) { Bid.PoolerTrace(" %d#, Connection %d#, Deactivating.\n", ObjectID, obj.ObjectID); obj.DeactivateConnection(); // we presume this operation is safe outside of a lock... 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 asyncronously completing on a second // thread. SysTx.Transaction transaction = obj.EnlistedTransaction; if (null != transaction) { // 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 ) { // 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 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) { Bid.PoolerTrace(" %d#, Connection %d#, Has Delegated Transaction, waiting to Dispose.\n", ObjectID, obj.ObjectID); } else { Bid.PoolerTrace(" %d#, Connection %d#, Removing from pool.\n", 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) { Bid.PoolerTrace(" %d#, Connection %d#, Removed from pool.\n", ObjectID, obj.ObjectID); PerformanceCounters.NumberOfPooledConnections.Decrement(); } obj.Dispose(); Bid.PoolerTrace(" %d#, Connection %d#, Disposed.\n", ObjectID, obj.ObjectID); PerformanceCounters.HardDisconnectsPerSecond.Increment(); } } private void ErrorCallback(Object state) { Bid.PoolerTrace(" %d#, Resetting Error handling.\n", 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. } } [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] // copied from Triaged.cs [ResourceExposure(ResourceScope.None)] // SxS: this method does not expose resources [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] internal DbConnectionInternal GetConnection(DbConnection owningObject) { DbConnectionInternal obj = null; SysTx.Transaction transaction = null; PerformanceCounters.SoftConnectsPerSecond.Increment(); if(_state != State.Running) { Bid.PoolerTrace(" %d#, DbConnectionInternal State != Running.\n", ObjectID); return null; } Bid.PoolerTrace(" %d#, Getting connection.\n", 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 (null == obj) { Interlocked.Increment(ref _waitCount); uint waitHandleCount = 3; uint waitForMultipleObjectsTimeout = (uint)CreationTimeout; // VSTFDEVDIV 445531: set the wait timeout to INFINITE (-1) if the SQL connection timeout is 0 (== infinite) if (waitForMultipleObjectsTimeout == 0) waitForMultipleObjectsTimeout = unchecked((uint)Timeout.Infinite); do { int waitResult = BOGUS_HANDLE; int releaseSemaphoreResult = 0; bool mustRelease = false; int waitForMultipleObjectsExHR = 0; RuntimeHelpers.PrepareConstrainedRegions(); try { _waitHandles.DangerousAddRef(ref mustRelease); // We absolutely must have the value of waitResult set, // or we may leak the mutex in async abort cases. RuntimeHelpers.PrepareConstrainedRegions(); try { Debug.Assert(2 == waitHandleCount || 3 == waitHandleCount, "unexpected waithandle count"); } finally { waitResult = SafeNativeMethods.WaitForMultipleObjectsEx(waitHandleCount, _waitHandles.DangerousGetHandle(), false, waitForMultipleObjectsTimeout, false); // VSTFDEVDIV 479551 - call GetHRForLastWin32Error immediately after after the native call if (waitResult == WAIT_FAILED) { waitForMultipleObjectsExHR = Marshal.GetHRForLastWin32Error(); } } // 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 WAIT_TIMEOUT: Bid.PoolerTrace(" %d#, Wait timed out.\n", ObjectID); Interlocked.Decrement(ref _waitCount); return null; case ERROR_HANDLE: // Throw the error that PoolCreateRequest stashed. Bid.PoolerTrace(" %d#, Errors are set.\n", ObjectID); Interlocked.Decrement(ref _waitCount); throw _resError; case CREATION_HANDLE: Bid.PoolerTrace(" %d#, Creating new connection.\n", ObjectID); try { obj = UserCreateRequest(owningObject); } catch { if (null == obj) { Interlocked.Decrement(ref _waitCount); } throw; } finally { // SQLBUDT #386664 - ensure that we release this waiter, regardless // of any exceptions that may be thrown. if (null != obj) { Interlocked.Decrement(ref _waitCount); } } if (null == obj) { // 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"); waitHandleCount = 2; } } } break; case SEMAPHORE_HANDLE: // // guaranteed available inventory // Interlocked.Decrement(ref _waitCount); obj = GetFromGeneralPool(); break; case WAIT_FAILED: Debug.Assert(waitForMultipleObjectsExHR != 0, "WaitForMultipleObjectsEx failed but waitForMultipleObjectsExHR remained 0"); Bid.PoolerTrace(" %d#, Wait failed.\n", ObjectID); Interlocked.Decrement(ref _waitCount); Marshal.ThrowExceptionForHR(waitForMultipleObjectsExHR); goto default; // if ThrowExceptionForHR didn't throw for some reason case (WAIT_ABANDONED+SEMAPHORE_HANDLE): Bid.PoolerTrace(" %d#, Semaphore handle abandonded.\n", ObjectID); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(SEMAPHORE_HANDLE,_waitHandles.PoolSemaphore); case (WAIT_ABANDONED+ERROR_HANDLE): Bid.PoolerTrace(" %d#, Error handle abandonded.\n", ObjectID); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(ERROR_HANDLE,_waitHandles.ErrorEvent); case (WAIT_ABANDONED+CREATION_HANDLE): Bid.PoolerTrace(" %d#, Creation handle abandoned.\n", ObjectID); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(CREATION_HANDLE,_waitHandles.CreationSemaphore); default: Bid.PoolerTrace(" %d#, WaitForMultipleObjects=%d\n", ObjectID, waitResult); Interlocked.Decrement(ref _waitCount); throw ADP.InternalError(ADP.InternalErrorCode.UnexpectedWaitAnyResult); } } finally { if (CREATION_HANDLE == waitResult) { int result = SafeNativeMethods.ReleaseSemaphore(_waitHandles.CreationHandle.DangerousGetHandle(), 1, IntPtr.Zero); if (0 == result) { // failure case releaseSemaphoreResult = Marshal.GetHRForLastWin32Error(); } } if (mustRelease) { _waitHandles.DangerousRelease(); } } if (0 != releaseSemaphoreResult) { Marshal.ThrowExceptionForHR(releaseSemaphoreResult); // will only throw if (hresult < 0) } } while (null == obj); } if (null != obj) { 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 (SecurityException) { // if Activate throws an exception // put it back in the pool or have it properly disposed of this.PutObject(obj, owningObject); throw; } } return(obj); } private DbConnectionInternal GetFromGeneralPool() { DbConnectionInternal obj = null; obj = _stackNew.SynchronizedPop(); if (null == obj) { obj = _stackOld.SynchronizedPop(); } // SQLBUDT #356870 -- 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!"); if (null != obj) { Bid.PoolerTrace(" %d#, Connection %d#, Popped from general pool.\n", ObjectID, obj.ObjectID); PerformanceCounters.NumberOfFreeConnections.Decrement(); } return(obj); } private DbConnectionInternal GetFromTransactedPool(out SysTx.Transaction transaction) { transaction = ADP.GetCurrentTransaction(); DbConnectionInternal obj = null; if (null != transaction && null != _transactedConnectionPool) { obj = _transactedConnectionPool.GetTransactedObject(transaction); if (null != obj) { Bid.PoolerTrace(" %d#, Connection %d#, Popped from transacted pool.\n", ObjectID, obj.ObjectID); PerformanceCounters.NumberOfFreeConnections.Decrement(); } } return obj; } [ResourceExposure(ResourceScope.None)] // SxS: this method does not expose resources [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] private void PoolCreateRequest(object state) { // called by pooler to ensure pool requests are currently being satisfied - // creation mutex has not been obtained IntPtr hscp; Bid.PoolerScopeEnter(out hscp, " %d#\n", ObjectID); try { if (State.Running == _state) { // 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; } bool mustRelease = false; int waitResult = BOGUS_HANDLE; uint timeout = (uint)CreationTimeout; RuntimeHelpers.PrepareConstrainedRegions(); try { _waitHandles.DangerousAddRef(ref mustRelease); // Obtain creation mutex so we're the only one creating objects // and we must have the wait result RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { waitResult = SafeNativeMethods.WaitForSingleObjectEx(_waitHandles.CreationHandle.DangerousGetHandle(), timeout, false); } if (WAIT_OBJECT_0 == waitResult) { DbConnectionInternal newObj; // Check ErrorOccurred again after obtaining mutex if (!ErrorOccurred) { while (NeedToReplenish) { newObj = CreateObject((DbConnection)null); // We do not need to check error flag here, since we know if // CreateObject returned null, we are in error case. if (null != newObj) { PutNewObject(newObj); } else { break; } } } } else if (WAIT_TIMEOUT == 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 { // trace waitResult and ignore the failure Bid.PoolerTrace(" %d#, PoolCreateRequest called WaitForSingleObject failed %d", 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. Bid.PoolerTrace(" %d#, PoolCreateRequest called CreateConnection which threw an exception: %ls", ObjectID, e); } finally { if (WAIT_OBJECT_0 == waitResult) { // reuse waitResult and ignore its value waitResult = SafeNativeMethods.ReleaseSemaphore(_waitHandles.CreationHandle.DangerousGetHandle(), 1, IntPtr.Zero); } if (mustRelease) { _waitHandles.DangerousRelease(); } } } } } } finally { Bid.ScopeLeave(ref hscp); } } private void ProcessDeactivateQueue(object state) { IntPtr hscp; Bid.PoolerScopeEnter(out hscp, " %d#\n", ObjectID); try { object[] deactivateQueue; lock (_deactivateQueue.SyncRoot) { deactivateQueue = _deactivateQueue.ToArray(); _deactivateQueue.Clear(); } foreach (DbConnectionInternal obj in deactivateQueue) { PerformanceCounters.NumberOfStasisConnections.Decrement(); DeactivateObject(obj); } } finally { Bid.ScopeLeave(ref hscp); } } internal void PutNewObject(DbConnectionInternal obj) { Debug.Assert(null != obj, "why are we adding a null object to the pool?"); Debug.Assert(obj.CanBePooled, "non-poolable object in pool"); Bid.PoolerTrace(" %d#, Connection %d#, Pushing to general pool.\n", ObjectID, obj.ObjectID); _stackNew.SynchronizedPush(obj); _waitHandles.PoolSemaphore.Release(1); PerformanceCounters.NumberOfFreeConnections.Increment(); } internal void PutObject(DbConnectionInternal obj, object owningObject) { Debug.Assert(null != obj, "null obj?"); PerformanceCounters.SoftDisconnectsPerSecond.Increment(); // 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 we drop the ball somewhere. obj.PrePush(owningObject); // } if (UseDeactivateQueue) { // If we're using the DeactivateQueue, we'll just queue it up and // be done; all the hard work will be done on the despooler thread. bool needToQueueWorkItem; Bid.PoolerTrace(" %d#, Connection %d#, Queueing for deactivation.\n", ObjectID, obj.ObjectID); PerformanceCounters.NumberOfStasisConnections.Increment(); lock (_deactivateQueue.SyncRoot) { needToQueueWorkItem = (0 == _deactivateQueue.Count); _deactivateQueue.Enqueue(obj); } if (needToQueueWorkItem) { // Make sure we actually get around to deactivating the object // and making it available again. ThreadPool.QueueUserWorkItem(_deactivateCallback, null); } } else { // no deactivate queue -- do the work right now. DeactivateObject(obj); } } internal void PutObjectFromTransactedPool(DbConnectionInternal obj) { Debug.Assert(null != obj, "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. Bid.PoolerTrace(" %d#, Connection %d#, Transaction has ended.\n", 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; Bid.PoolerTrace(" %d#\n", ObjectID); List reclaimedObjects = new List (); int count; lock(_objectList) { count = _objectList.Count; for (int i = 0; i < count; ++i) { DbConnectionInternal obj = _objectList[i]; if (null != obj) { 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]; Bid.PoolerTrace(" %d#, Connection %d#, Reclaiming.\n", ObjectID, obj.ObjectID); PerformanceCounters.NumberOfReclaimedConnections.Increment(); emancipatedObjectFound = true; // NOTE: it is not possible for us to have a connection that has // a delegated transaction at this point, because IsEmancipated // would not have returned true if it did, and when a connection // is emancipated, you can't enlist in a transaction (because you // can't get to it to make the call...) DeactivateObject(obj); } return emancipatedObjectFound; } internal void Startup() { Bid.PoolerTrace(" %d#, CleanupWait=%d\n", ObjectID, _cleanupWait); _cleanupTimer = CreateCleanupTimer(); if (NeedToReplenish) { QueuePoolCreateRequest(); } } internal void Shutdown() { Bid.PoolerTrace(" %d#\n", ObjectID); _state = State.ShuttingDown; Timer t; // deactivate timer callbacks t = _cleanupTimer; _cleanupTimer = null; if (null != t) { t.Dispose(); } t = _errorTimer; _errorTimer = null; if (null != t) { 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(SysTx.Transaction transaction, DbConnectionInternal transactedObject) { Debug.Assert(null != transaction, "null transaction?"); Debug.Assert(null != transactedObject, "null transactedObject?"); // Note: connection may still be associated with transaction due to Explicit Unbinding requirement. Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Transaction Completed\n", 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 (null != transactedConnectionPool) { transactedConnectionPool.TransactionEnded(transaction, transactedObject); } } private DbConnectionInternal UserCreateRequest(DbConnection owningObject) { // called by user when they were not able to obtain a free object but // instead obtained creation mutex DbConnectionInternal obj = null; if (ErrorOccurred) { throw _resError; } else { if ((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 ((Count & 0x1) == 0x1 || !ReclaimEmancipatedObjects()) obj = CreateObject(owningObject); } return obj; } } private class DbConnectionInternalListStack { private DbConnectionInternal _stack; #if DEBUG private int _version; private int _count; #endif internal DbConnectionInternalListStack() { } internal int Count { get { int count = 0; lock(this) { for(DbConnectionInternal x = _stack; null != x; x = x.NextPooledObject) { ++count; } } #if DEBUG Debug.Assert(count == _count, "count is corrupt"); #endif return count; } } internal DbConnectionInternal SynchronizedPop() { DbConnectionInternal value; lock(this) { value = _stack; if (null != value) { _stack = value.NextPooledObject; value.NextPooledObject = null; #if DEBUG _version++; _count--; #endif } #if DEBUG Debug.Assert((null != value || 0 == _count) && (0 <= _count), "broken SynchronizedPop"); #endif } return value; } internal void SynchronizedPush(DbConnectionInternal value) { Debug.Assert(null != value, "pushing null value"); lock(this) { #if DEBUG Debug.Assert(null == value.NextPooledObject, "pushing value with non-null NextPooledObject"); int index = 0; for(DbConnectionInternal x = _stack; null != x; x = x.NextPooledObject, ++index) { Debug.Assert(x != value, "double push: connection already in stack"); } Debug.Assert(_count == index, "SynchronizedPush count is corrupt"); #endif value.NextPooledObject = _stack; _stack = value; #if DEBUG _version++; _count++; #endif } } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //[....] //[....] //----------------------------------------------------------------------------- namespace System.Data.ProviderBase { using System; using System.Collections; using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using System.Security.Principal; using System.Threading; using SysTx = System.Transactions; using System.Runtime.Versioning; using System.Diagnostics.CodeAnalysis; sealed internal class DbConnectionPool { private enum State { Initializing, Running, ShuttingDown, } internal const Bid.ApiGroup PoolerTracePoints = Bid.ApiGroup.Pooling; // 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 SysTx.Transaction _transaction; internal TransactedConnectionList(int initialAllocation, SysTx.Transaction tx) : base(initialAllocation) { _transaction = tx; } internal void Dispose() { if (null != _transaction) { _transaction.Dispose(); } } } sealed private class TransactedConnectionPool { Dictionary _transactedCxns; DbConnectionPool _pool; private static int _objectTypeCount; // Bid counter internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); internal TransactedConnectionPool(DbConnectionPool pool) { Debug.Assert(null != pool, "null pool?"); _pool = pool; _transactedCxns = new Dictionary (); Bid.PoolerTrace(" %d#, Constructed for connection pool %d#\n", ObjectID, _pool.ObjectID); } internal int ObjectID { get { return _objectID; } } internal DbConnectionPool Pool { get { return _pool; } } internal DbConnectionInternal GetTransactedObject(SysTx.Transaction transaction) { Debug.Assert(null != transaction, "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 (null != transactedObject) { Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Popped.\n", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID); } return transactedObject; } internal void PutTransactedObject(SysTx.Transaction transaction, DbConnectionInternal transactedObject) { Debug.Assert(null != transaction, "null transaction?"); Debug.Assert(null != transactedObject, "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?"); Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Pushing.\n", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID); connections.Add(transactedObject); } } } // 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 SysTx.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?"); Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Pushing.\n", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID); connections.Add(transactedObject); } } else { Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Adding List to transacted pool.\n", 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 (null != transactionClone) { 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(); } } } Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Added.\n", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID ); } Pool.PerformanceCounters.NumberOfFreeConnections.Increment(); } internal void TransactionEnded(SysTx.Transaction transaction, DbConnectionInternal transactedObject) { Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Transaction Completed\n", 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. // lock ( _transactedCxns ) { if (_transactedCxns.TryGetValue(transaction, out connections)) { Debug.Assert ( connections != null ); // access to connections within PutTransactedObject is only synchronized via the lock // on this object. As per comments in GetTransactedObject above, multi-threaded access // with GetTransactedObject should not be possible, so there is no need to synchronize // on the connections object. // 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) { Bid.PoolerTrace(" %d#, Transaction %d#, Removing List from transacted pool.\n", 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. connections.Dispose(); } } else { //Debug.Assert ( false, "TransactionCompletedEvent fired before PutTransactedObject put the connection in the transacted pool." ); Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Transacted pool not yet created prior to transaction completing. Connection may be leaked.\n", 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) { Pool.PerformanceCounters.NumberOfFreeConnections.Decrement(); Pool.PutObjectFromTransactedPool(transactedObject); } } } private sealed class PoolWaitHandles : DbBuffer { 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 SafeHandle _poolHandle; private readonly SafeHandle _errorHandle; private readonly SafeHandle _creationHandle; private readonly int _releaseFlags; [ResourceExposure(ResourceScope.None)] // SxS: this method does not create named objects [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] internal PoolWaitHandles() : base(3*IntPtr.Size) { bool mustRelease1 = false, mustRelease2 = false, mustRelease3 = false; _poolSemaphore = new Semaphore(0, MAX_Q_SIZE); _errorEvent = new ManualResetEvent(false); _creationSemaphore = new Semaphore(1, 1); RuntimeHelpers.PrepareConstrainedRegions(); try { // because SafeWaitHandle doesn't have reliability contract _poolHandle = _poolSemaphore.SafeWaitHandle; _errorHandle = _errorEvent.SafeWaitHandle; _creationHandle = _creationSemaphore.SafeWaitHandle; _poolHandle.DangerousAddRef(ref mustRelease1); _errorHandle.DangerousAddRef(ref mustRelease2); _creationHandle.DangerousAddRef(ref mustRelease3); Debug.Assert(0 == SEMAPHORE_HANDLE, "SEMAPHORE_HANDLE"); Debug.Assert(1 == ERROR_HANDLE, "ERROR_HANDLE"); Debug.Assert(2 == CREATION_HANDLE, "CREATION_HANDLE"); WriteIntPtr(SEMAPHORE_HANDLE*IntPtr.Size, _poolHandle.DangerousGetHandle()); WriteIntPtr(ERROR_HANDLE*IntPtr.Size, _errorHandle.DangerousGetHandle()); WriteIntPtr(CREATION_HANDLE*IntPtr.Size, _creationHandle.DangerousGetHandle()); } finally { if (mustRelease1) { _releaseFlags |= 1; } if (mustRelease2) { _releaseFlags |= 2; } if (mustRelease3) { _releaseFlags |= 4; } } } internal SafeHandle CreationHandle { [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] get { return _creationHandle; } } internal Semaphore CreationSemaphore { get { return _creationSemaphore; } } internal ManualResetEvent ErrorEvent { get { return _errorEvent; } } internal Semaphore PoolSemaphore { get { return _poolSemaphore; } } protected override bool ReleaseHandle() { // NOTE: The SafeHandle class guarantees this will be called exactly once. // we know we can touch these other managed objects because of our original DangerousAddRef if (0 != (1 & _releaseFlags)) { _poolHandle.DangerousRelease(); } if (0 != (2 & _releaseFlags)) { _errorHandle.DangerousRelease(); } if (0 != (4 & _releaseFlags)) { _creationHandle.DangerousRelease(); } return base.ReleaseHandle(); } } 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 WAIT_OBJECT_0 = 0; private const int WAIT_TIMEOUT = (int)0x102; private const int WAIT_ABANDONED = (int)0x80; private const int WAIT_FAILED = -1; 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 readonly int _cleanupWait; private readonly DbConnectionPoolIdentity _identity; private readonly DbConnectionFactory _connectionFactory; private readonly DbConnectionPoolGroup _connectionPoolGroup; private readonly DbConnectionPoolGroupOptions _connectionPoolGroupOptions; private DbConnectionPoolProviderInfo _connectionPoolProviderInfo; private State _state; private readonly DbConnectionInternalListStack _stackOld = new DbConnectionInternalListStack(); private readonly DbConnectionInternalListStack _stackNew = new DbConnectionInternalListStack(); private readonly WaitCallback _poolCreateRequest; private readonly Queue _deactivateQueue; private readonly WaitCallback _deactivateCallback; 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; // Bid 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(ADP.IsWindowsNT, "Attempting to construct a connection pool on Win9x?"); Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup"); if ((null != identity) && identity.IsRestricted) { throw ADP.InternalError(ADP.InternalErrorCode.AttemptingToPoolOnRestrictedToken); } _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 } _connectionFactory = connectionFactory; _connectionPoolGroup = connectionPoolGroup; _connectionPoolGroupOptions = connectionPoolGroup.PoolGroupOptions; _connectionPoolProviderInfo = connectionPoolProviderInfo; _identity = identity; if (UseDeactivateQueue) { _deactivateQueue = new Queue(); _deactivateCallback = new WaitCallback(ProcessDeactivateQueue); } _waitHandles = new PoolWaitHandles(); _errorWait = ERROR_WAIT_DEFAULT; _errorTimer = null; // No error yet. _objectList = new List (MaxPoolSize); if(ADP.IsPlatformNT5) { _transactedConnectionPool = new TransactedConnectionPool(this); } _poolCreateRequest = new WaitCallback(PoolCreateRequest); // used by CleanupCallback _state = State.Running; Bid.PoolerTrace(" %d#, Constructed.\n", 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) // SQL BU DT 364595 - 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 DbConnectionPoolCounters PerformanceCounters { get { return _connectionFactory.PerformanceCounters; } } internal DbConnectionPoolGroup PoolGroup { get { return _connectionPoolGroup; } } internal DbConnectionPoolGroupOptions PoolGroupOptions { get { return _connectionPoolGroupOptions; } } internal DbConnectionPoolProviderInfo ProviderInfo { get { return _connectionPoolProviderInfo; } } private bool UseDeactivateQueue { get { return PoolGroupOptions.UseDeactivateQueue; } } internal bool UseLoadBalancing { get { return PoolGroupOptions.UseLoadBalancing; } } private bool UsingIntegrateSecurity { get { return (null != _identity && DbConnectionPoolIdentity.NoIdentity != _identity); } } private void CleanupCallback(Object state) { // Called when the cleanup-timer ticks over. // This is the automatic prunning 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. Bid.PoolerTrace(" %d#\n", ObjectID); // 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 */) { // We obtained a objects from the semaphore. DbConnectionInternal obj = _stackOld.SynchronizedPop(); if (null != obj) { // If we obtained one from the old stack, destroy it. PerformanceCounters.NumberOfFreeConnections.Decrement(); // Transaction roots must survive even aging out (TxEnd event will clean them up). bool shouldDestroy = true; lock (obj) { // Lock to prevent ---- 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 reslult 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 ---- 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, false) /* != WAIT_TIMEOUT */) { for(;;) { DbConnectionInternal obj = _stackNew.SynchronizedPop(); if (null == obj) break; Bid.PoolerTrace(" %d#, ChangeStacks=%d#\n", ObjectID, obj.ObjectID); Debug.Assert(!obj.IsEmancipated, "pooled object not in pool"); Debug.Assert(obj.CanBePooled, "pooled object is not poolable"); _stackOld.SynchronizedPush(obj); } _waitHandles.PoolSemaphore.Release(1); } // Queue up a request to bring us up to MinPoolSize QueuePoolCreateRequest(); } internal void Clear() { Bid.PoolerTrace(" %d#, Clearing.\n", ObjectID); DbConnectionInternal obj; // First, quickly doom everything. lock(_objectList) { int count = _objectList.Count; for (int i = 0; i < count; ++i) { obj = _objectList[i]; if (null != obj) { obj.DoNotPoolThisConnection(); } } } // Second, dispose of all the free connections. while (null != (obj = _stackNew.SynchronizedPop())) { PerformanceCounters.NumberOfFreeConnections.Decrement(); DestroyObject(obj); } while (null != (obj = _stackOld.SynchronizedPop())) { PerformanceCounters.NumberOfFreeConnections.Decrement(); DestroyObject(obj); } // Finally, reclaim everything that's emancipated (which, because // it's been doomed, will cause it to be disposed of as well) ReclaimEmancipatedObjects(); Bid.PoolerTrace(" %d#, Cleared.\n", ObjectID); } private Timer CreateCleanupTimer() { return (new Timer(new TimerCallback(this.CleanupCallback), null, _cleanupWait, _cleanupWait)); } private DbConnectionInternal CreateObject(DbConnection owningObject) { DbConnectionInternal newObj = null; try { newObj = _connectionFactory.CreatePooledConnection(owningObject, this, _connectionPoolGroup.ConnectionOptions); if (null == newObj) { 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) { _objectList.Add(newObj); _totalObjects = _objectList.Count; PerformanceCounters.NumberOfPooledConnections.Increment(); // } Bid.PoolerTrace(" %d#, Connection %d#, Added to pool.\n", ObjectID, newObj.ObjectID); // Reset the error wait: _errorWait = ERROR_WAIT_DEFAULT; } catch(Exception e) { // if (!ADP.IsCatchableExceptionType(e)) { throw; } ADP.TraceExceptionForCapture(e); newObj = null; // set to null, so we do not return bad new object // Failed to create instance _resError = e; // VSTFDEVDIV 479561: 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; RuntimeHelpers.PrepareConstrainedRegions(); 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) { Bid.PoolerTrace(" %d#, Connection %d#, Deactivating.\n", ObjectID, obj.ObjectID); obj.DeactivateConnection(); // we presume this operation is safe outside of a lock... 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 asyncronously completing on a second // thread. SysTx.Transaction transaction = obj.EnlistedTransaction; if (null != transaction) { // 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 ) { // 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 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) { Bid.PoolerTrace(" %d#, Connection %d#, Has Delegated Transaction, waiting to Dispose.\n", ObjectID, obj.ObjectID); } else { Bid.PoolerTrace(" %d#, Connection %d#, Removing from pool.\n", 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) { Bid.PoolerTrace(" %d#, Connection %d#, Removed from pool.\n", ObjectID, obj.ObjectID); PerformanceCounters.NumberOfPooledConnections.Decrement(); } obj.Dispose(); Bid.PoolerTrace(" %d#, Connection %d#, Disposed.\n", ObjectID, obj.ObjectID); PerformanceCounters.HardDisconnectsPerSecond.Increment(); } } private void ErrorCallback(Object state) { Bid.PoolerTrace(" %d#, Resetting Error handling.\n", 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. } } [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] // copied from Triaged.cs [ResourceExposure(ResourceScope.None)] // SxS: this method does not expose resources [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] internal DbConnectionInternal GetConnection(DbConnection owningObject) { DbConnectionInternal obj = null; SysTx.Transaction transaction = null; PerformanceCounters.SoftConnectsPerSecond.Increment(); if(_state != State.Running) { Bid.PoolerTrace(" %d#, DbConnectionInternal State != Running.\n", ObjectID); return null; } Bid.PoolerTrace(" %d#, Getting connection.\n", 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 (null == obj) { Interlocked.Increment(ref _waitCount); uint waitHandleCount = 3; uint waitForMultipleObjectsTimeout = (uint)CreationTimeout; // VSTFDEVDIV 445531: set the wait timeout to INFINITE (-1) if the SQL connection timeout is 0 (== infinite) if (waitForMultipleObjectsTimeout == 0) waitForMultipleObjectsTimeout = unchecked((uint)Timeout.Infinite); do { int waitResult = BOGUS_HANDLE; int releaseSemaphoreResult = 0; bool mustRelease = false; int waitForMultipleObjectsExHR = 0; RuntimeHelpers.PrepareConstrainedRegions(); try { _waitHandles.DangerousAddRef(ref mustRelease); // We absolutely must have the value of waitResult set, // or we may leak the mutex in async abort cases. RuntimeHelpers.PrepareConstrainedRegions(); try { Debug.Assert(2 == waitHandleCount || 3 == waitHandleCount, "unexpected waithandle count"); } finally { waitResult = SafeNativeMethods.WaitForMultipleObjectsEx(waitHandleCount, _waitHandles.DangerousGetHandle(), false, waitForMultipleObjectsTimeout, false); // VSTFDEVDIV 479551 - call GetHRForLastWin32Error immediately after after the native call if (waitResult == WAIT_FAILED) { waitForMultipleObjectsExHR = Marshal.GetHRForLastWin32Error(); } } // 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 WAIT_TIMEOUT: Bid.PoolerTrace(" %d#, Wait timed out.\n", ObjectID); Interlocked.Decrement(ref _waitCount); return null; case ERROR_HANDLE: // Throw the error that PoolCreateRequest stashed. Bid.PoolerTrace(" %d#, Errors are set.\n", ObjectID); Interlocked.Decrement(ref _waitCount); throw _resError; case CREATION_HANDLE: Bid.PoolerTrace(" %d#, Creating new connection.\n", ObjectID); try { obj = UserCreateRequest(owningObject); } catch { if (null == obj) { Interlocked.Decrement(ref _waitCount); } throw; } finally { // SQLBUDT #386664 - ensure that we release this waiter, regardless // of any exceptions that may be thrown. if (null != obj) { Interlocked.Decrement(ref _waitCount); } } if (null == obj) { // 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"); waitHandleCount = 2; } } } break; case SEMAPHORE_HANDLE: // // guaranteed available inventory // Interlocked.Decrement(ref _waitCount); obj = GetFromGeneralPool(); break; case WAIT_FAILED: Debug.Assert(waitForMultipleObjectsExHR != 0, "WaitForMultipleObjectsEx failed but waitForMultipleObjectsExHR remained 0"); Bid.PoolerTrace(" %d#, Wait failed.\n", ObjectID); Interlocked.Decrement(ref _waitCount); Marshal.ThrowExceptionForHR(waitForMultipleObjectsExHR); goto default; // if ThrowExceptionForHR didn't throw for some reason case (WAIT_ABANDONED+SEMAPHORE_HANDLE): Bid.PoolerTrace(" %d#, Semaphore handle abandonded.\n", ObjectID); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(SEMAPHORE_HANDLE,_waitHandles.PoolSemaphore); case (WAIT_ABANDONED+ERROR_HANDLE): Bid.PoolerTrace(" %d#, Error handle abandonded.\n", ObjectID); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(ERROR_HANDLE,_waitHandles.ErrorEvent); case (WAIT_ABANDONED+CREATION_HANDLE): Bid.PoolerTrace(" %d#, Creation handle abandoned.\n", ObjectID); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(CREATION_HANDLE,_waitHandles.CreationSemaphore); default: Bid.PoolerTrace(" %d#, WaitForMultipleObjects=%d\n", ObjectID, waitResult); Interlocked.Decrement(ref _waitCount); throw ADP.InternalError(ADP.InternalErrorCode.UnexpectedWaitAnyResult); } } finally { if (CREATION_HANDLE == waitResult) { int result = SafeNativeMethods.ReleaseSemaphore(_waitHandles.CreationHandle.DangerousGetHandle(), 1, IntPtr.Zero); if (0 == result) { // failure case releaseSemaphoreResult = Marshal.GetHRForLastWin32Error(); } } if (mustRelease) { _waitHandles.DangerousRelease(); } } if (0 != releaseSemaphoreResult) { Marshal.ThrowExceptionForHR(releaseSemaphoreResult); // will only throw if (hresult < 0) } } while (null == obj); } if (null != obj) { 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 (SecurityException) { // if Activate throws an exception // put it back in the pool or have it properly disposed of this.PutObject(obj, owningObject); throw; } } return(obj); } private DbConnectionInternal GetFromGeneralPool() { DbConnectionInternal obj = null; obj = _stackNew.SynchronizedPop(); if (null == obj) { obj = _stackOld.SynchronizedPop(); } // SQLBUDT #356870 -- 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!"); if (null != obj) { Bid.PoolerTrace(" %d#, Connection %d#, Popped from general pool.\n", ObjectID, obj.ObjectID); PerformanceCounters.NumberOfFreeConnections.Decrement(); } return(obj); } private DbConnectionInternal GetFromTransactedPool(out SysTx.Transaction transaction) { transaction = ADP.GetCurrentTransaction(); DbConnectionInternal obj = null; if (null != transaction && null != _transactedConnectionPool) { obj = _transactedConnectionPool.GetTransactedObject(transaction); if (null != obj) { Bid.PoolerTrace(" %d#, Connection %d#, Popped from transacted pool.\n", ObjectID, obj.ObjectID); PerformanceCounters.NumberOfFreeConnections.Decrement(); } } return obj; } [ResourceExposure(ResourceScope.None)] // SxS: this method does not expose resources [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] private void PoolCreateRequest(object state) { // called by pooler to ensure pool requests are currently being satisfied - // creation mutex has not been obtained IntPtr hscp; Bid.PoolerScopeEnter(out hscp, " %d#\n", ObjectID); try { if (State.Running == _state) { // 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; } bool mustRelease = false; int waitResult = BOGUS_HANDLE; uint timeout = (uint)CreationTimeout; RuntimeHelpers.PrepareConstrainedRegions(); try { _waitHandles.DangerousAddRef(ref mustRelease); // Obtain creation mutex so we're the only one creating objects // and we must have the wait result RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { waitResult = SafeNativeMethods.WaitForSingleObjectEx(_waitHandles.CreationHandle.DangerousGetHandle(), timeout, false); } if (WAIT_OBJECT_0 == waitResult) { DbConnectionInternal newObj; // Check ErrorOccurred again after obtaining mutex if (!ErrorOccurred) { while (NeedToReplenish) { newObj = CreateObject((DbConnection)null); // We do not need to check error flag here, since we know if // CreateObject returned null, we are in error case. if (null != newObj) { PutNewObject(newObj); } else { break; } } } } else if (WAIT_TIMEOUT == 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 { // trace waitResult and ignore the failure Bid.PoolerTrace(" %d#, PoolCreateRequest called WaitForSingleObject failed %d", 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. Bid.PoolerTrace(" %d#, PoolCreateRequest called CreateConnection which threw an exception: %ls", ObjectID, e); } finally { if (WAIT_OBJECT_0 == waitResult) { // reuse waitResult and ignore its value waitResult = SafeNativeMethods.ReleaseSemaphore(_waitHandles.CreationHandle.DangerousGetHandle(), 1, IntPtr.Zero); } if (mustRelease) { _waitHandles.DangerousRelease(); } } } } } } finally { Bid.ScopeLeave(ref hscp); } } private void ProcessDeactivateQueue(object state) { IntPtr hscp; Bid.PoolerScopeEnter(out hscp, " %d#\n", ObjectID); try { object[] deactivateQueue; lock (_deactivateQueue.SyncRoot) { deactivateQueue = _deactivateQueue.ToArray(); _deactivateQueue.Clear(); } foreach (DbConnectionInternal obj in deactivateQueue) { PerformanceCounters.NumberOfStasisConnections.Decrement(); DeactivateObject(obj); } } finally { Bid.ScopeLeave(ref hscp); } } internal void PutNewObject(DbConnectionInternal obj) { Debug.Assert(null != obj, "why are we adding a null object to the pool?"); Debug.Assert(obj.CanBePooled, "non-poolable object in pool"); Bid.PoolerTrace(" %d#, Connection %d#, Pushing to general pool.\n", ObjectID, obj.ObjectID); _stackNew.SynchronizedPush(obj); _waitHandles.PoolSemaphore.Release(1); PerformanceCounters.NumberOfFreeConnections.Increment(); } internal void PutObject(DbConnectionInternal obj, object owningObject) { Debug.Assert(null != obj, "null obj?"); PerformanceCounters.SoftDisconnectsPerSecond.Increment(); // 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 we drop the ball somewhere. obj.PrePush(owningObject); // } if (UseDeactivateQueue) { // If we're using the DeactivateQueue, we'll just queue it up and // be done; all the hard work will be done on the despooler thread. bool needToQueueWorkItem; Bid.PoolerTrace(" %d#, Connection %d#, Queueing for deactivation.\n", ObjectID, obj.ObjectID); PerformanceCounters.NumberOfStasisConnections.Increment(); lock (_deactivateQueue.SyncRoot) { needToQueueWorkItem = (0 == _deactivateQueue.Count); _deactivateQueue.Enqueue(obj); } if (needToQueueWorkItem) { // Make sure we actually get around to deactivating the object // and making it available again. ThreadPool.QueueUserWorkItem(_deactivateCallback, null); } } else { // no deactivate queue -- do the work right now. DeactivateObject(obj); } } internal void PutObjectFromTransactedPool(DbConnectionInternal obj) { Debug.Assert(null != obj, "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. Bid.PoolerTrace(" %d#, Connection %d#, Transaction has ended.\n", 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; Bid.PoolerTrace(" %d#\n", ObjectID); List reclaimedObjects = new List (); int count; lock(_objectList) { count = _objectList.Count; for (int i = 0; i < count; ++i) { DbConnectionInternal obj = _objectList[i]; if (null != obj) { 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]; Bid.PoolerTrace(" %d#, Connection %d#, Reclaiming.\n", ObjectID, obj.ObjectID); PerformanceCounters.NumberOfReclaimedConnections.Increment(); emancipatedObjectFound = true; // NOTE: it is not possible for us to have a connection that has // a delegated transaction at this point, because IsEmancipated // would not have returned true if it did, and when a connection // is emancipated, you can't enlist in a transaction (because you // can't get to it to make the call...) DeactivateObject(obj); } return emancipatedObjectFound; } internal void Startup() { Bid.PoolerTrace(" %d#, CleanupWait=%d\n", ObjectID, _cleanupWait); _cleanupTimer = CreateCleanupTimer(); if (NeedToReplenish) { QueuePoolCreateRequest(); } } internal void Shutdown() { Bid.PoolerTrace(" %d#\n", ObjectID); _state = State.ShuttingDown; Timer t; // deactivate timer callbacks t = _cleanupTimer; _cleanupTimer = null; if (null != t) { t.Dispose(); } t = _errorTimer; _errorTimer = null; if (null != t) { 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(SysTx.Transaction transaction, DbConnectionInternal transactedObject) { Debug.Assert(null != transaction, "null transaction?"); Debug.Assert(null != transactedObject, "null transactedObject?"); // Note: connection may still be associated with transaction due to Explicit Unbinding requirement. Bid.PoolerTrace(" %d#, Transaction %d#, Connection %d#, Transaction Completed\n", 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 (null != transactedConnectionPool) { transactedConnectionPool.TransactionEnded(transaction, transactedObject); } } private DbConnectionInternal UserCreateRequest(DbConnection owningObject) { // called by user when they were not able to obtain a free object but // instead obtained creation mutex DbConnectionInternal obj = null; if (ErrorOccurred) { throw _resError; } else { if ((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 ((Count & 0x1) == 0x1 || !ReclaimEmancipatedObjects()) obj = CreateObject(owningObject); } return obj; } } private class DbConnectionInternalListStack { private DbConnectionInternal _stack; #if DEBUG private int _version; private int _count; #endif internal DbConnectionInternalListStack() { } internal int Count { get { int count = 0; lock(this) { for(DbConnectionInternal x = _stack; null != x; x = x.NextPooledObject) { ++count; } } #if DEBUG Debug.Assert(count == _count, "count is corrupt"); #endif return count; } } internal DbConnectionInternal SynchronizedPop() { DbConnectionInternal value; lock(this) { value = _stack; if (null != value) { _stack = value.NextPooledObject; value.NextPooledObject = null; #if DEBUG _version++; _count--; #endif } #if DEBUG Debug.Assert((null != value || 0 == _count) && (0 <= _count), "broken SynchronizedPop"); #endif } return value; } internal void SynchronizedPush(DbConnectionInternal value) { Debug.Assert(null != value, "pushing null value"); lock(this) { #if DEBUG Debug.Assert(null == value.NextPooledObject, "pushing value with non-null NextPooledObject"); int index = 0; for(DbConnectionInternal x = _stack; null != x; x = x.NextPooledObject, ++index) { Debug.Assert(x != value, "double push: connection already in stack"); } Debug.Assert(_count == index, "SynchronizedPush count is corrupt"); #endif value.NextPooledObject = _stack; _stack = value; #if DEBUG _version++; _count++; #endif } } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- QueueProcessor.cs
- FlowDocumentView.cs
- EventRecord.cs
- EventLogger.cs
- MSAAEventDispatcher.cs
- AuthenticationServiceManager.cs
- DeleteMemberBinder.cs
- SimpleWebHandlerParser.cs
- TextModifier.cs
- FormatConvertedBitmap.cs
- TextServicesPropertyRanges.cs
- Rethrow.cs
- xmlglyphRunInfo.cs
- Model3DGroup.cs
- SqlGatherConsumedAliases.cs
- TempEnvironment.cs
- MailMessageEventArgs.cs
- LocalClientSecuritySettings.cs
- MenuCommand.cs
- WmlListAdapter.cs
- ProtocolImporter.cs
- WebServiceBindingAttribute.cs
- SafeSecurityHelper.cs
- OutputCacheSection.cs
- TreeNodeConverter.cs
- _emptywebproxy.cs
- WindowsFormsSynchronizationContext.cs
- InkCanvasInnerCanvas.cs
- GridViewAutomationPeer.cs
- ThreadPool.cs
- XmlNodeComparer.cs
- XmlQualifiedNameTest.cs
- TemplateBindingExpression.cs
- SqlVersion.cs
- Parser.cs
- TcpTransportSecurity.cs
- TransformConverter.cs
- UpdatePanelTrigger.cs
- XamlPointCollectionSerializer.cs
- DurationConverter.cs
- CodeTypeOfExpression.cs
- DecimalConstantAttribute.cs
- ReadOnlyDictionary.cs
- WmpBitmapEncoder.cs
- ContextMarshalException.cs
- NativeObjectSecurity.cs
- ScriptingAuthenticationServiceSection.cs
- OutputWindow.cs
- Container.cs
- WindowsFormsLinkLabel.cs
- AutomationPatternInfo.cs
- ListControlConvertEventArgs.cs
- ExtensionSimplifierMarkupObject.cs
- ObjectStateEntryBaseUpdatableDataRecord.cs
- RealProxy.cs
- CngProvider.cs
- TemplateBaseAction.cs
- CannotUnloadAppDomainException.cs
- PageAsyncTask.cs
- ProviderCommandInfoUtils.cs
- PhysicalFontFamily.cs
- AuthenticationModulesSection.cs
- DrawingContextDrawingContextWalker.cs
- SharedPersonalizationStateInfo.cs
- DiagnosticsConfigurationHandler.cs
- DesignerAutoFormat.cs
- MouseBinding.cs
- odbcmetadatafactory.cs
- BufferedGraphicsManager.cs
- Switch.cs
- TextRunProperties.cs
- GridViewColumn.cs
- WindowsEditBox.cs
- DbBuffer.cs
- PageRequestManager.cs
- ProjectionCamera.cs
- AggregationMinMaxHelpers.cs
- WebPartMenu.cs
- CalculatedColumn.cs
- InteropBitmapSource.cs
- DataControlLinkButton.cs
- XmlSchemaValidator.cs
- ConfigurationStrings.cs
- DefaultWorkflowTransactionService.cs
- SqlCaseSimplifier.cs
- RequestDescription.cs
- AnnotationElement.cs
- CultureInfoConverter.cs
- InstanceOwner.cs
- NamespaceInfo.cs
- MetabaseSettingsIis7.cs
- WebBrowserHelper.cs
- FactoryId.cs
- MarkerProperties.cs
- Asn1IntegerConverter.cs
- CalendarData.cs
- StandardOleMarshalObject.cs
- Size.cs
- Privilege.cs
- CheckableControlBaseAdapter.cs