From d4fa9b660c71dab305cc17870f02b16f8b7eef16 Mon Sep 17 00:00:00 2001 From: panoskj Date: Mon, 4 Dec 2023 12:48:59 +0200 Subject: [PATCH] Merge most "Network/Packet Reading & Processing" methods of TdsParserStateObject. --- .../SqlClient/TdsParserStateObject.netcore.cs | 555 ----------------- .../SqlClient/TdsParserStateObject.netfx.cs | 555 ----------------- .../Data/SqlClient/TdsParserStateObject.cs | 560 ++++++++++++++++++ 3 files changed, 560 insertions(+), 1110 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 9116826bb5..059c632eb1 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -261,561 +261,6 @@ internal void StartSession(object cancellationOwner) _cancellationOwner.Target = cancellationOwner; } - ///////////////////////////////////////// - // Network/Packet Reading & Processing // - ///////////////////////////////////////// - -#if DEBUG - private string _lastStack; -#endif - - internal bool TryReadNetworkPacket() - { - TdsParser.ReliabilitySection.Assert("unreliable call to TryReadNetworkPacket"); // you need to setup for a thread abort somewhere before you call this method - -#if DEBUG - Debug.Assert(!_shouldHaveEnoughData || _attentionSent, "Caller said there should be enough data, but we are currently reading a packet"); -#endif - - if (_snapshot != null) - { - if (_snapshotReplay) - { -#if DEBUG - // in debug builds stack traces contain line numbers so if we want to be - // able to compare the stack traces they must all be created in the same - // location in the code - string stackTrace = Environment.StackTrace; -#endif - if (_snapshot.MoveNext()) - { -#if DEBUG - if (s_checkNetworkPacketRetryStacks) - { - _snapshot.CheckStack(stackTrace); - } -#endif - return true; - } -#if DEBUG - else - { - if (s_checkNetworkPacketRetryStacks) - { - _lastStack = stackTrace; - } - } -#endif - } - - // previous buffer is in snapshot - _inBuff = new byte[_inBuff.Length]; - } - - if (_syncOverAsync) - { - ReadSniSyncOverAsync(); - return true; - } - - ReadSni(new TaskCompletionSource()); - -#if DEBUG - if (s_failAsyncPends) - { - throw new InvalidOperationException("Attempted to pend a read when s_failAsyncPends test hook was enabled"); - } - if (s_forceSyncOverAsyncAfterFirstPend) - { - _syncOverAsync = true; - } -#endif - Debug.Assert((_snapshot != null) ^ _asyncReadWithoutSnapshot, "Must have either _snapshot set up or _asyncReadWithoutSnapshot enabled (but not both) to pend a read"); - - return false; - } - - internal void PrepareReplaySnapshot() - { - _networkPacketTaskSource = null; - _snapshot.MoveToStart(); - } - - internal void ReadSniSyncOverAsync() - { - if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) - { - throw ADP.ClosedConnectionError(); - } - - PacketHandle readPacket = default; - - uint error; - - RuntimeHelpers.PrepareConstrainedRegions(); - bool shouldDecrement = false; - try - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadSniSync"); // you need to setup for a thread abort somewhere before you call this method - - Interlocked.Increment(ref _readingCount); - shouldDecrement = true; - - readPacket = ReadSyncOverAsync(GetTimeoutRemaining(), out error); - - Interlocked.Decrement(ref _readingCount); - shouldDecrement = false; - - if (_parser.MARSOn) - { // Only take reset lock on MARS and Async. - CheckSetResetConnectionState(error, CallbackType.Read); - } - - if (TdsEnums.SNI_SUCCESS == error) - { // Success - process results! - - Debug.Assert(!IsPacketEmpty(readPacket), "ReadNetworkPacket cannot be null in synchronous operation!"); - - ProcessSniPacket(readPacket, 0); -#if DEBUG - if (s_forcePendingReadsToWaitForUser) - { - _networkPacketTaskSource = new TaskCompletionSource(); - Interlocked.MemoryBarrier(); - _networkPacketTaskSource.Task.Wait(); - _networkPacketTaskSource = null; - } -#endif - } - else - { // Failure! - - Debug.Assert(!IsValidPacket(readPacket), "unexpected readPacket without corresponding SNIPacketRelease"); - - ReadSniError(this, error); - } - } - finally - { - if (shouldDecrement) - { - Interlocked.Decrement(ref _readingCount); - } - - if (!IsPacketEmpty(readPacket)) - { - ReleasePacket(readPacket); - } - - AssertValidState(); - } - } - - internal void OnConnectionClosed() - { - // the stateObj is not null, so the async invocation that registered this callback - // via the SqlReferenceCollection has not yet completed. We will look for a - // _networkPacketTaskSource and mark it faulted. If we don't find it, then - // TdsParserStateObject.ReadSni will abort when it does look to see if the parser - // is open. If we do, then when the call that created it completes and a continuation - // is registered, we will ensure the completion is called. - - // Note, this effort is necessary because when the app domain is being unloaded, - // we don't get callback from SNI. - - // first mark parser broken. This is to ensure that ReadSni will abort if it has - // not yet executed. - Parser.State = TdsParserState.Broken; - Parser.Connection.BreakConnection(); - - // Ensure that changing state occurs before checking _networkPacketTaskSource - Interlocked.MemoryBarrier(); - - // then check for networkPacketTaskSource - TaskCompletionSource taskSource = _networkPacketTaskSource; - if (taskSource != null) - { - taskSource.TrySetException(ADP.ExceptionWithStackTrace(ADP.ClosedConnectionError())); - } - - taskSource = _writeCompletionSource; - if (taskSource != null) - { - taskSource.TrySetException(ADP.ExceptionWithStackTrace(ADP.ClosedConnectionError())); - } - } - - public void SetTimeoutStateStopped() - { - Interlocked.Exchange(ref _timeoutState, TimeoutState.Stopped); - _timeoutIdentityValue = 0; - } - - public bool IsTimeoutStateExpired - { - get - { - int state = _timeoutState; - return state == TimeoutState.ExpiredAsync || state == TimeoutState.ExpiredSync; - } - } - - private void OnTimeoutAsync(object state) - { - if (_enforceTimeoutDelay) - { - Thread.Sleep(_enforcedTimeoutDelayInMilliSeconds); - } - - int currentIdentityValue = _timeoutIdentityValue; - TimeoutState timeoutState = (TimeoutState)state; - if (timeoutState.IdentityValue == _timeoutIdentityValue) - { - // the return value is not useful here because no choice is going to be made using it - // we only want to make this call to set the state knowing that it will be seen later - OnTimeoutCore(TimeoutState.Running, TimeoutState.ExpiredAsync); - } - else - { - Debug.WriteLine($"OnTimeoutAsync called with identity state={timeoutState.IdentityValue} but current identity is {currentIdentityValue} so it is being ignored"); - } - } - - private bool OnTimeoutSync(bool asyncClose = false) - { - return OnTimeoutCore(TimeoutState.Running, TimeoutState.ExpiredSync, asyncClose); - } - - /// - /// attempts to change the timout state from the expected state to the target state and if it succeeds - /// will setup the the stateobject into the timeout expired state - /// - /// the state that is the expected current state, state will change only if this is correct - /// the state that will be changed to if the expected state is correct - /// any close action to be taken by an async task to avoid deadlock. - /// boolean value indicating whether the call changed the timeout state - private bool OnTimeoutCore(int expectedState, int targetState, bool asyncClose = false) - { - Debug.Assert(targetState == TimeoutState.ExpiredAsync || targetState == TimeoutState.ExpiredSync, "OnTimeoutCore must have an expiry state as the targetState"); - - bool retval = false; - if (Interlocked.CompareExchange(ref _timeoutState, targetState, expectedState) == expectedState) - { - retval = true; - // lock protects against Close and Cancel - lock (this) - { - if (!_attentionSent) - { - AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); - - // Grab a reference to the _networkPacketTaskSource in case it becomes null while we are trying to use it - TaskCompletionSource source = _networkPacketTaskSource; - - if (_parser.Connection.IsInPool) - { - // We should never timeout if the connection is currently in the pool: the safest thing to do here is to doom the connection to avoid corruption - Debug.Assert(_parser.Connection.IsConnectionDoomed, "Timeout occurred while the connection is in the pool"); - _parser.State = TdsParserState.Broken; - _parser.Connection.BreakConnection(); - if (source != null) - { - source.TrySetCanceled(); - } - } - else if (_parser.State == TdsParserState.OpenLoggedIn) - { - try - { - SendAttention(mustTakeWriteLock: true, asyncClose); - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - // if unable to send attention, cancel the _networkPacketTaskSource to - // request the parser be broken. SNIWritePacket errors will already - // be in the _errors collection. - if (source != null) - { - source.TrySetCanceled(); - } - } - } - - // If we still haven't received a packet then we don't want to actually close the connection - // from another thread, so complete the pending operation as cancelled, informing them to break it - if (source != null) - { - Task.Delay(AttentionTimeoutSeconds * 1000).ContinueWith(_ => - { - // Only break the connection if the read didn't finish - if (!source.Task.IsCompleted) - { - int pendingCallback = IncrementPendingCallbacks(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - // If pendingCallback is at 3, then ReadAsyncCallback hasn't been called yet - // So it is safe for us to break the connection and cancel the Task (since we are not sure that ReadAsyncCallback will ever be called) - if ((pendingCallback == 3) && (!source.Task.IsCompleted)) - { - Debug.Assert(source == _networkPacketTaskSource, "_networkPacketTaskSource which is being timed is not the current task source"); - - // Try to throw the timeout exception and store it in the task - bool exceptionStored = false; - try - { - CheckThrowSNIException(); - } - catch (Exception ex) - { - if (source.TrySetException(ex)) - { - exceptionStored = true; - } - } - - // Ensure that the connection is no longer usable - // This is needed since the timeout error added above is non-fatal (and so throwing it won't break the connection) - _parser.State = TdsParserState.Broken; - _parser.Connection.BreakConnection(); - - // If we didn't get an exception (something else observed it?) then ensure that the task is cancelled - if (!exceptionStored) - { - source.TrySetCanceled(); - } - } - } - finally - { - DecrementPendingCallbacks(release: false); - } - } - }); - } - } - } - } - return retval; - } - - internal void ReadSni(TaskCompletionSource completion) - { - Debug.Assert(_networkPacketTaskSource == null || ((_asyncReadWithoutSnapshot) && (_networkPacketTaskSource.Task.IsCompleted)), "Pending async call or failed to replay snapshot when calling ReadSni"); - _networkPacketTaskSource = completion; - - // Ensure that setting the completion source is completed before checking the state - Interlocked.MemoryBarrier(); - - // We must check after assigning _networkPacketTaskSource to avoid races with - // SqlCommand.OnConnectionClosed - if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) - { - throw ADP.ClosedConnectionError(); - } - -#if DEBUG - if (s_forcePendingReadsToWaitForUser) - { - _realNetworkPacketTaskSource = new TaskCompletionSource(); - } -#endif - - - PacketHandle readPacket = default; - - uint error = 0; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - Debug.Assert(completion != null, "Async on but null asyncResult passed"); - - // if the state is currently stopped then change it to running and allocate a new identity value from - // the identity source. The identity value is used to correlate timer callback events to the currently - // running timeout and prevents a late timer callback affecting a result it does not relate to - int previousTimeoutState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Running, TimeoutState.Stopped); - Debug.Assert(previousTimeoutState == TimeoutState.Stopped, "previous timeout state was not Stopped"); - if (previousTimeoutState == TimeoutState.Stopped) - { - Debug.Assert(_timeoutIdentityValue == 0, "timer was previously stopped without resetting the _identityValue"); - _timeoutIdentityValue = Interlocked.Increment(ref _timeoutIdentitySource); - } - - _networkPacketTimeout?.Dispose(); - - _networkPacketTimeout = ADP.UnsafeCreateTimer( - _onTimeoutAsync, - new TimeoutState(_timeoutIdentityValue), - Timeout.Infinite, - Timeout.Infinite - ); - - - // -1 == Infinite - // 0 == Already timed out (NOTE: To simulate the same behavior as sync we will only timeout on 0 if we receive an IO Pending from SNI) - // >0 == Actual timeout remaining - int msecsRemaining = GetTimeoutRemaining(); - if (msecsRemaining > 0) - { - ChangeNetworkPacketTimeout(msecsRemaining, Timeout.Infinite); - } - - SessionHandle handle = default; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { } - finally - { - Interlocked.Increment(ref _readingCount); - - handle = SessionHandle; - if (!handle.IsNull) - { - IncrementPendingCallbacks(); - - readPacket = ReadAsync(handle, out error); - - if (!(TdsEnums.SNI_SUCCESS == error || TdsEnums.SNI_SUCCESS_IO_PENDING == error)) - { - DecrementPendingCallbacks(false); // Failure - we won't receive callback! - } - } - - Interlocked.Decrement(ref _readingCount); - } - - if (handle.IsNull) - { - throw ADP.ClosedConnectionError(); - } - - if (TdsEnums.SNI_SUCCESS == error) - { // Success - process results! - Debug.Assert(IsValidPacket(readPacket), "ReadNetworkPacket should not have been null on this async operation!"); - // Evaluate this condition for MANAGED_SNI. This may not be needed because the network call is happening Async and only the callback can receive a success. - ReadAsyncCallback(IntPtr.Zero, readPacket, 0); - - // Only release packet for Managed SNI as for Native SNI packet is released in finally block. - if (TdsParserStateObjectFactory.UseManagedSNI && !IsPacketEmpty(readPacket)) - { - ReleasePacket(readPacket); - } - } - else if (TdsEnums.SNI_SUCCESS_IO_PENDING != error) - { // FAILURE! - Debug.Assert(IsPacketEmpty(readPacket), "unexpected readPacket without corresponding SNIPacketRelease"); - - ReadSniError(this, error); -#if DEBUG - if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null)) - { - _realNetworkPacketTaskSource.TrySetResult(null); - } - else -#endif - { - _networkPacketTaskSource.TrySetResult(null); - } - // Disable timeout timer on error - SetTimeoutStateStopped(); - ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); - } - else if (msecsRemaining == 0) - { - // Got IO Pending, but we have no time left to wait - // disable the timer and set the error state by calling OnTimeoutSync - ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); - OnTimeoutSync(); - } - // DO NOT HANDLE PENDING READ HERE - which is TdsEnums.SNI_SUCCESS_IO_PENDING state. - // That is handled by user who initiated async read, or by ReadNetworkPacket which is sync over async. - } - finally - { - if (!TdsParserStateObjectFactory.UseManagedSNI) - { - if (!IsPacketEmpty(readPacket)) - { - // Be sure to release packet, otherwise it will be leaked by native. - ReleasePacket(readPacket); - } - } - AssertValidState(); - } - } - - /// - /// Checks to see if the underlying connection is still alive (used by connection pool resiliency) - /// NOTE: This is not safe to do on a connection that is currently in use - /// NOTE: This will mark the connection as broken if it is found to be dead - /// - /// If true then an exception will be thrown if the connection is found to be dead, otherwise no exception will be thrown - /// True if the connection is still alive, otherwise false - internal bool IsConnectionAlive(bool throwOnException) - { - Debug.Assert(_parser.Connection == null || _parser.Connection.Pool != null, "Shouldn't be calling IsConnectionAlive on non-pooled connections"); - bool isAlive = true; - - if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value > CheckConnectionWindow) - { - if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed))) - { - isAlive = false; - if (throwOnException) - { - throw SQL.ConnectionDoomed(); - } - } - else if ((_pendingCallbacks > 1) || ((_parser.Connection != null) && (!_parser.Connection.IsInPool))) - { - // This connection is currently in use, assume that the connection is 'alive' - // NOTE: SNICheckConnection is not currently supported for connections that are in use - Debug.Assert(true, "Call to IsConnectionAlive while connection is in use"); - } - else - { - uint error; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - TdsParser.ReliabilitySection.Assert("unreliable call to IsConnectionAlive"); // you need to setup for a thread abort somewhere before you call this method - - SniContext = SniContext.Snix_Connect; - - error = CheckConnection(); - if ((error != TdsEnums.SNI_SUCCESS) && (error != TdsEnums.SNI_WAIT_TIMEOUT)) - { - // Connection is dead - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.IsConnectionAlive | Info | State Object Id {0}, received error {1} on idle connection", _objectID, (int)error); - isAlive = false; - if (throwOnException) - { - // Get the error from SNI so that we can throw the correct exception - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(); - } - } - else - { - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; - } - } - finally - { - } - } - } - - return isAlive; - } - /// /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections) /// NOTE: This is not safe to do on a connection that is currently in use diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index a373c29ac4..5d4a332665 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -387,561 +387,6 @@ internal void StartSession(int objectID) _allowObjectID = objectID; } - ///////////////////////////////////////// - // Network/Packet Reading & Processing // - ///////////////////////////////////////// - -#if DEBUG - private string _lastStack; -#endif - - internal bool TryReadNetworkPacket() - { - TdsParser.ReliabilitySection.Assert("unreliable call to TryReadNetworkPacket"); // you need to setup for a thread abort somewhere before you call this method - -#if DEBUG - Debug.Assert(!_shouldHaveEnoughData || _attentionSent, "Caller said there should be enough data, but we are currently reading a packet"); -#endif - - if (_snapshot != null) - { - if (_snapshotReplay) - { -#if DEBUG - // in debug builds stack traces contain line numbers so if we want to be - // able to compare the stack traces they must all be created in the same - // location in the code - string stackTrace = Environment.StackTrace; -#endif - if (_snapshot.MoveNext()) - { -#if DEBUG - if (s_checkNetworkPacketRetryStacks) - { - _snapshot.CheckStack(stackTrace); - } -#endif - return true; - } -#if DEBUG - else - { - if (s_checkNetworkPacketRetryStacks) - { - _lastStack = stackTrace; - } - } -#endif - } - - // previous buffer is in snapshot - _inBuff = new byte[_inBuff.Length]; - } - - if (_syncOverAsync) - { - ReadSniSyncOverAsync(); - return true; - } - - ReadSni(new TaskCompletionSource()); - -#if DEBUG - if (s_failAsyncPends) - { - throw new InvalidOperationException("Attempted to pend a read when s_failAsyncPends test hook was enabled"); - } - if (s_forceSyncOverAsyncAfterFirstPend) - { - _syncOverAsync = true; - } -#endif - Debug.Assert((_snapshot != null) ^ _asyncReadWithoutSnapshot, "Must have either _snapshot set up or _asyncReadWithoutSnapshot enabled (but not both) to pend a read"); - - return false; - } - - internal void PrepareReplaySnapshot() - { - _networkPacketTaskSource = null; - _snapshot.MoveToStart(); - } - - internal void ReadSniSyncOverAsync() - { - if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) - { - throw ADP.ClosedConnectionError(); - } - - PacketHandle readPacket = default; - - uint error; - - RuntimeHelpers.PrepareConstrainedRegions(); - bool shouldDecrement = false; - try - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadSniSync"); // you need to setup for a thread abort somewhere before you call this method - - Interlocked.Increment(ref _readingCount); - shouldDecrement = true; - - readPacket = ReadSyncOverAsync(GetTimeoutRemaining(), out error); - - Interlocked.Decrement(ref _readingCount); - shouldDecrement = false; - - if (_parser.MARSOn) - { // Only take reset lock on MARS and Async. - CheckSetResetConnectionState(error, CallbackType.Read); - } - - if (TdsEnums.SNI_SUCCESS == error) - { // Success - process results! - - Debug.Assert(!IsPacketEmpty(readPacket), "ReadNetworkPacket cannot be null in synchronous operation!"); - - ProcessSniPacket(readPacket, 0); -#if DEBUG - if (s_forcePendingReadsToWaitForUser) - { - _networkPacketTaskSource = new TaskCompletionSource(); - Interlocked.MemoryBarrier(); - _networkPacketTaskSource.Task.Wait(); - _networkPacketTaskSource = null; - } -#endif - } - else - { // Failure! - - Debug.Assert(!IsValidPacket(readPacket), "unexpected readPacket without corresponding SNIPacketRelease"); - - ReadSniError(this, error); - } - } - finally - { - if (shouldDecrement) - { - Interlocked.Decrement(ref _readingCount); - } - - if (!IsPacketEmpty(readPacket)) - { - ReleasePacket(readPacket); - } - - AssertValidState(); - } - } - - internal void OnConnectionClosed() - { - // the stateObj is not null, so the async invocation that registered this callback - // via the SqlReferenceCollection has not yet completed. We will look for a - // _networkPacketTaskSource and mark it faulted. If we don't find it, then - // TdsParserStateObject.ReadSni will abort when it does look to see if the parser - // is open. If we do, then when the call that created it completes and a continuation - // is registered, we will ensure the completion is called. - - // Note, this effort is necessary because when the app domain is being unloaded, - // we don't get callback from SNI. - - // first mark parser broken. This is to ensure that ReadSni will abort if it has - // not yet executed. - Parser.State = TdsParserState.Broken; - Parser.Connection.BreakConnection(); - - // Ensure that changing state occurs before checking _networkPacketTaskSource - Interlocked.MemoryBarrier(); - - // then check for networkPacketTaskSource - TaskCompletionSource taskSource = _networkPacketTaskSource; - if (taskSource != null) - { - taskSource.TrySetException(ADP.ExceptionWithStackTrace(ADP.ClosedConnectionError())); - } - - taskSource = _writeCompletionSource; - if (taskSource != null) - { - taskSource.TrySetException(ADP.ExceptionWithStackTrace(ADP.ClosedConnectionError())); - } - } - - public void SetTimeoutStateStopped() - { - Interlocked.Exchange(ref _timeoutState, TimeoutState.Stopped); - _timeoutIdentityValue = 0; - } - - public bool IsTimeoutStateExpired - { - get - { - int state = _timeoutState; - return state == TimeoutState.ExpiredAsync || state == TimeoutState.ExpiredSync; - } - } - - private void OnTimeoutAsync(object state) - { - if (_enforceTimeoutDelay) - { - Thread.Sleep(_enforcedTimeoutDelayInMilliSeconds); - } - - int currentIdentityValue = _timeoutIdentityValue; - TimeoutState timeoutState = (TimeoutState)state; - if (timeoutState.IdentityValue == _timeoutIdentityValue) - { - // the return value is not useful here because no choice is going to be made using it - // we only want to make this call to set the state knowing that it will be seen later - OnTimeoutCore(TimeoutState.Running, TimeoutState.ExpiredAsync); - } - else - { - Debug.WriteLine($"OnTimeoutAsync called with identity state={timeoutState.IdentityValue} but current identity is {currentIdentityValue} so it is being ignored"); - } - } - - private bool OnTimeoutSync(bool asyncClose = false) - { - return OnTimeoutCore(TimeoutState.Running, TimeoutState.ExpiredSync, asyncClose); - } - - /// - /// attempts to change the timout state from the expected state to the target state and if it succeeds - /// will setup the the stateobject into the timeout expired state - /// - /// the state that is the expected current state, state will change only if this is correct - /// the state that will be changed to if the expected state is correct - /// any close action to be taken by an async task to avoid deadlock. - /// boolean value indicating whether the call changed the timeout state - private bool OnTimeoutCore(int expectedState, int targetState, bool asyncClose = false) - { - Debug.Assert(targetState == TimeoutState.ExpiredAsync || targetState == TimeoutState.ExpiredSync, "OnTimeoutCore must have an expiry state as the targetState"); - - bool retval = false; - if (Interlocked.CompareExchange(ref _timeoutState, targetState, expectedState) == expectedState) - { - retval = true; - // lock protects against Close and Cancel - lock (this) - { - if (!_attentionSent) - { - AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); - - // Grab a reference to the _networkPacketTaskSource in case it becomes null while we are trying to use it - TaskCompletionSource source = _networkPacketTaskSource; - - if (_parser.Connection.IsInPool) - { - // We should never timeout if the connection is currently in the pool: the safest thing to do here is to doom the connection to avoid corruption - Debug.Assert(_parser.Connection.IsConnectionDoomed, "Timeout occurred while the connection is in the pool"); - _parser.State = TdsParserState.Broken; - _parser.Connection.BreakConnection(); - if (source != null) - { - source.TrySetCanceled(); - } - } - else if (_parser.State == TdsParserState.OpenLoggedIn) - { - try - { - SendAttention(mustTakeWriteLock: true, asyncClose); - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - // if unable to send attention, cancel the _networkPacketTaskSource to - // request the parser be broken. SNIWritePacket errors will already - // be in the _errors collection. - if (source != null) - { - source.TrySetCanceled(); - } - } - } - - // If we still haven't received a packet then we don't want to actually close the connection - // from another thread, so complete the pending operation as cancelled, informing them to break it - if (source != null) - { - Task.Delay(AttentionTimeoutSeconds * 1000).ContinueWith(_ => - { - // Only break the connection if the read didn't finish - if (!source.Task.IsCompleted) - { - int pendingCallback = IncrementPendingCallbacks(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - // If pendingCallback is at 3, then ReadAsyncCallback hasn't been called yet - // So it is safe for us to break the connection and cancel the Task (since we are not sure that ReadAsyncCallback will ever be called) - if ((pendingCallback == 3) && (!source.Task.IsCompleted)) - { - Debug.Assert(source == _networkPacketTaskSource, "_networkPacketTaskSource which is being timed is not the current task source"); - - // Try to throw the timeout exception and store it in the task - bool exceptionStored = false; - try - { - CheckThrowSNIException(); - } - catch (Exception ex) - { - if (source.TrySetException(ex)) - { - exceptionStored = true; - } - } - - // Ensure that the connection is no longer usable - // This is needed since the timeout error added above is non-fatal (and so throwing it won't break the connection) - _parser.State = TdsParserState.Broken; - _parser.Connection.BreakConnection(); - - // If we didn't get an exception (something else observed it?) then ensure that the task is cancelled - if (!exceptionStored) - { - source.TrySetCanceled(); - } - } - } - finally - { - DecrementPendingCallbacks(release: false); - } - } - }); - } - } - } - } - return retval; - } - - internal void ReadSni(TaskCompletionSource completion) - { - Debug.Assert(_networkPacketTaskSource == null || ((_asyncReadWithoutSnapshot) && (_networkPacketTaskSource.Task.IsCompleted)), "Pending async call or failed to replay snapshot when calling ReadSni"); - _networkPacketTaskSource = completion; - - // Ensure that setting the completion source is completed before checking the state - Interlocked.MemoryBarrier(); - - // We must check after assigning _networkPacketTaskSource to avoid races with - // SqlCommand.OnConnectionClosed - if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) - { - throw ADP.ClosedConnectionError(); - } - -#if DEBUG - if (s_forcePendingReadsToWaitForUser) - { - _realNetworkPacketTaskSource = new TaskCompletionSource(); - } -#endif - - - PacketHandle readPacket = default; - - uint error = 0; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - Debug.Assert(completion != null, "Async on but null asyncResult passed"); - - // if the state is currently stopped then change it to running and allocate a new identity value from - // the identity source. The identity value is used to correlate timer callback events to the currently - // running timeout and prevents a late timer callback affecting a result it does not relate to - int previousTimeoutState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Running, TimeoutState.Stopped); - Debug.Assert(previousTimeoutState == TimeoutState.Stopped, "previous timeout state was not Stopped"); - if (previousTimeoutState == TimeoutState.Stopped) - { - Debug.Assert(_timeoutIdentityValue == 0, "timer was previously stopped without resetting the _identityValue"); - _timeoutIdentityValue = Interlocked.Increment(ref _timeoutIdentitySource); - } - - _networkPacketTimeout?.Dispose(); - - _networkPacketTimeout = ADP.UnsafeCreateTimer( - _onTimeoutAsync, - new TimeoutState(_timeoutIdentityValue), - Timeout.Infinite, - Timeout.Infinite - ); - - - // -1 == Infinite - // 0 == Already timed out (NOTE: To simulate the same behavior as sync we will only timeout on 0 if we receive an IO Pending from SNI) - // >0 == Actual timeout remaining - int msecsRemaining = GetTimeoutRemaining(); - if (msecsRemaining > 0) - { - ChangeNetworkPacketTimeout(msecsRemaining, Timeout.Infinite); - } - - SessionHandle handle = default; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { } - finally - { - Interlocked.Increment(ref _readingCount); - - handle = SessionHandle; - if (!handle.IsNull) - { - IncrementPendingCallbacks(); - - readPacket = ReadAsync(handle, out error); - - if (!(TdsEnums.SNI_SUCCESS == error || TdsEnums.SNI_SUCCESS_IO_PENDING == error)) - { - DecrementPendingCallbacks(false); // Failure - we won't receive callback! - } - } - - Interlocked.Decrement(ref _readingCount); - } - - if (handle.IsNull) - { - throw ADP.ClosedConnectionError(); - } - - if (TdsEnums.SNI_SUCCESS == error) - { // Success - process results! - Debug.Assert(IsValidPacket(readPacket), "ReadNetworkPacket should not have been null on this async operation!"); - // Evaluate this condition for MANAGED_SNI. This may not be needed because the network call is happening Async and only the callback can receive a success. - ReadAsyncCallback(IntPtr.Zero, readPacket, 0); - - // Only release packet for Managed SNI as for Native SNI packet is released in finally block. - if (TdsParserStateObjectFactory.UseManagedSNI && !IsPacketEmpty(readPacket)) - { - ReleasePacket(readPacket); - } - } - else if (TdsEnums.SNI_SUCCESS_IO_PENDING != error) - { // FAILURE! - Debug.Assert(IsPacketEmpty(readPacket), "unexpected readPacket without corresponding SNIPacketRelease"); - - ReadSniError(this, error); -#if DEBUG - if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null)) - { - _realNetworkPacketTaskSource.TrySetResult(null); - } - else -#endif - { - _networkPacketTaskSource.TrySetResult(null); - } - // Disable timeout timer on error - SetTimeoutStateStopped(); - ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); - } - else if (msecsRemaining == 0) - { - // Got IO Pending, but we have no time left to wait - // disable the timer and set the error state by calling OnTimeoutSync - ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); - OnTimeoutSync(); - } - // DO NOT HANDLE PENDING READ HERE - which is TdsEnums.SNI_SUCCESS_IO_PENDING state. - // That is handled by user who initiated async read, or by ReadNetworkPacket which is sync over async. - } - finally - { - if (!TdsParserStateObjectFactory.UseManagedSNI) - { - if (!IsPacketEmpty(readPacket)) - { - // Be sure to release packet, otherwise it will be leaked by native. - ReleasePacket(readPacket); - } - } - AssertValidState(); - } - } - - /// - /// Checks to see if the underlying connection is still alive (used by connection pool resiliency) - /// NOTE: This is not safe to do on a connection that is currently in use - /// NOTE: This will mark the connection as broken if it is found to be dead - /// - /// If true then an exception will be thrown if the connection is found to be dead, otherwise no exception will be thrown - /// True if the connection is still alive, otherwise false - internal bool IsConnectionAlive(bool throwOnException) - { - Debug.Assert(_parser.Connection == null || _parser.Connection.Pool != null, "Shouldn't be calling IsConnectionAlive on non-pooled connections"); - bool isAlive = true; - - if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value > CheckConnectionWindow) - { - if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed))) - { - isAlive = false; - if (throwOnException) - { - throw SQL.ConnectionDoomed(); - } - } - else if ((_pendingCallbacks > 1) || ((_parser.Connection != null) && (!_parser.Connection.IsInPool))) - { - // This connection is currently in use, assume that the connection is 'alive' - // NOTE: SNICheckConnection is not currently supported for connections that are in use - Debug.Assert(true, "Call to IsConnectionAlive while connection is in use"); - } - else - { - uint error; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - TdsParser.ReliabilitySection.Assert("unreliable call to IsConnectionAlive"); // you need to setup for a thread abort somewhere before you call this method - - SniContext = SniContext.Snix_Connect; - - error = CheckConnection(); - if ((error != TdsEnums.SNI_SUCCESS) && (error != TdsEnums.SNI_WAIT_TIMEOUT)) - { - // Connection is dead - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.IsConnectionAlive | Info | State Object Id {0}, received error {1} on idle connection", _objectID, (int)error); - isAlive = false; - if (throwOnException) - { - // Get the error from SNI so that we can throw the correct exception - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(); - } - } - else - { - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; - } - } - finally - { - } - } - } - - return isAlive; - } - /// /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections) /// NOTE: This is not safe to do on a connection that is currently in use diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index fec1d6ee39..f1730225cd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -13,6 +13,11 @@ namespace Microsoft.Data.SqlClient { +#if NETFRAMEWORK + using PacketHandle = IntPtr; + using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers; +#endif + sealed internal class LastIOTimer { internal long _value; @@ -1920,6 +1925,561 @@ internal bool TrySkipBytes(int num) return TryReadByteArray(Span.Empty, num); } + ///////////////////////////////////////// + // Network/Packet Reading & Processing // + ///////////////////////////////////////// + +#if DEBUG + private string _lastStack; +#endif + + internal bool TryReadNetworkPacket() + { + TdsParser.ReliabilitySection.Assert("unreliable call to TryReadNetworkPacket"); // you need to setup for a thread abort somewhere before you call this method + +#if DEBUG + Debug.Assert(!_shouldHaveEnoughData || _attentionSent, "Caller said there should be enough data, but we are currently reading a packet"); +#endif + + if (_snapshot != null) + { + if (_snapshotReplay) + { +#if DEBUG + // in debug builds stack traces contain line numbers so if we want to be + // able to compare the stack traces they must all be created in the same + // location in the code + string stackTrace = Environment.StackTrace; +#endif + if (_snapshot.MoveNext()) + { +#if DEBUG + if (s_checkNetworkPacketRetryStacks) + { + _snapshot.CheckStack(stackTrace); + } +#endif + return true; + } +#if DEBUG + else + { + if (s_checkNetworkPacketRetryStacks) + { + _lastStack = stackTrace; + } + } +#endif + } + + // previous buffer is in snapshot + _inBuff = new byte[_inBuff.Length]; + } + + if (_syncOverAsync) + { + ReadSniSyncOverAsync(); + return true; + } + + ReadSni(new TaskCompletionSource()); + +#if DEBUG + if (s_failAsyncPends) + { + throw new InvalidOperationException("Attempted to pend a read when s_failAsyncPends test hook was enabled"); + } + if (s_forceSyncOverAsyncAfterFirstPend) + { + _syncOverAsync = true; + } +#endif + Debug.Assert((_snapshot != null) ^ _asyncReadWithoutSnapshot, "Must have either _snapshot set up or _asyncReadWithoutSnapshot enabled (but not both) to pend a read"); + + return false; + } + + internal void PrepareReplaySnapshot() + { + _networkPacketTaskSource = null; + _snapshot.MoveToStart(); + } + + internal void ReadSniSyncOverAsync() + { + if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) + { + throw ADP.ClosedConnectionError(); + } + + PacketHandle readPacket = default; + + uint error; + + RuntimeHelpers.PrepareConstrainedRegions(); + bool shouldDecrement = false; + try + { + TdsParser.ReliabilitySection.Assert("unreliable call to ReadSniSync"); // you need to setup for a thread abort somewhere before you call this method + + Interlocked.Increment(ref _readingCount); + shouldDecrement = true; + + readPacket = ReadSyncOverAsync(GetTimeoutRemaining(), out error); + + Interlocked.Decrement(ref _readingCount); + shouldDecrement = false; + + if (_parser.MARSOn) + { // Only take reset lock on MARS and Async. + CheckSetResetConnectionState(error, CallbackType.Read); + } + + if (TdsEnums.SNI_SUCCESS == error) + { // Success - process results! + + Debug.Assert(!IsPacketEmpty(readPacket), "ReadNetworkPacket cannot be null in synchronous operation!"); + + ProcessSniPacket(readPacket, 0); +#if DEBUG + if (s_forcePendingReadsToWaitForUser) + { + _networkPacketTaskSource = new TaskCompletionSource(); + Interlocked.MemoryBarrier(); + _networkPacketTaskSource.Task.Wait(); + _networkPacketTaskSource = null; + } +#endif + } + else + { // Failure! + + Debug.Assert(!IsValidPacket(readPacket), "unexpected readPacket without corresponding SNIPacketRelease"); + + ReadSniError(this, error); + } + } + finally + { + if (shouldDecrement) + { + Interlocked.Decrement(ref _readingCount); + } + + if (!IsPacketEmpty(readPacket)) + { + ReleasePacket(readPacket); + } + + AssertValidState(); + } + } + + internal void OnConnectionClosed() + { + // the stateObj is not null, so the async invocation that registered this callback + // via the SqlReferenceCollection has not yet completed. We will look for a + // _networkPacketTaskSource and mark it faulted. If we don't find it, then + // TdsParserStateObject.ReadSni will abort when it does look to see if the parser + // is open. If we do, then when the call that created it completes and a continuation + // is registered, we will ensure the completion is called. + + // Note, this effort is necessary because when the app domain is being unloaded, + // we don't get callback from SNI. + + // first mark parser broken. This is to ensure that ReadSni will abort if it has + // not yet executed. + Parser.State = TdsParserState.Broken; + Parser.Connection.BreakConnection(); + + // Ensure that changing state occurs before checking _networkPacketTaskSource + Interlocked.MemoryBarrier(); + + // then check for networkPacketTaskSource + TaskCompletionSource taskSource = _networkPacketTaskSource; + if (taskSource != null) + { + taskSource.TrySetException(ADP.ExceptionWithStackTrace(ADP.ClosedConnectionError())); + } + + taskSource = _writeCompletionSource; + if (taskSource != null) + { + taskSource.TrySetException(ADP.ExceptionWithStackTrace(ADP.ClosedConnectionError())); + } + } + + public void SetTimeoutStateStopped() + { + Interlocked.Exchange(ref _timeoutState, TimeoutState.Stopped); + _timeoutIdentityValue = 0; + } + + public bool IsTimeoutStateExpired + { + get + { + int state = _timeoutState; + return state == TimeoutState.ExpiredAsync || state == TimeoutState.ExpiredSync; + } + } + + private void OnTimeoutAsync(object state) + { + if (_enforceTimeoutDelay) + { + Thread.Sleep(_enforcedTimeoutDelayInMilliSeconds); + } + + int currentIdentityValue = _timeoutIdentityValue; + TimeoutState timeoutState = (TimeoutState)state; + if (timeoutState.IdentityValue == _timeoutIdentityValue) + { + // the return value is not useful here because no choice is going to be made using it + // we only want to make this call to set the state knowing that it will be seen later + OnTimeoutCore(TimeoutState.Running, TimeoutState.ExpiredAsync); + } + else + { + Debug.WriteLine($"OnTimeoutAsync called with identity state={timeoutState.IdentityValue} but current identity is {currentIdentityValue} so it is being ignored"); + } + } + + private bool OnTimeoutSync(bool asyncClose = false) + { + return OnTimeoutCore(TimeoutState.Running, TimeoutState.ExpiredSync, asyncClose); + } + + /// + /// attempts to change the timout state from the expected state to the target state and if it succeeds + /// will setup the the stateobject into the timeout expired state + /// + /// the state that is the expected current state, state will change only if this is correct + /// the state that will be changed to if the expected state is correct + /// any close action to be taken by an async task to avoid deadlock. + /// boolean value indicating whether the call changed the timeout state + private bool OnTimeoutCore(int expectedState, int targetState, bool asyncClose = false) + { + Debug.Assert(targetState == TimeoutState.ExpiredAsync || targetState == TimeoutState.ExpiredSync, "OnTimeoutCore must have an expiry state as the targetState"); + + bool retval = false; + if (Interlocked.CompareExchange(ref _timeoutState, targetState, expectedState) == expectedState) + { + retval = true; + // lock protects against Close and Cancel + lock (this) + { + if (!_attentionSent) + { + AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); + + // Grab a reference to the _networkPacketTaskSource in case it becomes null while we are trying to use it + TaskCompletionSource source = _networkPacketTaskSource; + + if (_parser.Connection.IsInPool) + { + // We should never timeout if the connection is currently in the pool: the safest thing to do here is to doom the connection to avoid corruption + Debug.Assert(_parser.Connection.IsConnectionDoomed, "Timeout occurred while the connection is in the pool"); + _parser.State = TdsParserState.Broken; + _parser.Connection.BreakConnection(); + if (source != null) + { + source.TrySetCanceled(); + } + } + else if (_parser.State == TdsParserState.OpenLoggedIn) + { + try + { + SendAttention(mustTakeWriteLock: true, asyncClose); + } + catch (Exception e) + { + if (!ADP.IsCatchableExceptionType(e)) + { + throw; + } + // if unable to send attention, cancel the _networkPacketTaskSource to + // request the parser be broken. SNIWritePacket errors will already + // be in the _errors collection. + if (source != null) + { + source.TrySetCanceled(); + } + } + } + + // If we still haven't received a packet then we don't want to actually close the connection + // from another thread, so complete the pending operation as cancelled, informing them to break it + if (source != null) + { + Task.Delay(AttentionTimeoutSeconds * 1000).ContinueWith(_ => + { + // Only break the connection if the read didn't finish + if (!source.Task.IsCompleted) + { + int pendingCallback = IncrementPendingCallbacks(); + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + // If pendingCallback is at 3, then ReadAsyncCallback hasn't been called yet + // So it is safe for us to break the connection and cancel the Task (since we are not sure that ReadAsyncCallback will ever be called) + if ((pendingCallback == 3) && (!source.Task.IsCompleted)) + { + Debug.Assert(source == _networkPacketTaskSource, "_networkPacketTaskSource which is being timed is not the current task source"); + + // Try to throw the timeout exception and store it in the task + bool exceptionStored = false; + try + { + CheckThrowSNIException(); + } + catch (Exception ex) + { + if (source.TrySetException(ex)) + { + exceptionStored = true; + } + } + + // Ensure that the connection is no longer usable + // This is needed since the timeout error added above is non-fatal (and so throwing it won't break the connection) + _parser.State = TdsParserState.Broken; + _parser.Connection.BreakConnection(); + + // If we didn't get an exception (something else observed it?) then ensure that the task is cancelled + if (!exceptionStored) + { + source.TrySetCanceled(); + } + } + } + finally + { + DecrementPendingCallbacks(release: false); + } + } + }); + } + } + } + } + return retval; + } + + internal void ReadSni(TaskCompletionSource completion) + { + Debug.Assert(_networkPacketTaskSource == null || ((_asyncReadWithoutSnapshot) && (_networkPacketTaskSource.Task.IsCompleted)), "Pending async call or failed to replay snapshot when calling ReadSni"); + _networkPacketTaskSource = completion; + + // Ensure that setting the completion source is completed before checking the state + Interlocked.MemoryBarrier(); + + // We must check after assigning _networkPacketTaskSource to avoid races with + // SqlCommand.OnConnectionClosed + if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) + { + throw ADP.ClosedConnectionError(); + } + +#if DEBUG + if (s_forcePendingReadsToWaitForUser) + { + _realNetworkPacketTaskSource = new TaskCompletionSource(); + } +#endif + + + PacketHandle readPacket = default; + + uint error = 0; + + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + Debug.Assert(completion != null, "Async on but null asyncResult passed"); + + // if the state is currently stopped then change it to running and allocate a new identity value from + // the identity source. The identity value is used to correlate timer callback events to the currently + // running timeout and prevents a late timer callback affecting a result it does not relate to + int previousTimeoutState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Running, TimeoutState.Stopped); + Debug.Assert(previousTimeoutState == TimeoutState.Stopped, "previous timeout state was not Stopped"); + if (previousTimeoutState == TimeoutState.Stopped) + { + Debug.Assert(_timeoutIdentityValue == 0, "timer was previously stopped without resetting the _identityValue"); + _timeoutIdentityValue = Interlocked.Increment(ref _timeoutIdentitySource); + } + + _networkPacketTimeout?.Dispose(); + + _networkPacketTimeout = ADP.UnsafeCreateTimer( + _onTimeoutAsync, + new TimeoutState(_timeoutIdentityValue), + Timeout.Infinite, + Timeout.Infinite + ); + + + // -1 == Infinite + // 0 == Already timed out (NOTE: To simulate the same behavior as sync we will only timeout on 0 if we receive an IO Pending from SNI) + // >0 == Actual timeout remaining + int msecsRemaining = GetTimeoutRemaining(); + if (msecsRemaining > 0) + { + ChangeNetworkPacketTimeout(msecsRemaining, Timeout.Infinite); + } + + SessionHandle handle = default; + + RuntimeHelpers.PrepareConstrainedRegions(); + try + { } + finally + { + Interlocked.Increment(ref _readingCount); + + handle = SessionHandle; + if (!handle.IsNull) + { + IncrementPendingCallbacks(); + + readPacket = ReadAsync(handle, out error); + + if (!(TdsEnums.SNI_SUCCESS == error || TdsEnums.SNI_SUCCESS_IO_PENDING == error)) + { + DecrementPendingCallbacks(false); // Failure - we won't receive callback! + } + } + + Interlocked.Decrement(ref _readingCount); + } + + if (handle.IsNull) + { + throw ADP.ClosedConnectionError(); + } + + if (TdsEnums.SNI_SUCCESS == error) + { // Success - process results! + Debug.Assert(IsValidPacket(readPacket), "ReadNetworkPacket should not have been null on this async operation!"); + // Evaluate this condition for MANAGED_SNI. This may not be needed because the network call is happening Async and only the callback can receive a success. + ReadAsyncCallback(IntPtr.Zero, readPacket, 0); + + // Only release packet for Managed SNI as for Native SNI packet is released in finally block. + if (TdsParserStateObjectFactory.UseManagedSNI && !IsPacketEmpty(readPacket)) + { + ReleasePacket(readPacket); + } + } + else if (TdsEnums.SNI_SUCCESS_IO_PENDING != error) + { // FAILURE! + Debug.Assert(IsPacketEmpty(readPacket), "unexpected readPacket without corresponding SNIPacketRelease"); + + ReadSniError(this, error); +#if DEBUG + if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null)) + { + _realNetworkPacketTaskSource.TrySetResult(null); + } + else +#endif + { + _networkPacketTaskSource.TrySetResult(null); + } + // Disable timeout timer on error + SetTimeoutStateStopped(); + ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); + } + else if (msecsRemaining == 0) + { + // Got IO Pending, but we have no time left to wait + // disable the timer and set the error state by calling OnTimeoutSync + ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); + OnTimeoutSync(); + } + // DO NOT HANDLE PENDING READ HERE - which is TdsEnums.SNI_SUCCESS_IO_PENDING state. + // That is handled by user who initiated async read, or by ReadNetworkPacket which is sync over async. + } + finally + { + if (!TdsParserStateObjectFactory.UseManagedSNI) + { + if (!IsPacketEmpty(readPacket)) + { + // Be sure to release packet, otherwise it will be leaked by native. + ReleasePacket(readPacket); + } + } + AssertValidState(); + } + } + + /// + /// Checks to see if the underlying connection is still alive (used by connection pool resiliency) + /// NOTE: This is not safe to do on a connection that is currently in use + /// NOTE: This will mark the connection as broken if it is found to be dead + /// + /// If true then an exception will be thrown if the connection is found to be dead, otherwise no exception will be thrown + /// True if the connection is still alive, otherwise false + internal bool IsConnectionAlive(bool throwOnException) + { + Debug.Assert(_parser.Connection == null || _parser.Connection.Pool != null, "Shouldn't be calling IsConnectionAlive on non-pooled connections"); + bool isAlive = true; + + if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value > CheckConnectionWindow) + { + if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed))) + { + isAlive = false; + if (throwOnException) + { + throw SQL.ConnectionDoomed(); + } + } + else if ((_pendingCallbacks > 1) || ((_parser.Connection != null) && (!_parser.Connection.IsInPool))) + { + // This connection is currently in use, assume that the connection is 'alive' + // NOTE: SNICheckConnection is not currently supported for connections that are in use + Debug.Assert(true, "Call to IsConnectionAlive while connection is in use"); + } + else + { + uint error; + + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + TdsParser.ReliabilitySection.Assert("unreliable call to IsConnectionAlive"); // you need to setup for a thread abort somewhere before you call this method + + SniContext = SniContext.Snix_Connect; + + error = CheckConnection(); + if ((error != TdsEnums.SNI_SUCCESS) && (error != TdsEnums.SNI_WAIT_TIMEOUT)) + { + // Connection is dead + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.IsConnectionAlive | Info | State Object Id {0}, received error {1} on idle connection", _objectID, (int)error); + isAlive = false; + if (throwOnException) + { + // Get the error from SNI so that we can throw the correct exception + AddError(_parser.ProcessSNIError(this)); + ThrowExceptionAndWarning(); + } + } + else + { + _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; + } + } + finally + { + } + } + } + + return isAlive; + } + /* // leave this in. comes handy if you have to do Console.WriteLine style debugging ;)