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