/// <summary> /// Creates a new connection to replace an existing connection /// </summary> /// <param name="owningObject">Outer connection that currently owns <paramref name="oldConnection"/></param> /// <param name="userOptions">Options used to create the new connection</param> /// <param name="oldConnection">Inner connection that will be replaced</param> /// <returns>A new inner connection that is attached to the <paramref name="owningObject"/></returns> internal DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) { DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection); if (newConnection != null) { PrepareConnection(owningObject, newConnection); oldConnection.PrepareForReplaceConnection(); oldConnection.DeactivateConnection(); oldConnection.Dispose(); } return(newConnection); }
internal bool TryGetConnection(DbConnection owningObject, TaskCompletionSource <DbConnectionInternal> retry, DbConnectionOptions userOptions, out DbConnectionInternal connection) { uint waitForMultipleObjectsTimeout = 0; bool allowCreate = false; if (retry == null) { waitForMultipleObjectsTimeout = (uint)CreationTimeout; // Set the wait timeout to INFINITE (-1) if the SQL connection timeout is 0 (== infinite) if (waitForMultipleObjectsTimeout == 0) { waitForMultipleObjectsTimeout = unchecked ((uint)Timeout.Infinite); } allowCreate = true; } if (_state != State.Running) { connection = null; return(true); } bool onlyOneCheckConnection = true; if (TryGetConnection(owningObject, waitForMultipleObjectsTimeout, allowCreate, onlyOneCheckConnection, userOptions, out connection)) { return(true); } else if (retry == null) { // timed out on a sync call return(true); } var pendingGetConnection = new PendingGetConnection( CreationTimeout == 0 ? Timeout.Infinite : ADP.TimerCurrent() + ADP.TimerFromSeconds(CreationTimeout / 1000), owningObject, retry, userOptions); _pendingOpens.Enqueue(pendingGetConnection); // it is better to StartNew too many times than not enough if (_pendingOpensWaiting == 0) { Thread waitOpenThread = new Thread(WaitForPendingOpen); waitOpenThread.IsBackground = true; waitOpenThread.Start(); } connection = null; return(false); }
private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObjectsTimeout, bool allowCreate, bool onlyOneCheckConnection, DbConnectionOptions userOptions, out DbConnectionInternal connection) { DbConnectionInternal obj = null; if (null == obj) { Interlocked.Increment(ref _waitCount); do { int waitResult = BOGUS_HANDLE; try { try { } finally { waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(allowCreate), unchecked ((int)waitForMultipleObjectsTimeout)); } // From the WaitAny docs: "If more than one object became signaled during // the call, this is the array index of the signaled object with the // smallest index value of all the signaled objects." This is important // so that the free object signal will be returned before a creation // signal. switch (waitResult) { case WaitHandle.WaitTimeout: Interlocked.Decrement(ref _waitCount); connection = null; return(false); case ERROR_HANDLE: // Throw the error that PoolCreateRequest stashed. Interlocked.Decrement(ref _waitCount); throw TryCloneCachedException(); case CREATION_HANDLE: try { obj = UserCreateRequest(owningObject, userOptions); } catch { if (null == obj) { Interlocked.Decrement(ref _waitCount); } throw; } finally { // 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"); allowCreate = false; } } } break; case SEMAPHORE_HANDLE: // // guaranteed available inventory // Interlocked.Decrement(ref _waitCount); obj = GetFromGeneralPool(); if ((obj != null) && (!obj.IsConnectionAlive())) { DestroyObject(obj); obj = null; // Setting to null in case creating a new object fails if (onlyOneCheckConnection) { if (_waitHandles.CreationSemaphore.WaitOne(unchecked ((int)waitForMultipleObjectsTimeout))) { try { obj = UserCreateRequest(owningObject, userOptions); } finally { _waitHandles.CreationSemaphore.Release(1); } } else { // Timeout waiting for creation semaphore - return null connection = null; return(false); } } } break; default: Interlocked.Decrement(ref _waitCount); throw ADP.InternalError(ADP.InternalErrorCode.UnexpectedWaitAnyResult); } } finally { if (CREATION_HANDLE == waitResult) { _waitHandles.CreationSemaphore.Release(1); } } } while (null == obj); } if (null != obj) { PrepareConnection(owningObject, obj); } connection = obj; return(true); }
private void WaitForPendingOpen() { PendingGetConnection next; do { bool started = false; try { try { } finally { started = Interlocked.CompareExchange(ref _pendingOpensWaiting, 1, 0) == 0; } if (!started) { return; } while (_pendingOpens.TryDequeue(out next)) { if (next.Completion.Task.IsCompleted) { continue; } uint delay; if (next.DueTime == Timeout.Infinite) { delay = unchecked ((uint)Timeout.Infinite); } else { delay = (uint)Math.Max(ADP.TimerRemainingMilliseconds(next.DueTime), 0); } DbConnectionInternal connection = null; bool timeout = false; Exception caughtException = null; try { bool allowCreate = true; bool onlyOneCheckConnection = false; timeout = !TryGetConnection(next.Owner, delay, allowCreate, onlyOneCheckConnection, next.UserOptions, out connection); } catch (Exception e) { caughtException = e; } if (caughtException != null) { next.Completion.TrySetException(caughtException); } else if (timeout) { next.Completion.TrySetException(ADP.ExceptionWithStackTrace(ADP.PooledOpenTimeout())); } else { Debug.Assert(connection != null, "connection should never be null in success case"); if (!next.Completion.TrySetResult(connection)) { // if the completion was cancelled, lets try and get this connection back for the next try PutObject(connection, next.Owner); } } } } finally { if (started) { Interlocked.Exchange(ref _pendingOpensWaiting, 0); } } } while (_pendingOpens.TryPeek(out next)); }
private void DeactivateObject(DbConnectionInternal obj) { obj.DeactivateConnection(); bool returnToGeneralPool = false; bool destroyObject = 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) { // connection is being closed and the pool has been marked as shutting // down, so destroy this object. destroyObject = 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. // return to general pool returnToGeneralPool = true; } else { // object is not fit for reuse -- just dispose of it destroyObject = true; } } } } if (returnToGeneralPool) { // Only push the connection into the general pool if we didn't // already push it onto the transacted pool, put it into stasis, // or want to destroy it. Debug.Assert(destroyObject == false); PutNewObject(obj); } else if (destroyObject) { DestroyObject(obj); QueuePoolCreateRequest(); } //------------------------------------------------------------------------------------- // postcondition // ensure that the connection was processed Debug.Assert( returnToGeneralPool == true || destroyObject == true); }
private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) { DbConnectionInternal newObj = null; try { newObj = _connectionFactory.CreatePooledConnection(this, owningObject, _connectionPoolGroup.ConnectionOptions, _connectionPoolGroup.PoolKey, userOptions); 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) { if ((oldConnection != null) && (oldConnection.Pool == this)) { _objectList.Remove(oldConnection); } _objectList.Add(newObj); _totalObjects = _objectList.Count; } // If the old connection belonged to another pool, we need to remove it from that if (oldConnection != null) { var oldConnectionPool = oldConnection.Pool; if (oldConnectionPool != null && oldConnectionPool != this) { Debug.Assert(oldConnectionPool._state == State.ShuttingDown, "Old connections pool should be shutting down"); lock (oldConnectionPool._objectList) { oldConnectionPool._objectList.Remove(oldConnection); oldConnectionPool._totalObjects = oldConnectionPool._objectList.Count; } } } // Reset the error wait: _errorWait = ERROR_WAIT_DEFAULT; } catch (Exception e) { if (!ADP.IsCatchableExceptionType(e)) { throw; } newObj = null; // set to null, so we do not return bad new object // Failed to create instance _resError = e; // Make sure the timer starts even if ThreadAbort occurs after setting the ErrorEvent. // timer allocation has to be done out of CER block Timer t = new Timer(new TimerCallback(this.ErrorCallback), null, Timeout.Infinite, Timeout.Infinite); bool timerIsNotDisposed; try { } finally { _waitHandles.ErrorEvent.Set(); _errorOccurred = true; // Enable the timer. // Note that the timer is created to allow periodic invocation. If ThreadAbort occurs in the middle of ErrorCallback, // the timer will restart. Otherwise, the timer callback (ErrorCallback) destroys the timer after resetting the error to avoid second callback. _errorTimer = t; timerIsNotDisposed = t.Change(_errorWait, _errorWait); } Debug.Assert(timerIsNotDisposed, "ErrorCallback timer has been disposed"); if (30000 < _errorWait) { _errorWait = 60000; } else { _errorWait *= 2; } throw; } return(newObj); }
private DbConnectionInternal UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection = null) { // called by user when they were not able to obtain a free object but // instead obtained creation mutex DbConnectionInternal obj = null; if (ErrorOccurred) { throw TryCloneCachedException(); } else { if ((oldConnection != null) || (Count < MaxPoolSize) || (0 == MaxPoolSize)) { // If we have an odd number of total objects, reclaim any dead objects. // If we did not find any objects to reclaim, create a new one. if ((oldConnection != null) || (Count & 0x1) == 0x1 || !ReclaimEmancipatedObjects()) { obj = CreateObject(owningObject, userOptions, oldConnection); } } return(obj); } }
private bool ReclaimEmancipatedObjects() { bool emancipatedObjectFound = false; List <DbConnectionInternal> reclaimedObjects = new List <DbConnectionInternal>(); 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]; emancipatedObjectFound = true; DeactivateObject(obj); } return(emancipatedObjectFound); }