1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Data.Common;
7 using System.Diagnostics;
8 using System.Threading;
9 using System.Threading.Tasks;
10 using System.Collections.Concurrent;
11 
12 namespace System.Data.ProviderBase
13 {
14     internal sealed class DbConnectionPool
15     {
16         private enum State
17         {
18             Initializing,
19             Running,
20             ShuttingDown,
21         }
22 
23 
24         private sealed class PendingGetConnection
25         {
PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSource<DbConnectionInternal> completion, DbConnectionOptions userOptions)26             public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSource<DbConnectionInternal> completion, DbConnectionOptions userOptions)
27             {
28                 DueTime = dueTime;
29                 Owner = owner;
30                 Completion = completion;
31             }
32             public long DueTime { get; private set; }
33             public DbConnection Owner { get; private set; }
34             public TaskCompletionSource<DbConnectionInternal> Completion { get; private set; }
35             public DbConnectionOptions UserOptions { get; private set; }
36         }
37 
38 
39         private sealed class PoolWaitHandles
40         {
41             private readonly Semaphore _poolSemaphore;
42             private readonly ManualResetEvent _errorEvent;
43 
44             // Using a Mutex requires ThreadAffinity because SQL CLR can swap
45             // the underlying Win32 thread associated with a managed thread in preemptive mode.
46             // Using an AutoResetEvent does not have that complication.
47             private readonly Semaphore _creationSemaphore;
48 
49             private readonly WaitHandle[] _handlesWithCreate;
50             private readonly WaitHandle[] _handlesWithoutCreate;
51 
PoolWaitHandles()52             internal PoolWaitHandles()
53             {
54                 _poolSemaphore = new Semaphore(0, MAX_Q_SIZE);
55                 _errorEvent = new ManualResetEvent(false);
56                 _creationSemaphore = new Semaphore(1, 1);
57 
58                 _handlesWithCreate = new WaitHandle[] { _poolSemaphore, _errorEvent, _creationSemaphore };
59                 _handlesWithoutCreate = new WaitHandle[] { _poolSemaphore, _errorEvent };
60             }
61 
62 
63             internal Semaphore CreationSemaphore
64             {
65                 get { return _creationSemaphore; }
66             }
67 
68             internal ManualResetEvent ErrorEvent
69             {
70                 get { return _errorEvent; }
71             }
72 
73             internal Semaphore PoolSemaphore
74             {
75                 get { return _poolSemaphore; }
76             }
77 
GetHandles(bool withCreate)78             internal WaitHandle[] GetHandles(bool withCreate)
79             {
80                 return withCreate ? _handlesWithCreate : _handlesWithoutCreate;
81             }
82         }
83 
84         private const int MAX_Q_SIZE = (int)0x00100000;
85 
86         // The order of these is important; we want the WaitAny call to be signaled
87         // for a free object before a creation signal.  Only the index first signaled
88         // object is returned from the WaitAny call.
89         private const int SEMAPHORE_HANDLE = (int)0x0;
90         private const int ERROR_HANDLE = (int)0x1;
91         private const int CREATION_HANDLE = (int)0x2;
92         private const int BOGUS_HANDLE = (int)0x3;
93 
94 
95         private const int ERROR_WAIT_DEFAULT = 5 * 1000; // 5 seconds
96 
97         // we do want a testable, repeatable set of generated random numbers
98         private static readonly Random s_random = new Random(5101977); // Value obtained from Dave Driver
99 
100         private readonly int _cleanupWait;
101         private readonly DbConnectionPoolIdentity _identity;
102 
103         private readonly DbConnectionFactory _connectionFactory;
104         private readonly DbConnectionPoolGroup _connectionPoolGroup;
105         private readonly DbConnectionPoolGroupOptions _connectionPoolGroupOptions;
106         private DbConnectionPoolProviderInfo _connectionPoolProviderInfo;
107 
108         private State _state;
109 
110         private readonly ConcurrentStack<DbConnectionInternal> _stackOld = new ConcurrentStack<DbConnectionInternal>();
111         private readonly ConcurrentStack<DbConnectionInternal> _stackNew = new ConcurrentStack<DbConnectionInternal>();
112 
113         private readonly ConcurrentQueue<PendingGetConnection> _pendingOpens = new ConcurrentQueue<PendingGetConnection>();
114         private int _pendingOpensWaiting = 0;
115 
116         private readonly WaitCallback _poolCreateRequest;
117 
118         private int _waitCount;
119         private readonly PoolWaitHandles _waitHandles;
120 
121         private Exception _resError;
122         private volatile bool _errorOccurred;
123 
124         private int _errorWait;
125         private Timer _errorTimer;
126 
127         private Timer _cleanupTimer;
128 
129 
130         private readonly List<DbConnectionInternal> _objectList;
131         private int _totalObjects;
132 
133 
134         // only created by DbConnectionPoolGroup.GetConnectionPool
DbConnectionPool( DbConnectionFactory connectionFactory, DbConnectionPoolGroup connectionPoolGroup, DbConnectionPoolIdentity identity, DbConnectionPoolProviderInfo connectionPoolProviderInfo)135         internal DbConnectionPool(
136                             DbConnectionFactory connectionFactory,
137                             DbConnectionPoolGroup connectionPoolGroup,
138                             DbConnectionPoolIdentity identity,
139                             DbConnectionPoolProviderInfo connectionPoolProviderInfo)
140         {
141             Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup");
142 
143             if ((null != identity) && identity.IsRestricted)
144             {
145                 throw ADP.InternalError(ADP.InternalErrorCode.AttemptingToPoolOnRestrictedToken);
146             }
147 
148             _state = State.Initializing;
149 
150             lock (s_random)
151             { // Random.Next is not thread-safe
152                 _cleanupWait = s_random.Next(12, 24) * 10 * 1000; // 2-4 minutes in 10 sec intervals
153             }
154 
155             _connectionFactory = connectionFactory;
156             _connectionPoolGroup = connectionPoolGroup;
157             _connectionPoolGroupOptions = connectionPoolGroup.PoolGroupOptions;
158             _connectionPoolProviderInfo = connectionPoolProviderInfo;
159             _identity = identity;
160 
161             _waitHandles = new PoolWaitHandles();
162 
163             _errorWait = ERROR_WAIT_DEFAULT;
164             _errorTimer = null;  // No error yet.
165 
166             _objectList = new List<DbConnectionInternal>(MaxPoolSize);
167 
168             _poolCreateRequest = new WaitCallback(PoolCreateRequest); // used by CleanupCallback
169             _state = State.Running;
170 
171             //_cleanupTimer & QueuePoolCreateRequest is delayed until DbConnectionPoolGroup calls
172             // StartBackgroundCallbacks after pool is actually in the collection
173         }
174 
175         private int CreationTimeout
176         {
177             get { return PoolGroupOptions.CreationTimeout; }
178         }
179 
180         internal int Count
181         {
182             get { return _totalObjects; }
183         }
184 
185         internal DbConnectionFactory ConnectionFactory
186         {
187             get { return _connectionFactory; }
188         }
189 
190         internal bool ErrorOccurred
191         {
192             get { return _errorOccurred; }
193         }
194 
195 
196         internal TimeSpan LoadBalanceTimeout
197         {
198             get { return PoolGroupOptions.LoadBalanceTimeout; }
199         }
200 
201         private bool NeedToReplenish
202         {
203             get
204             {
205                 if (State.Running != _state) // Don't allow connection create when not running.
206                     return false;
207 
208                 int totalObjects = Count;
209 
210                 if (totalObjects >= MaxPoolSize)
211                     return false;
212 
213                 if (totalObjects < MinPoolSize)
214                     return true;
215 
216                 int freeObjects = (_stackNew.Count + _stackOld.Count);
217                 int waitingRequests = _waitCount;
218                 bool needToReplenish = (freeObjects < waitingRequests) || ((freeObjects == waitingRequests) && (totalObjects > 1));
219 
220                 return needToReplenish;
221             }
222         }
223 
224         internal DbConnectionPoolIdentity Identity
225         {
226             get { return _identity; }
227         }
228 
229         internal bool IsRunning
230         {
231             get { return State.Running == _state; }
232         }
233 
234         private int MaxPoolSize
235         {
236             get { return PoolGroupOptions.MaxPoolSize; }
237         }
238 
239         private int MinPoolSize
240         {
241             get { return PoolGroupOptions.MinPoolSize; }
242         }
243 
244 
245         internal DbConnectionPoolGroup PoolGroup
246         {
247             get { return _connectionPoolGroup; }
248         }
249 
250         internal DbConnectionPoolGroupOptions PoolGroupOptions
251         {
252             get { return _connectionPoolGroupOptions; }
253         }
254 
255         internal DbConnectionPoolProviderInfo ProviderInfo
256         {
257             get { return _connectionPoolProviderInfo; }
258         }
259 
260         internal bool UseLoadBalancing
261         {
262             get { return PoolGroupOptions.UseLoadBalancing; }
263         }
264 
265         private bool UsingIntegrateSecurity
266         {
267             get { return (null != _identity && DbConnectionPoolIdentity.NoIdentity != _identity); }
268         }
269 
CleanupCallback(Object state)270         private void CleanupCallback(Object state)
271         {
272             // Called when the cleanup-timer ticks over.
273 
274             // This is the automatic pruning method.  Every period, we will
275             // perform a two-step process:
276             //
277             // First, for each free object above MinPoolSize, we will obtain a
278             // semaphore representing one object and destroy one from old stack.
279             // We will continue this until we either reach MinPoolSize, we are
280             // unable to obtain a free object, or we have exhausted all the
281             // objects on the old stack.
282             //
283             // Second we move all free objects on the new stack to the old stack.
284             // So, every period the objects on the old stack are destroyed and
285             // the objects on the new stack are pushed to the old stack.  All
286             // objects that are currently out and in use are not on either stack.
287             //
288             // With this logic, objects are pruned from the pool if unused for
289             // at least one period but not more than two periods.
290 
291 
292             // Destroy free objects that put us above MinPoolSize from old stack.
293             while (Count > MinPoolSize)
294             { // While above MinPoolSize...
295                 if (_waitHandles.PoolSemaphore.WaitOne(0))
296                 {
297                     // We obtained a objects from the semaphore.
298                     DbConnectionInternal obj;
299 
300                     if (_stackOld.TryPop(out obj))
301                     {
302                         Debug.Assert(obj != null, "null connection is not expected");
303                         // If we obtained one from the old stack, destroy it.
304 
305                         DestroyObject(obj);
306                     }
307                     else
308                     {
309                         // Else we exhausted the old stack (the object the
310                         // semaphore represents is on the new stack), so break.
311                         _waitHandles.PoolSemaphore.Release(1);
312                         break;
313                     }
314                 }
315                 else
316                 {
317                     break;
318                 }
319             }
320 
321             // Push to the old-stack.  For each free object, move object from
322             // new stack to old stack.
323             if (_waitHandles.PoolSemaphore.WaitOne(0))
324             {
325                 for (;;)
326                 {
327                     DbConnectionInternal obj;
328 
329                     if (!_stackNew.TryPop(out obj))
330                         break;
331 
332                     Debug.Assert(obj != null, "null connection is not expected");
333 
334 
335                     Debug.Assert(!obj.IsEmancipated, "pooled object not in pool");
336                     Debug.Assert(obj.CanBePooled, "pooled object is not poolable");
337 
338                     _stackOld.Push(obj);
339                 }
340                 _waitHandles.PoolSemaphore.Release(1);
341             }
342 
343             // Queue up a request to bring us up to MinPoolSize
344             QueuePoolCreateRequest();
345         }
346 
Clear()347         internal void Clear()
348         {
349             DbConnectionInternal obj;
350 
351             // First, quickly doom everything.
352             lock (_objectList)
353             {
354                 int count = _objectList.Count;
355 
356                 for (int i = 0; i < count; ++i)
357                 {
358                     obj = _objectList[i];
359 
360                     if (null != obj)
361                     {
362                         obj.DoNotPoolThisConnection();
363                     }
364                 }
365             }
366 
367             // Second, dispose of all the free connections.
368             while (_stackNew.TryPop(out obj))
369             {
370                 Debug.Assert(obj != null, "null connection is not expected");
371                 DestroyObject(obj);
372             }
373             while (_stackOld.TryPop(out obj))
374             {
375                 Debug.Assert(obj != null, "null connection is not expected");
376                 DestroyObject(obj);
377             }
378 
379             // Finally, reclaim everything that's emancipated (which, because
380             // it's been doomed, will cause it to be disposed of as well)
381             ReclaimEmancipatedObjects();
382         }
383 
CreateCleanupTimer()384         private Timer CreateCleanupTimer()
385         {
386             return (new Timer(new TimerCallback(this.CleanupCallback), null, _cleanupWait, _cleanupWait));
387         }
388 
CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)389         private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
390         {
391             DbConnectionInternal newObj = null;
392 
393             try
394             {
395                 newObj = _connectionFactory.CreatePooledConnection(this, owningObject, _connectionPoolGroup.ConnectionOptions, _connectionPoolGroup.PoolKey, userOptions);
396                 if (null == newObj)
397                 {
398                     throw ADP.InternalError(ADP.InternalErrorCode.CreateObjectReturnedNull);    // CreateObject succeeded, but null object
399                 }
400                 if (!newObj.CanBePooled)
401                 {
402                     throw ADP.InternalError(ADP.InternalErrorCode.NewObjectCannotBePooled);        // CreateObject succeeded, but non-poolable object
403                 }
404                 newObj.PrePush(null);
405 
406                 lock (_objectList)
407                 {
408                     if ((oldConnection != null) && (oldConnection.Pool == this))
409                     {
410                         _objectList.Remove(oldConnection);
411                     }
412                     _objectList.Add(newObj);
413                     _totalObjects = _objectList.Count;
414                 }
415 
416                 // If the old connection belonged to another pool, we need to remove it from that
417                 if (oldConnection != null)
418                 {
419                     var oldConnectionPool = oldConnection.Pool;
420                     if (oldConnectionPool != null && oldConnectionPool != this)
421                     {
422                         Debug.Assert(oldConnectionPool._state == State.ShuttingDown, "Old connections pool should be shutting down");
423                         lock (oldConnectionPool._objectList)
424                         {
425                             oldConnectionPool._objectList.Remove(oldConnection);
426                             oldConnectionPool._totalObjects = oldConnectionPool._objectList.Count;
427                         }
428                     }
429                 }
430 
431                 // Reset the error wait:
432                 _errorWait = ERROR_WAIT_DEFAULT;
433             }
434             catch (Exception e)
435             {
436                 if (!ADP.IsCatchableExceptionType(e))
437                 {
438                     throw;
439                 }
440                 newObj = null; // set to null, so we do not return bad new object
441                 // Failed to create instance
442                 _resError = e;
443 
444                 // Make sure the timer starts even if ThreadAbort occurs after setting the ErrorEvent.
445 
446                 // timer allocation has to be done out of CER block
447                 Timer t = new Timer(new TimerCallback(this.ErrorCallback), null, Timeout.Infinite, Timeout.Infinite);
448                 bool timerIsNotDisposed;
449                 try { }
450                 finally
451                 {
452                     _waitHandles.ErrorEvent.Set();
453                     _errorOccurred = true;
454 
455                     // Enable the timer.
456                     // Note that the timer is created to allow periodic invocation. If ThreadAbort occurs in the middle of ErrorCallback,
457                     // the timer will restart. Otherwise, the timer callback (ErrorCallback) destroys the timer after resetting the error to avoid second callback.
458                     _errorTimer = t;
459                     timerIsNotDisposed = t.Change(_errorWait, _errorWait);
460                 }
461 
462                 Debug.Assert(timerIsNotDisposed, "ErrorCallback timer has been disposed");
463 
464                 if (30000 < _errorWait)
465                 {
466                     _errorWait = 60000;
467                 }
468                 else
469                 {
470                     _errorWait *= 2;
471                 }
472                 throw;
473             }
474             return newObj;
475         }
476 
DeactivateObject(DbConnectionInternal obj)477         private void DeactivateObject(DbConnectionInternal obj)
478         {
479             obj.DeactivateConnection();
480 
481             bool returnToGeneralPool = false;
482             bool destroyObject = false;
483 
484             if (obj.IsConnectionDoomed)
485             {
486                 // the object is not fit for reuse -- just dispose of it.
487                 destroyObject = true;
488             }
489             else
490             {
491                 // NOTE: constructor should ensure that current state cannot be State.Initializing, so it can only
492                 //   be State.Running or State.ShuttingDown
493                 Debug.Assert(_state == State.Running || _state == State.ShuttingDown);
494 
495                 lock (obj)
496                 {
497                     // A connection with a delegated transaction cannot currently
498                     // be returned to a different customer until the transaction
499                     // actually completes, so we send it into Stasis -- the SysTx
500                     // transaction object will ensure that it is owned (not lost),
501                     // and it will be certain to put it back into the pool.
502 
503                     if (_state == State.ShuttingDown)
504                     {
505                         // connection is being closed and the pool has been marked as shutting
506                         //   down, so destroy this object.
507                         destroyObject = true;
508                     }
509                     else
510                     {
511                         if (obj.CanBePooled)
512                         {
513                             // We must put this connection into the transacted pool
514                             // while inside a lock to prevent a race condition with
515                             // the transaction asynchronously completing on a second
516                             // thread.
517 
518                             // return to general pool
519                             returnToGeneralPool = true;
520                         }
521                         else
522                         {
523                             // object is not fit for reuse -- just dispose of it
524                             destroyObject = true;
525                         }
526                     }
527                 }
528             }
529 
530             if (returnToGeneralPool)
531             {
532                 // Only push the connection into the general pool if we didn't
533                 //   already push it onto the transacted pool, put it into stasis,
534                 //   or want to destroy it.
535                 Debug.Assert(destroyObject == false);
536                 PutNewObject(obj);
537             }
538             else if (destroyObject)
539             {
540                 DestroyObject(obj);
541                 QueuePoolCreateRequest();
542             }
543 
544             //-------------------------------------------------------------------------------------
545             // postcondition
546 
547             // ensure that the connection was processed
548             Debug.Assert(
549                 returnToGeneralPool == true || destroyObject == true);
550         }
551 
DestroyObject(DbConnectionInternal obj)552         internal void DestroyObject(DbConnectionInternal obj)
553         {
554             // A connection with a delegated transaction cannot be disposed of
555             // until the delegated transaction has actually completed.  Instead,
556             // we simply leave it alone; when the transaction completes, it will
557             // come back through PutObjectFromTransactedPool, which will call us
558             // again.
559             bool removed = false;
560             lock (_objectList)
561             {
562                 removed = _objectList.Remove(obj);
563                 Debug.Assert(removed, "attempt to DestroyObject not in list");
564                 _totalObjects = _objectList.Count;
565             }
566 
567             if (removed)
568             {
569             }
570             obj.Dispose();
571         }
572 
ErrorCallback(Object state)573         private void ErrorCallback(Object state)
574         {
575             _errorOccurred = false;
576             _waitHandles.ErrorEvent.Reset();
577 
578             // the error state is cleaned, destroy the timer to avoid periodic invocation
579             Timer t = _errorTimer;
580             _errorTimer = null;
581             if (t != null)
582             {
583                 t.Dispose(); // Cancel timer request.
584             }
585         }
586 
587 
588         // TODO: move this to src/Common and integrate with SqlClient
589         // Note: Odbc connections are not passing through this code
TryCloneCachedException()590         private Exception TryCloneCachedException()
591         {
592             return _resError;
593         }
594 
WaitForPendingOpen()595         private void WaitForPendingOpen()
596         {
597             PendingGetConnection next;
598 
599             do
600             {
601                 bool started = false;
602 
603                 try
604                 {
605                     try { }
606                     finally
607                     {
608                         started = Interlocked.CompareExchange(ref _pendingOpensWaiting, 1, 0) == 0;
609                     }
610 
611                     if (!started)
612                     {
613                         return;
614                     }
615 
616                     while (_pendingOpens.TryDequeue(out next))
617                     {
618                         if (next.Completion.Task.IsCompleted)
619                         {
620                             continue;
621                         }
622 
623                         uint delay;
624                         if (next.DueTime == Timeout.Infinite)
625                         {
626                             delay = unchecked((uint)Timeout.Infinite);
627                         }
628                         else
629                         {
630                             delay = (uint)Math.Max(ADP.TimerRemainingMilliseconds(next.DueTime), 0);
631                         }
632 
633                         DbConnectionInternal connection = null;
634                         bool timeout = false;
635                         Exception caughtException = null;
636 
637                         try
638                         {
639                             bool allowCreate = true;
640                             bool onlyOneCheckConnection = false;
641                             timeout = !TryGetConnection(next.Owner, delay, allowCreate, onlyOneCheckConnection, next.UserOptions, out connection);
642                         }
643                         catch (Exception e)
644                         {
645                             caughtException = e;
646                         }
647 
648                         if (caughtException != null)
649                         {
650                             next.Completion.TrySetException(caughtException);
651                         }
652                         else if (timeout)
653                         {
654                             next.Completion.TrySetException(ADP.ExceptionWithStackTrace(ADP.PooledOpenTimeout()));
655                         }
656                         else
657                         {
658                             Debug.Assert(connection != null, "connection should never be null in success case");
659                             if (!next.Completion.TrySetResult(connection))
660                             {
661                                 // if the completion was cancelled, lets try and get this connection back for the next try
662                                 PutObject(connection, next.Owner);
663                             }
664                         }
665                     }
666                 }
667                 finally
668                 {
669                     if (started)
670                     {
671                         Interlocked.Exchange(ref _pendingOpensWaiting, 0);
672                     }
673                 }
674             } while (_pendingOpens.TryPeek(out next));
675         }
676 
TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions, out DbConnectionInternal connection)677         internal bool TryGetConnection(DbConnection owningObject, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions, out DbConnectionInternal connection)
678         {
679             uint waitForMultipleObjectsTimeout = 0;
680             bool allowCreate = false;
681 
682             if (retry == null)
683             {
684                 waitForMultipleObjectsTimeout = (uint)CreationTimeout;
685 
686                 // Set the wait timeout to INFINITE (-1) if the SQL connection timeout is 0 (== infinite)
687                 if (waitForMultipleObjectsTimeout == 0)
688                     waitForMultipleObjectsTimeout = unchecked((uint)Timeout.Infinite);
689 
690                 allowCreate = true;
691             }
692 
693             if (_state != State.Running)
694             {
695                 connection = null;
696                 return true;
697             }
698 
699             bool onlyOneCheckConnection = true;
700             if (TryGetConnection(owningObject, waitForMultipleObjectsTimeout, allowCreate, onlyOneCheckConnection, userOptions, out connection))
701             {
702                 return true;
703             }
704             else if (retry == null)
705             {
706                 // timed out on a sync call
707                 return true;
708             }
709 
710             var pendingGetConnection =
711                 new PendingGetConnection(
712                     CreationTimeout == 0 ? Timeout.Infinite : ADP.TimerCurrent() + ADP.TimerFromSeconds(CreationTimeout / 1000),
713                     owningObject,
714                     retry,
715                     userOptions);
716             _pendingOpens.Enqueue(pendingGetConnection);
717 
718             // it is better to StartNew too many times than not enough
719             if (_pendingOpensWaiting == 0)
720             {
721                 Thread waitOpenThread = new Thread(WaitForPendingOpen);
722                 waitOpenThread.IsBackground = true;
723                 waitOpenThread.Start();
724             }
725 
726             connection = null;
727             return false;
728         }
729 
TryGetConnection(DbConnection owningObject, uint waitForMultipleObjectsTimeout, bool allowCreate, bool onlyOneCheckConnection, DbConnectionOptions userOptions, out DbConnectionInternal connection)730         private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObjectsTimeout, bool allowCreate, bool onlyOneCheckConnection, DbConnectionOptions userOptions, out DbConnectionInternal connection)
731         {
732             DbConnectionInternal obj = null;
733             if (null == obj)
734             {
735                 Interlocked.Increment(ref _waitCount);
736 
737                 do
738                 {
739                     int waitResult = BOGUS_HANDLE;
740                     try
741                     {
742                         try
743                         {
744                         }
745                         finally
746                         {
747                             waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(allowCreate), unchecked((int)waitForMultipleObjectsTimeout));
748                         }
749 
750                         // From the WaitAny docs: "If more than one object became signaled during
751                         // the call, this is the array index of the signaled object with the
752                         // smallest index value of all the signaled objects."  This is important
753                         // so that the free object signal will be returned before a creation
754                         // signal.
755 
756                         switch (waitResult)
757                         {
758                             case WaitHandle.WaitTimeout:
759                                 Interlocked.Decrement(ref _waitCount);
760                                 connection = null;
761                                 return false;
762 
763                             case ERROR_HANDLE:
764                                 // Throw the error that PoolCreateRequest stashed.
765                                 Interlocked.Decrement(ref _waitCount);
766                                 throw TryCloneCachedException();
767 
768                             case CREATION_HANDLE:
769 
770                                 try
771                                 {
772                                     obj = UserCreateRequest(owningObject, userOptions);
773                                 }
774                                 catch
775                                 {
776                                     if (null == obj)
777                                     {
778                                         Interlocked.Decrement(ref _waitCount);
779                                     }
780                                     throw;
781                                 }
782                                 finally
783                                 {
784                                     // Ensure that we release this waiter, regardless
785                                     // of any exceptions that may be thrown.
786                                     if (null != obj)
787                                     {
788                                         Interlocked.Decrement(ref _waitCount);
789                                     }
790                                 }
791 
792                                 if (null == obj)
793                                 {
794                                     // If we were not able to create an object, check to see if
795                                     // we reached MaxPoolSize.  If so, we will no longer wait on
796                                     // the CreationHandle, but instead wait for a free object or
797                                     // the timeout.
798                                     if (Count >= MaxPoolSize && 0 != MaxPoolSize)
799                                     {
800                                         if (!ReclaimEmancipatedObjects())
801                                         {
802                                             // modify handle array not to wait on creation mutex anymore
803                                             Debug.Assert(2 == CREATION_HANDLE, "creation handle changed value");
804                                             allowCreate = false;
805                                         }
806                                     }
807                                 }
808                                 break;
809 
810                             case SEMAPHORE_HANDLE:
811                                 //
812                                 //    guaranteed available inventory
813                                 //
814                                 Interlocked.Decrement(ref _waitCount);
815                                 obj = GetFromGeneralPool();
816 
817                                 if ((obj != null) && (!obj.IsConnectionAlive()))
818                                 {
819                                     DestroyObject(obj);
820                                     obj = null;     // Setting to null in case creating a new object fails
821 
822                                     if (onlyOneCheckConnection)
823                                     {
824                                         if (_waitHandles.CreationSemaphore.WaitOne(unchecked((int)waitForMultipleObjectsTimeout)))
825                                         {
826                                             try
827                                             {
828                                                 obj = UserCreateRequest(owningObject, userOptions);
829                                             }
830                                             finally
831                                             {
832                                                 _waitHandles.CreationSemaphore.Release(1);
833                                             }
834                                         }
835                                         else
836                                         {
837                                             // Timeout waiting for creation semaphore - return null
838                                             connection = null;
839                                             return false;
840                                         }
841                                     }
842                                 }
843                                 break;
844                             default:
845                                 Interlocked.Decrement(ref _waitCount);
846                                 throw ADP.InternalError(ADP.InternalErrorCode.UnexpectedWaitAnyResult);
847                         }
848                     }
849                     finally
850                     {
851                         if (CREATION_HANDLE == waitResult)
852                         {
853                             _waitHandles.CreationSemaphore.Release(1);
854                         }
855                     }
856                 } while (null == obj);
857             }
858 
859             if (null != obj)
860             {
861                 PrepareConnection(owningObject, obj);
862             }
863 
864             connection = obj;
865             return true;
866         }
867 
PrepareConnection(DbConnection owningObject, DbConnectionInternal obj)868         private void PrepareConnection(DbConnection owningObject, DbConnectionInternal obj)
869         {
870             lock (obj)
871             {   // Protect against Clear and ReclaimEmancipatedObjects, which call IsEmancipated, which is affected by PrePush and PostPop
872                 obj.PostPop(owningObject);
873             }
874             try
875             {
876                 obj.ActivateConnection();
877             }
878             catch
879             {
880                 // if Activate throws an exception
881                 // put it back in the pool or have it properly disposed of
882                 this.PutObject(obj, owningObject);
883                 throw;
884             }
885         }
886 
887         /// <summary>
888         /// Creates a new connection to replace an existing connection
889         /// </summary>
890         /// <param name="owningObject">Outer connection that currently owns <paramref name="oldConnection"/></param>
891         /// <param name="userOptions">Options used to create the new connection</param>
892         /// <param name="oldConnection">Inner connection that will be replaced</param>
893         /// <returns>A new inner connection that is attached to the <paramref name="owningObject"/></returns>
ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)894         internal DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
895         {
896             DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection);
897 
898             if (newConnection != null)
899             {
900                 PrepareConnection(owningObject, newConnection);
901                 oldConnection.PrepareForReplaceConnection();
902                 oldConnection.DeactivateConnection();
903                 oldConnection.Dispose();
904             }
905 
906             return newConnection;
907         }
908 
GetFromGeneralPool()909         private DbConnectionInternal GetFromGeneralPool()
910         {
911             DbConnectionInternal obj = null;
912 
913             if (!_stackNew.TryPop(out obj))
914             {
915                 if (!_stackOld.TryPop(out obj))
916                 {
917                     obj = null;
918                 }
919                 else
920                 {
921                     Debug.Assert(obj != null, "null connection is not expected");
922                 }
923             }
924             else
925             {
926                 Debug.Assert(obj != null, "null connection is not expected");
927             }
928 
929             // When another thread is clearing this pool,
930             // it will remove all connections in this pool which causes the
931             // following assert to fire, which really mucks up stress against
932             //  checked bits.
933 
934             if (null != obj)
935             {
936             }
937             return (obj);
938         }
939 
PoolCreateRequest(object state)940         private void PoolCreateRequest(object state)
941         {
942             // called by pooler to ensure pool requests are currently being satisfied -
943             // creation mutex has not been obtained
944 
945             if (State.Running == _state)
946             {
947                 // in case WaitForPendingOpen ever failed with no subsequent OpenAsync calls,
948                 // start it back up again
949                 if (!_pendingOpens.IsEmpty && _pendingOpensWaiting == 0)
950                 {
951                     Thread waitOpenThread = new Thread(WaitForPendingOpen);
952                     waitOpenThread.IsBackground = true;
953                     waitOpenThread.Start();
954                 }
955 
956                 // Before creating any new objects, reclaim any released objects that were
957                 // not closed.
958                 ReclaimEmancipatedObjects();
959 
960                 if (!ErrorOccurred)
961                 {
962                     if (NeedToReplenish)
963                     {
964                         // Check to see if pool was created using integrated security and if so, make
965                         // sure the identity of current user matches that of user that created pool.
966                         // If it doesn't match, do not create any objects on the ThreadPool thread,
967                         // since either Open will fail or we will open a object for this pool that does
968                         // not belong in this pool.  The side effect of this is that if using integrated
969                         // security min pool size cannot be guaranteed.
970                         if (UsingIntegrateSecurity && !_identity.Equals(DbConnectionPoolIdentity.GetCurrent()))
971                         {
972                             return;
973                         }
974                         int waitResult = BOGUS_HANDLE;
975                         try
976                         {
977                             try { }
978                             finally
979                             {
980                                 waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(withCreate: true), CreationTimeout);
981                             }
982                             if (CREATION_HANDLE == waitResult)
983                             {
984                                 DbConnectionInternal newObj;
985 
986                                 // Check ErrorOccurred again after obtaining mutex
987                                 if (!ErrorOccurred)
988                                 {
989                                     while (NeedToReplenish)
990                                     {
991                                         // Don't specify any user options because there is no outer connection associated with the new connection
992                                         newObj = CreateObject(owningObject: null, userOptions: null, oldConnection: null);
993 
994                                         // We do not need to check error flag here, since we know if
995                                         // CreateObject returned null, we are in error case.
996                                         if (null != newObj)
997                                         {
998                                             PutNewObject(newObj);
999                                         }
1000                                         else
1001                                         {
1002                                             break;
1003                                         }
1004                                     }
1005                                 }
1006                             }
1007                             else if (WaitHandle.WaitTimeout == waitResult)
1008                             {
1009                                 // do not wait forever and potential block this worker thread
1010                                 // instead wait for a period of time and just requeue to try again
1011                                 QueuePoolCreateRequest();
1012                             }
1013                         }
1014                         finally
1015                         {
1016                             if (CREATION_HANDLE == waitResult)
1017                             {
1018                                 // reuse waitResult and ignore its value
1019                                 _waitHandles.CreationSemaphore.Release(1);
1020                             }
1021                         }
1022                     }
1023                 }
1024             }
1025         }
1026 
PutNewObject(DbConnectionInternal obj)1027         internal void PutNewObject(DbConnectionInternal obj)
1028         {
1029             Debug.Assert(null != obj, "why are we adding a null object to the pool?");
1030             // Debug.Assert(obj.CanBePooled,    "non-poolable object in pool");
1031 
1032 
1033             _stackNew.Push(obj);
1034             _waitHandles.PoolSemaphore.Release(1);
1035         }
1036 
PutObject(DbConnectionInternal obj, object owningObject)1037         internal void PutObject(DbConnectionInternal obj, object owningObject)
1038         {
1039             Debug.Assert(null != obj, "null obj?");
1040 
1041 
1042             // Once a connection is closing (which is the state that we're in at
1043             // this point in time) you cannot delegate a transaction to or enlist
1044             // a transaction in it, so we can correctly presume that if there was
1045             // not a delegated or enlisted transaction to start with, that there
1046             // will not be a delegated or enlisted transaction once we leave the
1047             // lock.
1048 
1049             lock (obj)
1050             {
1051                 // Calling PrePush prevents the object from being reclaimed
1052                 // once we leave the lock, because it sets _pooledCount such
1053                 // that it won't appear to be out of the pool.  What that
1054                 // means, is that we're now responsible for this connection:
1055                 // it won't get reclaimed if we drop the ball somewhere.
1056                 obj.PrePush(owningObject);
1057             }
1058 
1059             DeactivateObject(obj);
1060         }
1061 
1062 
QueuePoolCreateRequest()1063         private void QueuePoolCreateRequest()
1064         {
1065             if (State.Running == _state)
1066             {
1067                 // Make sure we're at quota by posting a callback to the threadpool.
1068                 ThreadPool.QueueUserWorkItem(_poolCreateRequest);
1069             }
1070         }
1071 
ReclaimEmancipatedObjects()1072         private bool ReclaimEmancipatedObjects()
1073         {
1074             bool emancipatedObjectFound = false;
1075 
1076             List<DbConnectionInternal> reclaimedObjects = new List<DbConnectionInternal>();
1077             int count;
1078 
1079             lock (_objectList)
1080             {
1081                 count = _objectList.Count;
1082 
1083                 for (int i = 0; i < count; ++i)
1084                 {
1085                     DbConnectionInternal obj = _objectList[i];
1086 
1087                     if (null != obj)
1088                     {
1089                         bool locked = false;
1090 
1091                         try
1092                         {
1093                             Monitor.TryEnter(obj, ref locked);
1094 
1095                             if (locked)
1096                             { // avoid race condition with PrePush/PostPop and IsEmancipated
1097                                 if (obj.IsEmancipated)
1098                                 {
1099                                     // Inside the lock, we want to do as little
1100                                     // as possible, so we simply mark the object
1101                                     // as being in the pool, but hand it off to
1102                                     // an out of pool list to be deactivated,
1103                                     // etc.
1104                                     obj.PrePush(null);
1105                                     reclaimedObjects.Add(obj);
1106                                 }
1107                             }
1108                         }
1109                         finally
1110                         {
1111                             if (locked)
1112                                 Monitor.Exit(obj);
1113                         }
1114                     }
1115                 }
1116             }
1117 
1118             // NOTE: we don't want to call DeactivateObject while we're locked,
1119             // because it can make roundtrips to the server and this will block
1120             // object creation in the pooler.  Instead, we queue things we need
1121             // to do up, and process them outside the lock.
1122             count = reclaimedObjects.Count;
1123 
1124             for (int i = 0; i < count; ++i)
1125             {
1126                 DbConnectionInternal obj = reclaimedObjects[i];
1127 
1128                 emancipatedObjectFound = true;
1129 
1130                 DeactivateObject(obj);
1131             }
1132             return emancipatedObjectFound;
1133         }
1134 
Startup()1135         internal void Startup()
1136         {
1137             _cleanupTimer = CreateCleanupTimer();
1138             if (NeedToReplenish)
1139             {
1140                 QueuePoolCreateRequest();
1141             }
1142         }
1143 
Shutdown()1144         internal void Shutdown()
1145         {
1146             _state = State.ShuttingDown;
1147 
1148             // deactivate timer callbacks
1149             Timer t = _cleanupTimer;
1150             _cleanupTimer = null;
1151             if (null != t)
1152             {
1153                 t.Dispose();
1154             }
1155         }
1156 
1157 
UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection = null)1158         private DbConnectionInternal UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection = null)
1159         {
1160             // called by user when they were not able to obtain a free object but
1161             // instead obtained creation mutex
1162 
1163             DbConnectionInternal obj = null;
1164             if (ErrorOccurred)
1165             {
1166                 throw TryCloneCachedException();
1167             }
1168             else
1169             {
1170                 if ((oldConnection != null) || (Count < MaxPoolSize) || (0 == MaxPoolSize))
1171                 {
1172                     // If we have an odd number of total objects, reclaim any dead objects.
1173                     // If we did not find any objects to reclaim, create a new one.
1174                     if ((oldConnection != null) || (Count & 0x1) == 0x1 || !ReclaimEmancipatedObjects())
1175                         obj = CreateObject(owningObject, userOptions, oldConnection);
1176                 }
1177                 return obj;
1178             }
1179         }
1180     }
1181 }
1182