diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index bc7cd362a8..53d9848378 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -14,57 +14,17 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - private static readonly ContextCallback s_readAsyncCallbackComplete = ReadAsyncCallbackComplete; - - // Timeout variables - private readonly WeakReference _cancellationOwner = new WeakReference(null); + private static bool UseManagedSNI => TdsParserStateObjectFactory.UseManagedSNI; // Async private StateSnapshot _cachedSnapshot; private SnapshottedStateFlags _snapshottedState; - ////////////////// - // Constructors // - ////////////////// - - internal TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async) - { - // Construct a MARS session - Debug.Assert(null != parser, "no parser?"); - _parser = parser; - _onTimeoutAsync = OnTimeoutAsync; - SniContext = SniContext.Snix_GetMarsSession; - - Debug.Assert(null != _parser._physicalStateObj, "no physical session?"); - Debug.Assert(null != _parser._physicalStateObj._inBuff, "no in buffer?"); - Debug.Assert(null != _parser._physicalStateObj._outBuff, "no out buffer?"); - Debug.Assert(_parser._physicalStateObj._outBuff.Length == - _parser._physicalStateObj._inBuff.Length, "Unexpected unequal buffers."); - - // Determine packet size based on physical connection buffer lengths. - SetPacketSize(_parser._physicalStateObj._outBuff.Length); - - CreateSessionHandle(physicalConnection, async); - - if (IsFailedHandle()) - { - AddError(parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(); - } - - // we post a callback that represents the call to dispose; once the - // object is disposed, the next callback will cause the GC Handle to - // be released. - IncrementPendingCallbacks(); - _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer; - } - //////////////// // Properties // //////////////// internal abstract uint DisableSsl(); - internal abstract uint EnableMars(ref uint info); internal abstract uint Status @@ -77,124 +37,12 @@ internal abstract SessionHandle SessionHandle get; } - private partial struct NullBitmap - { - internal bool TryInitialize(TdsParserStateObject stateObj, int columnsCount) - { - _columnsCount = columnsCount; - // 1-8 columns need 1 byte - // 9-16: 2 bytes, and so on - int bitmapArrayLength = (columnsCount + 7) / 8; - - // allow reuse of previously allocated bitmap - if (_nullBitmap == null || _nullBitmap.Length != bitmapArrayLength) - { - _nullBitmap = new byte[bitmapArrayLength]; - } - - // read the null bitmap compression information from TDS - if (!stateObj.TryReadByteArray(_nullBitmap, _nullBitmap.Length)) - { - return false; - } - - SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.NullBitmap.Initialize | INFO | ADV | State Object Id {0}, NBCROW bitmap received, column count = {1}", stateObj._objectID, columnsCount); - SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParserStateObject.NullBitmap.Initialize | INFO | ADV | State Object Id {0}, Null Bitmap length {1}, NBCROW bitmap data: {2}", stateObj._objectID, (ushort)_nullBitmap.Length, _nullBitmap); - return true; - } - } + private static bool TransparentNetworkIPResolution => false; ///////////////////// // General methods // ///////////////////// - // This method is only called by the command or datareader as a result of a user initiated - // cancel request. - internal void Cancel(object caller) - { - Debug.Assert(caller != null, "Null caller for Cancel!"); - Debug.Assert(caller is SqlCommand || caller is SqlDataReader, "Calling API with invalid caller type: " + caller.GetType()); - - bool hasLock = false; - try - { - // Keep looping until we either grabbed the lock (and therefore sent attention) or the connection closes\breaks - while ((!hasLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) - { - Monitor.TryEnter(this, WaitForCancellationLockPollTimeout, ref hasLock); - if (hasLock) - { // Lock for the time being - since we need to synchronize the attention send. - // This lock is also protecting against concurrent close and async continuations - - // Ensure that, once we have the lock, that we are still the owner - if ((!_cancelled) && (_cancellationOwner.Target == caller)) - { - _cancelled = true; - - if (HasPendingData && !_attentionSent) - { - bool hasParserLock = false; - // Keep looping until we have the parser lock (and so are allowed to write), or the connection closes\breaks - while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) - { - try - { - _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: WaitForCancellationLockPollTimeout, lockTaken: ref hasParserLock); - if (hasParserLock) - { - _parser.Connection.ThreadHasParserLockForClose = true; - SendAttention(); - } - } - finally - { - if (hasParserLock) - { - if (_parser.Connection.ThreadHasParserLockForClose) - { - _parser.Connection.ThreadHasParserLockForClose = false; - } - _parser.Connection._parserLock.Release(); - } - } - } - } - } - } - } - } - finally - { - if (hasLock) - { - Monitor.Exit(this); - } - } - } - - private void ResetCancelAndProcessAttention() - { - // This method is shared by CloseSession initiated by DataReader.Close or completed - // command execution, as well as the session reclamation code for cases where the - // DataReader is opened and then GC'ed. - lock (this) - { - // Reset cancel state. - _cancelled = false; - _cancellationOwner.Target = null; - - if (_attentionSent) - { - // Make sure we're cleaning up the AttentionAck if Cancel happened before taking the lock. - // We serialize Cancel/CloseSession to prevent a race condition between these two states. - // The problem is that both sending and receiving attentions are time taking - // operations. - Parser.ProcessPendingAck(this); - } - SetTimeoutStateStopped(); - } - } - internal abstract void CreatePhysicalSNIHandle( string serverName, bool ignoreSniOpenTimeout, @@ -251,1609 +99,87 @@ internal abstract void CreatePhysicalSNIHandle( internal abstract PacketHandle AddPacketToPendingList(PacketHandle packet); - protected abstract void RemovePacketFromPendingList(PacketHandle pointer); - - internal abstract uint GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer); - - internal int DecrementPendingCallbacks(bool release) - { - int remaining = Interlocked.Decrement(ref _pendingCallbacks); - SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.DecrementPendingCallbacks | ADV | State Object Id {0}, after decrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); - FreeGcHandle(remaining, release); - // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed - // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it - Debug.Assert((remaining == -1 && SessionHandle.IsNull) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}"); - return remaining; - } - - internal int IncrementPendingCallbacks() - { - int remaining = Interlocked.Increment(ref _pendingCallbacks); - SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.IncrementPendingCallbacks | ADV | State Object Id {0}, after incrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); - Debug.Assert(0 < remaining && remaining <= 3, $"_pendingCallbacks values is invalid after incrementing: {remaining}"); - return remaining; - } - - internal void StartSession(object cancellationOwner) - { - _cancellationOwner.Target = cancellationOwner; - } - - private void SetSnapshottedState(SnapshottedStateFlags flag, bool value) - { - if (value) - { - _snapshottedState |= flag; - } - else - { - _snapshottedState &= ~flag; - } - } - - private bool GetSnapshottedState(SnapshottedStateFlags flag) - { - return (_snapshottedState & flag) == flag; - } - - internal bool HasOpenResult - { - get => GetSnapshottedState(SnapshottedStateFlags.OpenResult); - set => SetSnapshottedState(SnapshottedStateFlags.OpenResult, value); - } - - internal bool HasPendingData - { - get => GetSnapshottedState(SnapshottedStateFlags.PendingData); - set => SetSnapshottedState(SnapshottedStateFlags.PendingData, value); - } - - internal bool HasReceivedError - { - get => GetSnapshottedState(SnapshottedStateFlags.ErrorTokenReceived); - set => SetSnapshottedState(SnapshottedStateFlags.ErrorTokenReceived, value); - } - - internal bool HasReceivedAttention - { - get => GetSnapshottedState(SnapshottedStateFlags.AttentionReceived); - set => SetSnapshottedState(SnapshottedStateFlags.AttentionReceived, value); - } - - internal bool HasReceivedColumnMetadata - { - get => GetSnapshottedState(SnapshottedStateFlags.ColMetaDataReceived); - set => SetSnapshottedState(SnapshottedStateFlags.ColMetaDataReceived, value); - } - - /////////////////////////////////////// - // Buffer read methods - data values // - /////////////////////////////////////// - - // look at the next byte without pulling it off the wire, don't just return _inBytesUsed since we may - // have to go to the network to get the next byte. - internal bool TryPeekByte(out byte value) - { - if (!TryReadByte(out value)) - { - return false; - } - - // now do fixup - _inBytesPacket++; - _inBytesUsed--; - - AssertValidState(); - return true; - } - - // Takes a byte array, an offset, and a len and fills the array from the offset to len number of - // bytes from the in buffer. - public bool TryReadByteArray(Span buff, int len) - { - return TryReadByteArray(buff, len, out _); - } - - // NOTE: This method must be retriable WITHOUT replaying a snapshot - // Every time you call this method increment the offset and decrease len by the value of totalRead - public bool TryReadByteArray(Span buff, int len, out int totalRead) - { - totalRead = 0; - -#if DEBUG - if (_snapshot != null && _snapshot.DoPend()) - { - _networkPacketTaskSource = new TaskCompletionSource(); - Interlocked.MemoryBarrier(); - - if (s_forcePendingReadsToWaitForUser) - { - _realNetworkPacketTaskSource = new TaskCompletionSource(); - _realNetworkPacketTaskSource.SetResult(null); - } - else - { - _networkPacketTaskSource.TrySetResult(null); - } - return false; - } -#endif - - Debug.Assert(buff == null || buff.Length >= len, "Invalid length sent to ReadByteArray()!"); - - // loop through and read up to array length - while (len > 0) - { - if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) - { - if (!TryPrepareBuffer()) - { - return false; - } - } - - int bytesToRead = Math.Min(len, Math.Min(_inBytesPacket, _inBytesRead - _inBytesUsed)); - Debug.Assert(bytesToRead > 0, "0 byte read in TryReadByteArray"); - if (!buff.IsEmpty) - { - ReadOnlySpan copyFrom = new ReadOnlySpan(_inBuff, _inBytesUsed, bytesToRead); - Span copyTo = buff.Slice(totalRead, bytesToRead); - copyFrom.CopyTo(copyTo); - } - - totalRead += bytesToRead; - _inBytesUsed += bytesToRead; - _inBytesPacket -= bytesToRead; - len -= bytesToRead; - - AssertValidState(); - } - - return true; - } - - // Takes no arguments and returns a byte from the buffer. If the buffer is empty, it is filled - // before the byte is returned. - internal bool TryReadByte(out byte value) - { - Debug.Assert(_inBytesUsed >= 0 && _inBytesUsed <= _inBytesRead, "ERROR - TDSParser: _inBytesUsed < 0 or _inBytesUsed > _inBytesRead"); - value = 0; - -#if DEBUG - if (_snapshot != null && _snapshot.DoPend()) - { - _networkPacketTaskSource = new TaskCompletionSource(); - Interlocked.MemoryBarrier(); - - if (s_forcePendingReadsToWaitForUser) - { - _realNetworkPacketTaskSource = new TaskCompletionSource(); - _realNetworkPacketTaskSource.SetResult(null); - } - else - { - _networkPacketTaskSource.TrySetResult(null); - } - return false; - } -#endif - - if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) - { - if (!TryPrepareBuffer()) - { - return false; - } - } - - // decrement the number of bytes left in the packet - _inBytesPacket--; - - Debug.Assert(_inBytesPacket >= 0, "ERROR - TDSParser: _inBytesPacket < 0"); - - // return the byte from the buffer and increment the counter for number of bytes used in the in buffer - value = (_inBuff[_inBytesUsed++]); - - AssertValidState(); - return true; - } - - internal bool TryReadChar(out char value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - - Span buffer = stackalloc byte[2]; - if (((_inBytesUsed + 2) > _inBytesRead) || (_inBytesPacket < 2)) - { - // If the char isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - if (!TryReadByteArray(buffer, 2)) - { - value = '\0'; - return false; - } - } - else - { - // The entire char is in the packet and in the buffer, so just return it - // and take care of the counters. - buffer = _inBuff.AsSpan(_inBytesUsed, 2); - _inBytesUsed += 2; - _inBytesPacket -= 2; - } - - AssertValidState(); - value = (char)((buffer[1] << 8) + buffer[0]); - - return true; - } - - internal bool TryReadInt16(out short value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - - Span buffer = stackalloc byte[2]; - if (((_inBytesUsed + 2) > _inBytesRead) || (_inBytesPacket < 2)) - { - // If the int16 isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - if (!TryReadByteArray(buffer, 2)) - { - value = default; - return false; - } - } - else - { - // The entire int16 is in the packet and in the buffer, so just return it - // and take care of the counters. - buffer = _inBuff.AsSpan(_inBytesUsed, 2); - _inBytesUsed += 2; - _inBytesPacket -= 2; - } - - AssertValidState(); - value = (short)((buffer[1] << 8) + buffer[0]); - return true; - } - - internal bool TryReadInt32(out int value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - Span buffer = stackalloc byte[4]; - if (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4)) - { - // If the int isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - if (!TryReadByteArray(buffer, 4)) - { - value = 0; - return false; - } - } - else - { - // The entire int is in the packet and in the buffer, so just return it - // and take care of the counters. - buffer = _inBuff.AsSpan(_inBytesUsed, 4); - _inBytesUsed += 4; - _inBytesPacket -= 4; - } - - AssertValidState(); - value = (buffer[3] << 24) + (buffer[2] << 16) + (buffer[1] << 8) + buffer[0]; - return true; - - } - - // This method is safe to call when doing async without snapshot - internal bool TryReadInt64(out long value) - { - if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) - { - if (!TryPrepareBuffer()) - { - value = 0; - return false; - } - } - - if ((_bTmpRead > 0) || (((_inBytesUsed + 8) > _inBytesRead) || (_inBytesPacket < 8))) - { - // If the long isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - int bytesRead; - if (!TryReadByteArray(_bTmp.AsSpan(_bTmpRead), 8 - _bTmpRead, out bytesRead)) - { - Debug.Assert(_bTmpRead + bytesRead <= 8, "Read more data than required"); - _bTmpRead += bytesRead; - value = 0; - return false; - } - else - { - Debug.Assert(_bTmpRead + bytesRead == 8, "TryReadByteArray returned true without reading all data required"); - _bTmpRead = 0; - AssertValidState(); - value = BitConverter.ToInt64(_bTmp, 0); - return true; - } - } - else - { - // The entire long is in the packet and in the buffer, so just return it - // and take care of the counters. - - value = BitConverter.ToInt64(_inBuff, _inBytesUsed); - - _inBytesUsed += 8; - _inBytesPacket -= 8; - - AssertValidState(); - return true; - } - } - - internal bool TryReadUInt16(out ushort value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - - Span buffer = stackalloc byte[2]; - if (((_inBytesUsed + 2) > _inBytesRead) || (_inBytesPacket < 2)) - { - // If the uint16 isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - if (!TryReadByteArray(buffer, 2)) - { - value = default; - return false; - } - } - else - { - // The entire uint16 is in the packet and in the buffer, so just return it - // and take care of the counters. - buffer = _inBuff.AsSpan(_inBytesUsed, 2); - _inBytesUsed += 2; - _inBytesPacket -= 2; - } - - AssertValidState(); - value = (ushort)((buffer[1] << 8) + buffer[0]); - return true; - } - - // This method is safe to call when doing async without replay - internal bool TryReadUInt32(out uint value) - { - if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) - { - if (!TryPrepareBuffer()) - { - value = 0; - return false; - } - } - - if ((_bTmpRead > 0) || (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4))) - { - // If the int isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - int bytesRead; - if (!TryReadByteArray(_bTmp.AsSpan(_bTmpRead), 4 - _bTmpRead, out bytesRead)) - { - Debug.Assert(_bTmpRead + bytesRead <= 4, "Read more data than required"); - _bTmpRead += bytesRead; - value = 0; - return false; - } - else - { - Debug.Assert(_bTmpRead + bytesRead == 4, "TryReadByteArray returned true without reading all data required"); - _bTmpRead = 0; - AssertValidState(); - value = BitConverter.ToUInt32(_bTmp, 0); - return true; - } - } - else - { - // The entire int is in the packet and in the buffer, so just return it - // and take care of the counters. - - value = BitConverter.ToUInt32(_inBuff, _inBytesUsed); - - _inBytesUsed += 4; - _inBytesPacket -= 4; - - AssertValidState(); - return true; - } - } - - internal bool TryReadSingle(out float value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - if (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4)) - { - // If the float isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - if (!TryReadByteArray(_bTmp, 4)) - { - value = default; - return false; - } - - AssertValidState(); - value = BitConverter.ToSingle(_bTmp, 0); - return true; - } - else - { - // The entire float is in the packet and in the buffer, so just return it - // and take care of the counters. - - value = BitConverter.ToSingle(_inBuff, _inBytesUsed); - - _inBytesUsed += 4; - _inBytesPacket -= 4; - - AssertValidState(); - return true; - } - } - - internal bool TryReadDouble(out double value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - if (((_inBytesUsed + 8) > _inBytesRead) || (_inBytesPacket < 8)) - { - // If the double isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - if (!TryReadByteArray(_bTmp, 8)) - { - value = default; - return false; - } - - AssertValidState(); - value = BitConverter.ToDouble(_bTmp, 0); - return true; - } - else - { - // The entire double is in the packet and in the buffer, so just return it - // and take care of the counters. - - value = BitConverter.ToDouble(_inBuff, _inBytesUsed); - - _inBytesUsed += 8; - _inBytesPacket -= 8; - - AssertValidState(); - return true; - } - } - - internal bool TryReadString(int length, out string value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - int cBytes = length << 1; - byte[] buf; - int offset = 0; - - if (((_inBytesUsed + cBytes) > _inBytesRead) || (_inBytesPacket < cBytes)) - { - if (_bTmp == null || _bTmp.Length < cBytes) - { - _bTmp = new byte[cBytes]; - } - - if (!TryReadByteArray(_bTmp, cBytes)) - { - value = null; - return false; - } - - // assign local to point to parser scratch buffer - buf = _bTmp; - - AssertValidState(); - } - else - { - // assign local to point to _inBuff - buf = _inBuff; - offset = _inBytesUsed; - _inBytesUsed += cBytes; - _inBytesPacket -= cBytes; - - AssertValidState(); - } - - value = System.Text.Encoding.Unicode.GetString(buf, offset, cBytes); - return true; - } - - internal bool TryReadStringWithEncoding(int length, System.Text.Encoding encoding, bool isPlp, out string value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - - if (null == encoding) - { - // Need to skip the current column before throwing the error - this ensures that the state shared between this and the data reader is consistent when calling DrainData - if (isPlp) - { - if (!_parser.TrySkipPlpValue((ulong)length, this, out _)) - { - value = null; - return false; - } - } - else - { - if (!TrySkipBytes(length)) - { - value = null; - return false; - } - } - - _parser.ThrowUnsupportedCollationEncountered(this); - } - byte[] buf = null; - int offset = 0; - - if (isPlp) - { - if (!TryReadPlpBytes(ref buf, 0, int.MaxValue, out length)) - { - value = null; - return false; - } - - AssertValidState(); - } - else - { - if (((_inBytesUsed + length) > _inBytesRead) || (_inBytesPacket < length)) - { - if (_bTmp == null || _bTmp.Length < length) - { - _bTmp = new byte[length]; - } - - if (!TryReadByteArray(_bTmp, length)) - { - value = null; - return false; - } - - // assign local to point to parser scratch buffer - buf = _bTmp; - - AssertValidState(); - } - else - { - // assign local to point to _inBuff - buf = _inBuff; - offset = _inBytesUsed; - _inBytesUsed += length; - _inBytesPacket -= length; - - AssertValidState(); - } - } - - // BCL optimizes to not use char[] underneath - value = encoding.GetString(buf, offset, length); - return true; - } - - internal ulong ReadPlpLength(bool returnPlpNullIfNull) - { - ulong value; - Debug.Assert(_syncOverAsync, "Should not attempt pends in a synchronous call"); - bool result = TryReadPlpLength(returnPlpNullIfNull, out value); - if (!result) - { - throw SQL.SynchronousCallMayNotPend(); - } - return value; - } - - // Reads the length of either the entire data or the length of the next chunk in a - // partially length prefixed data - // After this call, call ReadPlpBytes/ReadPlpUnicodeChars until the specified length of data - // is consumed. Repeat this until ReadPlpLength returns 0 in order to read the - // entire stream. - // When this function returns 0, it means the data stream is read completely and the - // plp state in the tdsparser is cleaned. - internal bool TryReadPlpLength(bool returnPlpNullIfNull, out ulong lengthLeft) - { - uint chunklen; - // bool firstchunk = false; - bool isNull = false; - - Debug.Assert(_longlenleft == 0, "Out of synch length read request"); - if (_longlen == 0) - { - // First chunk is being read. Find out what type of chunk it is - long value; - if (!TryReadInt64(out value)) - { - lengthLeft = 0; - return false; - } - _longlen = (ulong)value; - // firstchunk = true; - } - - if (_longlen == TdsEnums.SQL_PLP_NULL) - { - _longlen = 0; - _longlenleft = 0; - isNull = true; - } - else - { - // Data is coming in uint chunks, read length of next chunk - if (!TryReadUInt32(out chunklen)) - { - lengthLeft = 0; - return false; - } - if (chunklen == TdsEnums.SQL_PLP_CHUNK_TERMINATOR) - { - _longlenleft = 0; - _longlen = 0; - } - else - { - _longlenleft = chunklen; - } - } - - AssertValidState(); - - if (isNull && returnPlpNullIfNull) - { - lengthLeft = TdsEnums.SQL_PLP_NULL; - return true; - } - - lengthLeft = _longlenleft; - return true; - } - - internal int ReadPlpBytesChunk(byte[] buff, int offset, int len) - { - Debug.Assert(_syncOverAsync, "Should not attempt pends in a synchronous call"); - Debug.Assert(_longlenleft > 0, "Read when no data available"); - - int value; - int bytesToRead = (int)Math.Min(_longlenleft, (ulong)len); - bool result = TryReadByteArray(buff.AsSpan(offset), bytesToRead, out value); - _longlenleft -= (ulong)bytesToRead; - if (!result) - { - throw SQL.SynchronousCallMayNotPend(); - } - return value; - } - - // Reads the requested number of bytes from a plp data stream, or the entire data if - // requested length is -1 or larger than the actual length of data. First call to this method - // should be preceeded by a call to ReadPlpLength or ReadDataLength. - // Returns the actual bytes read. - // NOTE: This method must be retriable WITHOUT replaying a snapshot - // Every time you call this method increment the offset and decrease len by the value of totalBytesRead - internal bool TryReadPlpBytes(ref byte[] buff, int offset, int len, out int totalBytesRead) - { - int bytesRead; - int bytesLeft; - byte[] newbuf; - - if (_longlen == 0) - { - Debug.Assert(_longlenleft == 0); - if (buff == null) - { - buff = Array.Empty(); - } - - AssertValidState(); - totalBytesRead = 0; - return true; // No data - } - - Debug.Assert(_longlen != TdsEnums.SQL_PLP_NULL, "Out of sync plp read request"); - Debug.Assert((buff == null && offset == 0) || (buff.Length >= offset + len), "Invalid length sent to ReadPlpBytes()!"); - - bytesLeft = len; - - // If total length is known up front, allocate the whole buffer in one shot instead of realloc'ing and copying over each time - if (buff == null && _longlen != TdsEnums.SQL_PLP_UNKNOWNLEN) - { - if (_snapshot != null) - { - // if there is a snapshot and it contains a stored plp buffer take it - // and try to use it if it is the right length - buff = _snapshot._plpBuffer; - _snapshot._plpBuffer = null; - } - - if ((ulong)(buff?.Length ?? 0) != _longlen) - { - // if the buffer is null or the wrong length create one to use - buff = new byte[(Math.Min((int)_longlen, len))]; - } - } - - if (_longlenleft == 0) - { - if (!TryReadPlpLength(false, out _)) - { - totalBytesRead = 0; - return false; - } - if (_longlenleft == 0) - { // Data read complete - totalBytesRead = 0; - return true; - } - } - - if (buff == null) - { - buff = new byte[_longlenleft]; - } - - totalBytesRead = 0; - - while (bytesLeft > 0) - { - int bytesToRead = (int)Math.Min(_longlenleft, (ulong)bytesLeft); - if (buff.Length < (offset + bytesToRead)) - { - // Grow the array - newbuf = new byte[offset + bytesToRead]; - Buffer.BlockCopy(buff, 0, newbuf, 0, offset); - buff = newbuf; - } - - bool result = TryReadByteArray(buff.AsSpan(offset), bytesToRead, out bytesRead); - Debug.Assert(bytesRead <= bytesLeft, "Read more bytes than we needed"); - Debug.Assert((ulong)bytesRead <= _longlenleft, "Read more bytes than is available"); - - bytesLeft -= bytesRead; - offset += bytesRead; - totalBytesRead += bytesRead; - _longlenleft -= (ulong)bytesRead; - if (!result) - { - if (_snapshot != null) - { - // a partial read has happened so store the target buffer in the snapshot - // so it can be re-used when another packet arrives and we read again - _snapshot._plpBuffer = buff; - } - return false; - } - - if (_longlenleft == 0) - { - // Read the next chunk or cleanup state if hit the end - if (!TryReadPlpLength(false, out _)) - { - if (_snapshot != null) - { - // a partial read has happened so store the target buffer in the snapshot - // so it can be re-used when another packet arrives and we read again - _snapshot._plpBuffer = buff; - } - return false; - } - } - - AssertValidState(); - - // Catch the point where we read the entire plp data stream and clean up state - if (_longlenleft == 0) // Data read complete - break; - } - return true; - } - - - ///////////////////////////////////////// - // Value Skip Logic // - ///////////////////////////////////////// - - - // Reads bytes from the buffer but doesn't return them, in effect simply deleting them. - // Does not handle plp fields, need to use SkipPlpBytesValue for those. - // Does not handle null values or NBC bitmask, ensure the value is not null before calling this method - internal bool TrySkipLongBytes(long num) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - - while (num > 0) - { - int cbSkip = (int)Math.Min(int.MaxValue, num); - if (!TryReadByteArray(Span.Empty, cbSkip)) - { - return false; - } - num -= cbSkip; - } - - return true; - } - - // Reads bytes from the buffer but doesn't return them, in effect simply deleting them. - internal bool TrySkipBytes(int num) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - return TryReadByteArray(Span.Empty, num); - } - - ///////////////////////////////////////// - // Network/Packet Reading & Processing // - ///////////////////////////////////////// - - internal void SetSnapshot() - { - StateSnapshot snapshot = _snapshot; - if (snapshot is null) - { - snapshot = Interlocked.Exchange(ref _cachedSnapshot, null) ?? new StateSnapshot(); - } - else - { - snapshot.Clear(); - } - _snapshot = snapshot; - _snapshot.Snap(this); - _snapshotReplay = false; - } - - internal void ResetSnapshot() - { - if (_snapshot != null) - { - StateSnapshot snapshot = _snapshot; - _snapshot = null; - snapshot.Clear(); - Interlocked.CompareExchange(ref _cachedSnapshot, snapshot, null); - } - _snapshotReplay = false; - } - -#if DEBUG - private string _lastStack; -#endif - - internal bool TryReadNetworkPacket() - { -#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 (_snapshot.Replay()) - { -#if DEBUG - if (s_checkNetworkPacketRetryStacks) - { - _snapshot.CheckStack(Environment.StackTrace); - } -#endif - return true; - } -#if DEBUG - else - { - if (s_checkNetworkPacketRetryStacks) - { - _lastStack = Environment.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 _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.PrepareReplay(); - } - - internal void ReadSniSyncOverAsync() - { - if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) - { - throw ADP.ClosedConnectionError(); - } - - PacketHandle readPacket = default; - - uint error; - - bool shouldDecrement = false; - try - { - 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(); - 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; - - 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); - } - - Interlocked.Increment(ref _readingCount); - - SessionHandle 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; - 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; - } - } - } - - 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 - /// NOTE: This will mark the connection as broken if it is found to be dead - /// - /// True if the connection is still alive, otherwise false - internal bool ValidateSNIConnection() - { - if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed))) - { - return false; - } - - if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value <= CheckConnectionWindow) - { - return true; - } - - uint error = TdsEnums.SNI_SUCCESS; - SniContext = SniContext.Snix_Connect; - try - { - Interlocked.Increment(ref _readingCount); - error = CheckConnection(); - } - finally - { - Interlocked.Decrement(ref _readingCount); - } - return (error == TdsEnums.SNI_SUCCESS) || (error == TdsEnums.SNI_WAIT_TIMEOUT); - } - - // This method should only be called by ReadSni! If not - it may have problems with timeouts! - private void ReadSniError(TdsParserStateObject stateObj, uint error) - { - if (TdsEnums.SNI_WAIT_TIMEOUT == error) - { - Debug.Assert(_syncOverAsync, "Should never reach here with async on!"); - bool fail = false; - - if (IsTimeoutStateExpired) - { // This is now our second timeout - time to give up. - fail = true; - } - else - { - stateObj.SetTimeoutStateStopped(); - Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point."); - AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); - - if (!stateObj._attentionSent) - { - if (stateObj.Parser.State == TdsParserState.OpenLoggedIn) - { - stateObj.SendAttention(mustTakeWriteLock: true); - - PacketHandle syncReadPacket = default; - - bool shouldDecrement = false; - try - { - Interlocked.Increment(ref _readingCount); - shouldDecrement = true; - - syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error); - - Interlocked.Decrement(ref _readingCount); - shouldDecrement = false; - - if (TdsEnums.SNI_SUCCESS == error) - { - // We will end up letting the run method deal with the expected done:done_attn token stream. - stateObj.ProcessSniPacket(syncReadPacket, 0); - return; - } - else - { - Debug.Assert(!IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease"); - fail = true; // Subsequent read failed, time to give up. - } - } - finally - { - if (shouldDecrement) - { - Interlocked.Decrement(ref _readingCount); - } - - if (!IsPacketEmpty(syncReadPacket)) - { - ReleasePacket(syncReadPacket); - } - } - } - else - { - if (_parser._loginWithFailover) - { - // For DbMirroring Failover during login, never break the connection, just close the TdsParser - _parser.Disconnect(); - } - else if ((_parser.State == TdsParserState.OpenNotLoggedIn) && (_parser.Connection.ConnectionOptions.MultiSubnetFailover)) - { - // For MultiSubnet Failover during login, never break the connection, just close the TdsParser - _parser.Disconnect(); - } - else - fail = true; // We aren't yet logged in - just fail. - } - } - } + protected abstract void RemovePacketFromPendingList(PacketHandle pointer); - if (fail) - { - _parser.State = TdsParserState.Broken; // We failed subsequent read, we have to quit! - _parser.Connection.BreakConnection(); - } + internal abstract uint GenerateSspiClientContext(byte[] receivedBuff, uint receivedLength, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer); + + private void SetSnapshottedState(SnapshottedStateFlags flag, bool value) + { + if (value) + { + _snapshottedState |= flag; } else { - // Caution: ProcessSNIError always returns a fatal error! - AddError(_parser.ProcessSNIError(stateObj)); + _snapshottedState &= ~flag; } - ThrowExceptionAndWarning(); - - AssertValidState(); } - public void ProcessSniPacket(PacketHandle packet, uint error) + private bool GetSnapshottedState(SnapshottedStateFlags flag) { - if (error != 0) - { - if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) - { - // Do nothing with callback if closed or broken and error not 0 - callback can occur - // after connection has been closed. PROBLEM IN NETLIB - DESIGN FLAW. - return; - } - - AddError(_parser.ProcessSNIError(this)); - AssertValidState(); - } - else - { - uint dataSize = 0; + return (_snapshottedState & flag) == flag; + } - uint getDataError = SNIPacketGetData(packet, _inBuff, ref dataSize); + internal bool HasOpenResult + { + get => GetSnapshottedState(SnapshottedStateFlags.OpenResult); + set => SetSnapshottedState(SnapshottedStateFlags.OpenResult, value); + } - if (getDataError == TdsEnums.SNI_SUCCESS) - { - if (_inBuff.Length < dataSize) - { - Debug.Assert(true, "Unexpected dataSize on Read"); - throw SQL.InvalidInternalPacketSize(StringsHelper.GetString(Strings.SqlMisc_InvalidArraySizeMessage)); - } + internal bool HasPendingData + { + get => GetSnapshottedState(SnapshottedStateFlags.PendingData); + set => SetSnapshottedState(SnapshottedStateFlags.PendingData, value); + } - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; - _inBytesRead = (int)dataSize; - _inBytesUsed = 0; + internal bool HasReceivedError + { + get => GetSnapshottedState(SnapshottedStateFlags.ErrorTokenReceived); + set => SetSnapshottedState(SnapshottedStateFlags.ErrorTokenReceived, value); + } - if (_snapshot != null) - { - _snapshot.PushBuffer(_inBuff, _inBytesRead); - if (_snapshotReplay) - { - _snapshot.Replay(); -#if DEBUG - _snapshot.AssertCurrent(); -#endif - } - } + internal bool HasReceivedAttention + { + get => GetSnapshottedState(SnapshottedStateFlags.AttentionReceived); + set => SetSnapshottedState(SnapshottedStateFlags.AttentionReceived, value); + } - SniReadStatisticsAndTracing(); + internal bool HasReceivedColumnMetadata + { + get => GetSnapshottedState(SnapshottedStateFlags.ColMetaDataReceived); + set => SetSnapshottedState(SnapshottedStateFlags.ColMetaDataReceived, value); + } + ///////////////////////////////////////// + // Network/Packet Reading & Processing // + ///////////////////////////////////////// - AssertValidState(); - } - else - { - throw SQL.ParsingError(); - } + internal void SetSnapshot() + { + StateSnapshot snapshot = _snapshot; + if (snapshot is null) + { + snapshot = Interlocked.Exchange(ref _cachedSnapshot, null) ?? new StateSnapshot(); + } + else + { + snapshot.Clear(); } + _snapshot = snapshot; + _snapshot.Snap(this); + _snapshotReplay = false; } - private void ChangeNetworkPacketTimeout(int dueTime, int period) + internal void ResetSnapshot() { - Timer networkPacketTimeout = _networkPacketTimeout; - if (networkPacketTimeout != null) + if (_snapshot != null) { - try - { - networkPacketTimeout.Change(dueTime, period); - } - catch (ObjectDisposedException) - { - // _networkPacketTimeout is set to null before Disposing, but there is still a slight chance - // that object was disposed after we took a copy - } + StateSnapshot snapshot = _snapshot; + _snapshot = null; + snapshot.Clear(); + Interlocked.CompareExchange(ref _cachedSnapshot, snapshot, null); } + _snapshotReplay = false; } private void SetBufferSecureStrings() @@ -1885,332 +211,14 @@ private void SetBufferSecureStrings() public void ReadAsyncCallback(PacketHandle packet, uint error) => ReadAsyncCallback(IntPtr.Zero, packet, error); - public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) - { - // Key never used. - // Note - it's possible that when native calls managed that an asynchronous exception - // could occur in the native->managed transition, which would - // have two impacts: - // 1) user event not called - // 2) DecrementPendingCallbacks not called, which would mean this object would be leaked due - // to the outstanding GCRoot until AppDomain.Unload. - // We live with the above for the time being due to the constraints of the current - // reliability infrastructure provided by the CLR. - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.ReadAsyncCallback | Info | State Object Id {0}, received error {1} on idle connection", _objectID, (int)error); - - TaskCompletionSource source = _networkPacketTaskSource; -#if DEBUG - if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null)) - { - source = _realNetworkPacketTaskSource; - } -#endif - - // The mars physical connection can get a callback - // with a packet but no result after the connection is closed. - if (source == null && _parser._pMarsPhysicalConObj == this) - { - return; - } - - bool processFinallyBlock = true; - try - { - Debug.Assert(CheckPacket(packet, source) && source != null, "AsyncResult null on callback"); - - if (_parser.MARSOn) - { - // Only take reset lock on MARS and Async. - CheckSetResetConnectionState(error, CallbackType.Read); - } - - ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); - - // The timer thread may be unreliable under high contention scenarios. It cannot be - // assumed that the timeout has happened on the timer thread callback. Check the timeout - // synchrnously and then call OnTimeoutSync to force an atomic change of state. - if (TimeoutHasExpired) - { - OnTimeoutSync(true); - } - - // try to change to the stopped state but only do so if currently in the running state - // and use cmpexch so that all changes out of the running state are atomic - int previousState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Stopped, TimeoutState.Running); - - // if the state is anything other than running then this query has reached an end so - // set the correlation _timeoutIdentityValue to 0 to prevent late callbacks executing - if (_timeoutState != TimeoutState.Running) - { - _timeoutIdentityValue = 0; - } - - ProcessSniPacket(packet, error); - } - catch (Exception e) - { - processFinallyBlock = ADP.IsCatchableExceptionType(e); - throw; - } - finally - { - // pendingCallbacks may be 2 after decrementing, this indicates that a fatal timeout is occurring, and therefore we shouldn't complete the task - int pendingCallbacks = DecrementPendingCallbacks(false); // may dispose of GC handle. - if ((processFinallyBlock) && (source != null) && (pendingCallbacks < 2)) - { - if (error == 0) - { - if (_executionContext != null) - { - ExecutionContext.Run(_executionContext, s_readAsyncCallbackComplete, source); - } - else - { - source.TrySetResult(null); - } - } - else - { - if (_executionContext != null) - { - ExecutionContext.Run(_executionContext, state => ReadAsyncCallbackCaptureException((TaskCompletionSource)state), source); - } - else - { - ReadAsyncCallbackCaptureException(source); - } - } - } - - AssertValidState(); - } - } - - private static void ReadAsyncCallbackComplete(object state) - { - TaskCompletionSource source = (TaskCompletionSource)state; - source.TrySetResult(null); - } - protected abstract bool CheckPacket(PacketHandle packet, TaskCompletionSource source); - private void ReadAsyncCallbackCaptureException(TaskCompletionSource source) - { - bool captureSuccess = false; - try - { - if (_hasErrorOrWarning) - { - // Do the close on another thread, since we don't want to block the callback thread - ThrowExceptionAndWarning(asyncClose: true); - } - else if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) - { - // Connection was closed by another thread before we parsed the packet, so no error was added to the collection - throw ADP.ClosedConnectionError(); - } - } - catch (Exception ex) - { - if (source.TrySetException(ex)) - { - // There was an exception, and it was successfully stored in the task - captureSuccess = true; - } - } - - if (!captureSuccess) - { - // Either there was no exception, or the task was already completed - // This is unusual, but possible if a fatal timeout occurred on another thread (which should mean that the connection is now broken) - Debug.Assert(_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed || _parser.Connection.IsConnectionDoomed, "Failed to capture exception while the connection was still healthy"); - - // The safest thing to do is to ensure that the connection is broken and attempt to cancel the task - // This must be done from another thread to not block the callback thread - Task.Factory.StartNew(() => - { - _parser.State = TdsParserState.Broken; - _parser.Connection.BreakConnection(); - source.TrySetCanceled(); - }); - } - } - public void WriteAsyncCallback(PacketHandle packet, uint sniError) => WriteAsyncCallback(IntPtr.Zero, packet, sniError); - public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) - { // Key never used. - RemovePacketFromPendingList(packet); - try - { - if (sniError != TdsEnums.SNI_SUCCESS) - { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.WriteAsyncCallback | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError); - try - { - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(asyncClose: true); - } - catch (Exception e) - { - TaskCompletionSource writeCompletionSource = _writeCompletionSource; - if (writeCompletionSource != null) - { - writeCompletionSource.TrySetException(e); - } - else - { - _delayedWriteAsyncCallbackException = e; - - // Ensure that _delayedWriteAsyncCallbackException is set before checking _writeCompletionSource - Interlocked.MemoryBarrier(); - - // Double check that _writeCompletionSource hasn't been created in the meantime - writeCompletionSource = _writeCompletionSource; - if (writeCompletionSource != null) - { - Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - writeCompletionSource.TrySetException(delayedException); - } - } - } - - return; - } - } - else - { - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; - } - } - finally - { -#if DEBUG - if (SqlCommand.DebugForceAsyncWriteDelay > 0) - { - new Timer(obj => - { - Interlocked.Decrement(ref _asyncWriteCount); - TaskCompletionSource writeCompletionSource = _writeCompletionSource; - if (_asyncWriteCount == 0 && writeCompletionSource != null) - { - writeCompletionSource.TrySetResult(null); - } - }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite); - } - else - { -#else - { -#endif - Interlocked.Decrement(ref _asyncWriteCount); - } - } -#if DEBUG - if (SqlCommand.DebugForceAsyncWriteDelay > 0) - { - return; - } -#endif - TaskCompletionSource completionSource = _writeCompletionSource; - if (_asyncWriteCount == 0 && completionSource != null) - { - completionSource.TrySetResult(null); - } - } - ///////////////////////////////////////// // Network/Packet Writing & Processing // ///////////////////////////////////////// - internal void WriteSecureString(SecureString secureString) - { - Debug.Assert(_securePasswords[0] == null || _securePasswords[1] == null, "There are more than two secure passwords"); - - int index = _securePasswords[0] != null ? 1 : 0; - - _securePasswords[index] = secureString; - _securePasswordOffsetsInBuffer[index] = _outBytesUsed; - - // loop through and write the entire array - int lengthInBytes = secureString.Length * 2; - - // It is guaranteed both secure password and secure change password should fit into the first packet - // Given current TDS format and implementation it is not possible that one of secure string is the last item and exactly fill up the output buffer - // if this ever happens and it is correct situation, the packet needs to be written out after _outBytesUsed is update - Debug.Assert((_outBytesUsed + lengthInBytes) < _outBuff.Length, "Passwords cannot be split into two different packet or the last item which fully fill up _outBuff!!!"); - - _outBytesUsed += lengthInBytes; - } - - internal void ResetSecurePasswordsInformation() - { - for (int i = 0; i < _securePasswords.Length; ++i) - { - _securePasswords[i] = null; - _securePasswordOffsetsInBuffer[i] = 0; - } - } - - internal Task WaitForAccumulatedWrites() - { - // Checked for stored exceptions - Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } -#pragma warning restore 420 - - if (_asyncWriteCount == 0) - { - return null; - } - - _writeCompletionSource = new TaskCompletionSource(); - Task task = _writeCompletionSource.Task; - - // Ensure that _writeCompletionSource is set before checking state - Interlocked.MemoryBarrier(); - - // Now that we have set _writeCompletionSource, check if parser is closed or broken - if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) - { - throw ADP.ClosedConnectionError(); - } - - // Check for stored exceptions - delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } - - // If there are no outstanding writes, see if we can shortcut and return null - if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null))) - { - task = null; - } - - return task; - } - - // Takes in a single byte and writes it to the buffer. If the buffer is full, it is flushed - // and then the buffer is re-initialized in flush() and then the byte is put in the buffer. - internal void WriteByte(byte b) - { - Debug.Assert(_outBytesUsed <= _outBuff.Length, "ERROR - TDSParser: _outBytesUsed > _outBuff.Length"); - - // check to make sure we haven't used the full amount of space available in the buffer, if so, flush it - if (_outBytesUsed == _outBuff.Length) - { - WritePacket(TdsEnums.SOFTFLUSH, canAccumulate: true); - } - // set byte in buffer and increment the counter for number of bytes used in the out buffer - _outBuff[_outBytesUsed++] = b; - } internal Task WriteByteSpan(ReadOnlySpan span, bool canAccumulate = true, TaskCompletionSource completion = null) { @@ -2625,9 +633,8 @@ internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = fa return; } - uint sniError; _parser._asyncWrite = false; // stop async write - SNIWritePacket(attnPacket, out sniError, canAccumulate: false, callerHasConnectionLock: false, asyncClose); + SNIWritePacket(attnPacket, out _, canAccumulate: false, callerHasConnectionLock: false, asyncClose); SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Sent Attention.", _objectID); } finally @@ -2764,7 +771,6 @@ private void AssertValidState() Debug.Assert(_inBytesPacket >= 0, "Packet must not be negative"); } - ////////////////////////////////////////////// // Errors and Warnings // ////////////////////////////////////////////// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 81ccfb570a..51ebef2948 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -3088,7 +3088,7 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio { _stateObj = _parser.GetSession(this); _stateObj._bulkCopyOpperationInProgress = true; - _stateObj.StartSession(ObjectID); + _stateObj.StartSession(this); } finally { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index fdfa0772ea..6297090df5 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -1283,14 +1283,14 @@ public override void Cancel() TdsParserStateObject stateObj = _stateObj; if (null != stateObj) { - stateObj.Cancel(ObjectID); + stateObj.Cancel(this); } else { SqlDataReader reader = connection.FindLiveReader(this); if (reader != null) { - reader.Cancel(ObjectID); + reader.Cancel(this); } } } @@ -5986,7 +5986,7 @@ private void GetStateObject(TdsParser parser = null) } TdsParserStateObject stateObj = parser.GetSession(this); - stateObj.StartSession(ObjectID); + stateObj.StartSession(this); _stateObj = stateObj; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs index fd7306a8f7..bd3f711d8b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -797,12 +797,13 @@ internal DataTable BuildSchemaTable() return schemaTable; } - internal void Cancel(int objectID) + internal void Cancel(SqlCommand command) { + Debug.Assert(command == _command, "Calling command from an object that isn't this reader's command"); TdsParserStateObject stateObj = _stateObj; if (null != stateObj) { - stateObj.Cancel(objectID); + stateObj.Cancel(command); } } @@ -860,7 +861,7 @@ private bool TryCleanPartialRead() } #if DEBUG - if (_stateObj._pendingData) + if (_stateObj.HasPendingData) { byte token; if (!_stateObj.TryPeekByte(out token)) @@ -1067,7 +1068,7 @@ private bool TryCloseInternal(bool closeReader) #else { #endif //DEBUG - if ((!_isClosed) && (parser != null) && (stateObj != null) && (stateObj._pendingData)) + if ((!_isClosed) && (parser != null) && (stateObj != null) && (stateObj.HasPendingData)) { // It is possible for this to be called during connection close on a @@ -1333,7 +1334,7 @@ private bool TryConsumeMetaData() { // warning: Don't check the MetaData property within this function // warning: as it will be a reentrant call - while (_parser != null && _stateObj != null && _stateObj._pendingData && !_metaDataConsumed) + while (_parser != null && _stateObj != null && _stateObj.HasPendingData && !_metaDataConsumed) { if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) { @@ -3473,7 +3474,7 @@ private bool TryHasMoreResults(out bool moreResults) Debug.Assert(null != _command, "unexpected null command from the data reader!"); - while (_stateObj._pendingData) + while (_stateObj.HasPendingData) { byte token; if (!_stateObj.TryPeekByte(out token)) @@ -3563,7 +3564,7 @@ private bool TryHasMoreRows(out bool moreRows) moreRows = false; return true; } - if (_stateObj._pendingData) + if (_stateObj.HasPendingData) { // Consume error's, info's, done's on HasMoreRows, so user obtains error on Read. // Previous bug where Read() would return false with error on the wire in the case @@ -3620,7 +3621,7 @@ private bool TryHasMoreRows(out bool moreRows) moreRows = false; return false; } - if (_stateObj._pendingData) + if (_stateObj.HasPendingData) { if (!_stateObj.TryPeekByte(out b)) { @@ -3964,7 +3965,7 @@ private bool TryReadInternal(bool setTimeout, out bool more) if (moreRows) { // read the row from the backend (unless it's an altrow were the marker is already inside the altrow ...) - while (_stateObj._pendingData) + while (_stateObj.HasPendingData) { if (_altRowStatus != ALTROWSTATUS.AltRow) { @@ -3996,7 +3997,7 @@ private bool TryReadInternal(bool setTimeout, out bool more) } } - if (!_stateObj._pendingData) + if (!_stateObj.HasPendingData) { if (!TryCloseInternal(false /*closeReader*/)) { @@ -4020,7 +4021,7 @@ private bool TryReadInternal(bool setTimeout, out bool more) { // if we are in SingleRow mode, and we've read the first row, // read the rest of the rows, if any - while (_stateObj._pendingData && !_sharedState._dataReady) + while (_stateObj.HasPendingData && !_sharedState._dataReady) { if (!_parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _sharedState._dataReady)) { @@ -4061,7 +4062,7 @@ private bool TryReadInternal(bool setTimeout, out bool more) more = false; #if DEBUG - if ((!_sharedState._dataReady) && (_stateObj._pendingData)) + if ((!_sharedState._dataReady) && (_stateObj.HasPendingData)) { byte token; if (!_stateObj.TryPeekByte(out token)) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index ee8d211e7b..d844075902 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -910,11 +910,11 @@ override internal void ValidateConnectionForExecute(SqlCommand command) // or if MARS is off, then a datareader exists throw ADP.OpenReaderExists(parser.MARSOn); // MDAC 66411 } - else if (!parser.MARSOn && parser._physicalStateObj._pendingData) + else if (!parser.MARSOn && parser._physicalStateObj.HasPendingData) { parser.DrainData(parser._physicalStateObj); } - Debug.Assert(!parser._physicalStateObj._pendingData, "Should not have a busy physicalStateObject at this point!"); + Debug.Assert(!parser._physicalStateObj.HasPendingData, "Should not have a busy physicalStateObject at this point!"); parser.RollbackOrphanedAPITransactions(); } @@ -1073,7 +1073,7 @@ private void ResetConnection() // obtains a clone. Debug.Assert(!HasLocalTransactionFromAPI, "Upon ResetConnection SqlInternalConnectionTds has a currently ongoing local transaction."); - Debug.Assert(!_parser._physicalStateObj._pendingData, "Upon ResetConnection SqlInternalConnectionTds has pending data."); + Debug.Assert(!_parser._physicalStateObj.HasPendingData, "Upon ResetConnection SqlInternalConnectionTds has pending data."); if (_fResetConnection) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index a15da77757..bea1b31365 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -949,7 +949,7 @@ internal void EnableMars() internal TdsParserStateObject CreateSession() { - TdsParserStateObject session = new TdsParserStateObject(this, (SNIHandle)_pMarsPhysicalConObj.Handle, true); + TdsParserStateObject session = new TdsParserStateObject(this, _pMarsPhysicalConObj, true); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} created session {1}", ObjectID, session.ObjectID); return session; } @@ -964,7 +964,7 @@ internal TdsParserStateObject GetSession(object owner) { session = _sessionPool.GetSession(owner); - Debug.Assert(!session._pendingData, "pending data on a pooled MARS session"); + Debug.Assert(!session.HasPendingData, "pending data on a pooled MARS session"); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} getting session {1} from pool", ObjectID, session.ObjectID); } else @@ -1598,7 +1598,7 @@ internal void Deactivate(bool connectionIsDoomed) if (!connectionIsDoomed && null != _physicalStateObj) { - if (_physicalStateObj._pendingData) + if (_physicalStateObj.HasPendingData) { DrainData(_physicalStateObj); } @@ -2479,7 +2479,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead { if (token == TdsEnums.SQLERROR) { - stateObj._errorTokenReceived = true; // Keep track of the fact error token was received - for Done processing. + stateObj.HasReceivedError = true; // Keep track of the fact error token was received - for Done processing. } SqlError error; @@ -3009,18 +3009,18 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead break; } - Debug.Assert(stateObj._pendingData || !dataReady, "dataReady is set, but there is no pending data"); + Debug.Assert(stateObj.HasPendingData || !dataReady, "dataReady is set, but there is no pending data"); } // Loop while data pending & runbehavior not return immediately, OR // if in attention case, loop while no more pending data & attention has not yet been // received. - while ((stateObj._pendingData && + while ((stateObj.HasPendingData && (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))) || - (!stateObj._pendingData && stateObj._attentionSent && !stateObj._attentionReceived)); + (!stateObj.HasPendingData && stateObj._attentionSent && !stateObj.HasReceivedAttention)); #if DEBUG - if ((stateObj._pendingData) && (!dataReady)) + if ((stateObj.HasPendingData) && (!dataReady)) { byte token; if (!stateObj.TryPeekByte(out token)) @@ -3031,7 +3031,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead } #endif - if (!stateObj._pendingData) + if (!stateObj.HasPendingData) { if (null != CurrentTransaction) { @@ -3041,7 +3041,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead // if we recieved an attention (but this thread didn't send it) then // we throw an Operation Cancelled error - if (stateObj._attentionReceived) + if (stateObj.HasReceivedAttention) { // Dev11 #344723: SqlClient stress test suspends System_Data!Tcp::ReadSync via a call to SqlDataReader::Close // Spin until SendAttention has cleared _attentionSending, this prevents a race condition between receiving the attention ACK and setting _attentionSent @@ -3052,7 +3052,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead { // Reset attention state. stateObj._attentionSent = false; - stateObj._attentionReceived = false; + stateObj.HasReceivedAttention = false; if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior) && !stateObj.IsTimeoutStateExpired) { @@ -3518,7 +3518,7 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio { Debug.Assert(TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE), "Not expecting DONE_MORE when receiving DONE_ATTN"); Debug.Assert(stateObj._attentionSent, "Received attention done without sending one!"); - stateObj._attentionReceived = true; + stateObj.HasReceivedAttention = true; Debug.Assert(stateObj._inBytesUsed == stateObj._inBytesRead && stateObj._inBytesPacket == 0, "DONE_ATTN received with more data left on wire"); } if ((null != cmd) && (TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) @@ -3537,13 +3537,13 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio } // Skip the bogus DONE counts sent by the server - if (stateObj._receivedColMetaData || (curCmd != TdsEnums.SELECT)) + if (stateObj.HasReceivedColumnMetadata || (curCmd != TdsEnums.SELECT)) { cmd.OnStatementCompleted(count); } } - stateObj._receivedColMetaData = false; + stateObj.HasReceivedColumnMetadata = false; // Surface exception for DONE_ERROR in the case we did not receive an error token // in the stream, but an error occurred. In these cases, we throw a general server error. The @@ -3552,7 +3552,7 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio // the server has reached its max connection limit. Bottom line, we need to throw general // error in the cases where we did not receive a error token along with the DONE_ERROR. if ((TdsEnums.DONE_ERROR == (TdsEnums.DONE_ERROR & status)) && stateObj.ErrorCount == 0 && - stateObj._errorTokenReceived == false && (RunBehavior.Clean != (RunBehavior.Clean & run))) + stateObj.HasReceivedError == false && (RunBehavior.Clean != (RunBehavior.Clean & run))) { stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0)); @@ -3586,17 +3586,17 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio // stop if the DONE_MORE bit isn't set (see above for attention handling) if (TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE)) { - stateObj._errorTokenReceived = false; + stateObj.HasReceivedError = false; if (stateObj._inBytesUsed >= stateObj._inBytesRead) { - stateObj._pendingData = false; + stateObj.HasPendingData = false; } } // _pendingData set by e.g. 'TdsExecuteSQLBatch' // _hasOpenResult always set to true by 'WriteMarsHeader' // - if (!stateObj._pendingData && stateObj._hasOpenResult) + if (!stateObj.HasPendingData && stateObj.HasOpenResult) { /* Debug.Assert(!((sqlTransaction != null && _distributedTransaction != null) || @@ -5140,7 +5140,7 @@ internal void ThrowUnsupportedCollationEncountered(TdsParserStateObject stateObj { DrainData(stateObj); - stateObj._pendingData = false; + stateObj.HasPendingData = false; } ThrowExceptionAndWarning(stateObj); @@ -5742,7 +5742,7 @@ private bool TryCommonProcessMetaData(TdsParserStateObject stateObj, _SqlMetaDat // We get too many DONE COUNTs from the server, causing too meany StatementCompleted event firings. // We only need to fire this event when we actually have a meta data stream with 0 or more rows. - stateObj._receivedColMetaData = true; + stateObj.HasReceivedColumnMetadata = true; return true; } @@ -9340,8 +9340,8 @@ internal void TdsLogin(SqlLogin rec, } _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); - _physicalStateObj.ResetSecurePasswordsInfomation(); // Password information is needed only from Login process; done with writing login packet and should clear information - _physicalStateObj._pendingData = true; + _physicalStateObj.ResetSecurePasswordsInformation(); // Password information is needed only from Login process; done with writing login packet and should clear information + _physicalStateObj.HasPendingData = true; _physicalStateObj._messageStatus = 0; // Remvove CTAIP Provider after login record is sent. @@ -9389,7 +9389,7 @@ internal void SendFedAuthToken(SqlFedAuthToken fedAuthToken) _physicalStateObj.WriteByteArray(accessToken, accessToken.Length, 0); _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); - _physicalStateObj._pendingData = true; + _physicalStateObj.HasPendingData = true; _physicalStateObj._messageStatus = 0; _connHandler._federatedAuthenticationRequested = true; @@ -9701,7 +9701,7 @@ internal SqlDataReader TdsExecuteTransactionManagerRequest( Task writeTask = stateObj.WritePacket(TdsEnums.HARDFLUSH); Debug.Assert(writeTask == null, "Writes should not pend when writing sync"); - stateObj._pendingData = true; + stateObj.HasPendingData = true; stateObj._messageStatus = 0; SqlDataReader dtcReader = null; @@ -11223,7 +11223,7 @@ internal Task WriteBulkCopyDone(TdsParserStateObject stateObj) WriteShort(0, stateObj); WriteInt(0, stateObj); - stateObj._pendingData = true; + stateObj.HasPendingData = true; stateObj._messageStatus = 0; return stateObj.WritePacket(TdsEnums.HARDFLUSH); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index d8171c63b6..86e8c73db7 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -16,14 +16,26 @@ namespace Microsoft.Data.SqlClient { + internal readonly ref struct SessionHandle + { + public readonly SNIHandle NativeHandle; + + public SessionHandle(SNIHandle nativeHandle) => NativeHandle = nativeHandle; + + public bool IsNull => NativeHandle is null; + } + internal partial class TdsParserStateObject { + private static bool UseManagedSNI => false; + private SNIHandle _sessionHandle = null; // the SNI handle we're to work on - internal bool _pendingData = false; - internal bool _errorTokenReceived = false; // Keep track of whether an error was received for the result. - // This is reset upon each done token - there can be - // SNI variables // multiple resultsets in one batch. + private SessionHandle SessionHandle => new SessionHandle(_sessionHandle); + + private bool TransparentNetworkIPResolution => _parser.Connection.ConnectionOptions.TransparentNetworkIPResolution; + + // SNI variables private SNIPacket _sniPacket = null; // Will have to re-vamp this for MARS internal SNIPacket _sniAsyncAttnPacket = null; // Packet to use to send Attn private readonly WritePacketCache _writePacketCache = new WritePacketCache(); // Store write packets that are ready to be re-used @@ -32,2287 +44,253 @@ internal partial class TdsParserStateObject // Async variables private GCHandle _gcHandle; // keeps this object alive until we're closed. - // Timeout variables - internal bool _attentionReceived = false; // NOTE: Received is not volatile as it is only ever accessed\modified by TryRun its callees (i.e. single threaded access) - - // This variable is used to prevent sending an attention by another thread that is not the - // current owner of the stateObj. I currently do not know how this can happen. Mark added - // the code but does not remember either. At some point, we need to research killing this - // logic. - private volatile int _allowObjectID; - - internal bool _hasOpenResult = false; - - // Used for blanking out password in trace. - internal int _tracePasswordOffset = 0; - internal int _tracePasswordLength = 0; - internal int _traceChangePasswordOffset = 0; - internal int _traceChangePasswordLength = 0; - - internal bool _receivedColMetaData; // Used to keep track of when to fire StatementCompleted event. - - ////////////////// - // Constructors // - ////////////////// - - internal TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, bool async) - { - // Construct a MARS session - Debug.Assert(null != parser, "no parser?"); - _parser = parser; - SniContext = SniContext.Snix_GetMarsSession; - - Debug.Assert(null != _parser._physicalStateObj, "no physical session?"); - Debug.Assert(null != _parser._physicalStateObj._inBuff, "no in buffer?"); - Debug.Assert(null != _parser._physicalStateObj._outBuff, "no out buffer?"); - Debug.Assert(_parser._physicalStateObj._outBuff.Length == - _parser._physicalStateObj._inBuff.Length, "Unexpected unequal buffers."); - - // Determine packet size based on physical connection buffer lengths. - SetPacketSize(_parser._physicalStateObj._outBuff.Length); - - SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); - SQLDNSInfo cachedDNSInfo; - - SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCache, out cachedDNSInfo); - - _sessionHandle = new SNIHandle(myInfo, physicalConnection, _parser.Connection.ConnectionOptions.IPAddressPreference, cachedDNSInfo); - if (_sessionHandle.Status != TdsEnums.SNI_SUCCESS) - { - AddError(parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(); - } - - // we post a callback that represents the call to dispose; once the - // object is disposed, the next callback will cause the GC Handle to - // be released. - IncrementPendingCallbacks(); - _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer; - } - - //////////////// - // Properties // - //////////////// - internal SNIHandle Handle - { - get - { - return _sessionHandle; - } - } - - internal bool HasOpenResult - { - get => _hasOpenResult; - set => _hasOpenResult = value; - } - - internal bool HasPendingData - { - get => _pendingData; - set => _pendingData = value; - } - - internal uint Status - { - get - { - if (_sessionHandle != null) - { - return _sessionHandle.Status; - } - else - { // SQL BU DT 395431. - return TdsEnums.SNI_UNINITIALIZED; - } - } - } - - private partial struct NullBitmap - { - internal bool TryInitialize(TdsParserStateObject stateObj, int columnsCount) - { - _columnsCount = columnsCount; - // 1-8 columns need 1 byte - // 9-16: 2 bytes, and so on - int bitmapArrayLength = (columnsCount + 7) / 8; - - // allow reuse of previously allocated bitmap - if (_nullBitmap == null || _nullBitmap.Length != bitmapArrayLength) - { - _nullBitmap = new byte[bitmapArrayLength]; - } - - // read the null bitmap compression information from TDS - if (!stateObj.TryReadByteArray(_nullBitmap, _nullBitmap.Length)) - { - return false; - } - SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.NullBitmap.Initialize | INFO | ADV | State Object Id {0}, NBCROW bitmap received, column count = {1}", stateObj.ObjectID, columnsCount); - SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParserStateObject.NullBitmap.Initialize | INFO | ADV | State Object Id {0}, NBCROW bitmap data. Null Bitmap {1}, Null bitmap length: {2}", stateObj.ObjectID, _nullBitmap, (ushort)_nullBitmap.Length); - - return true; - } - } - - ///////////////////// - // General methods // - ///////////////////// - - // This method is only called by the command or datareader as a result of a user initiated - // cancel request. - internal void Cancel(int objectID) - { - bool hasLock = false; - try - { - // Keep looping until we either grabbed the lock (and therefore sent attention) or the connection closes\breaks - while ((!hasLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) - { - - Monitor.TryEnter(this, WaitForCancellationLockPollTimeout, ref hasLock); - if (hasLock) - { // Lock for the time being - since we need to synchronize the attention send. - // At some point in the future, I hope to remove this. - // This lock is also protecting against concurrent close and async continuations - - // don't allow objectID -1 since it is reserved for 'not associated with a command' - // yes, the 2^32-1 comand won't cancel - but it also won't cancel when we don't want it - if ((!_cancelled) && (objectID == _allowObjectID) && (objectID != -1)) - { - _cancelled = true; - - if (_pendingData && !_attentionSent) - { - bool hasParserLock = false; - // Keep looping until we have the parser lock (and so are allowed to write), or the conneciton closes\breaks - while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) - { - try - { - _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: WaitForCancellationLockPollTimeout, lockTaken: ref hasParserLock); - if (hasParserLock) - { - _parser.Connection.ThreadHasParserLockForClose = true; - SendAttention(); - } - } - finally - { - if (hasParserLock) - { - if (_parser.Connection.ThreadHasParserLockForClose) - { - _parser.Connection.ThreadHasParserLockForClose = false; - } - _parser.Connection._parserLock.Release(); - } - } - } - } - } - } - } - } - finally - { - if (hasLock) - { - Monitor.Exit(this); - } - } - } - - private void ResetCancelAndProcessAttention() - { - // This method is shared by CloseSession initiated by DataReader.Close or completed - // command execution, as well as the session reclaimation code for cases where the - // DataReader is opened and then GC'ed. - lock (this) - { - // Reset cancel state. - _cancelled = false; - _allowObjectID = -1; - - if (_attentionSent) - { - // Make sure we're cleaning up the AttentionAck if Cancel happened before taking the lock. - // We serialize Cancel/CloseSession to prevent a race condition between these two states. - // The problem is that both sending and receiving attentions are time taking - // operations. -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#endif //DEBUG - Parser.ProcessPendingAck(this); -#if DEBUG - } - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - SetTimeoutStateStopped(); - } - } - - private SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async) - { - SNINativeMethodWrapper.ConsumerInfo myInfo = new SNINativeMethodWrapper.ConsumerInfo(); - - Debug.Assert(_outBuff.Length == _inBuff.Length, "Unexpected unequal buffers."); - - myInfo.defaultBufferSize = _outBuff.Length; // Obtain packet size from outBuff size. - - if (async) - { - myInfo.readDelegate = SNILoadHandle.SingletonInstance.ReadAsyncCallbackDispatcher; - myInfo.writeDelegate = SNILoadHandle.SingletonInstance.WriteAsyncCallbackDispatcher; - _gcHandle = GCHandle.Alloc(this, GCHandleType.Normal); - myInfo.key = (IntPtr)_gcHandle; - } - return myInfo; - } - - internal void CreatePhysicalSNIHandle( - string serverName, - bool ignoreSniOpenTimeout, - long timerExpire, - out byte[] instanceName, - byte[] spnBuffer, - bool flushCache, - bool async, - bool fParallel, - TransparentNetworkResolutionState transparentNetworkResolutionState, - int totalTimeout, - SqlConnectionIPAddressPreference ipPreference, - string cachedFQDN, - string hostNameInCertificate = "") - { - SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); - - // Translate to SNI timeout values (Int32 milliseconds) - long timeout; - if (long.MaxValue == timerExpire) - { - timeout = int.MaxValue; - } - else - { - timeout = ADP.TimerRemainingMilliseconds(timerExpire); - if (timeout > int.MaxValue) - { - timeout = int.MaxValue; - } - else if (0 > timeout) - { - timeout = 0; - } - } - - // serverName : serverInfo.ExtendedServerName - // may not use this serverName as key - - _ = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out SQLDNSInfo cachedDNSInfo); - - _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer, ignoreSniOpenTimeout, checked((int)timeout), - out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout, - ipPreference, cachedDNSInfo, hostNameInCertificate); - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - internal int DecrementPendingCallbacks(bool release) - { - int remaining = Interlocked.Decrement(ref _pendingCallbacks); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, after decrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks); - - if ((0 == remaining || release) && _gcHandle.IsAllocated) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, FREEING HANDLE!", ObjectID); - _gcHandle.Free(); - } - - // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed - // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it - Debug.Assert((remaining == -1 && _sessionHandle == null) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}"); - return remaining; - } - - internal void Dispose() - { - - SafeHandle packetHandle = _sniPacket; - SafeHandle sessionHandle = _sessionHandle; - SafeHandle asyncAttnPacket = _sniAsyncAttnPacket; - _sniPacket = null; - _sessionHandle = null; - _sniAsyncAttnPacket = null; - - DisposeCounters(); - - if (null != sessionHandle || null != packetHandle) - { - // Comment CloseMARSSession - // UNDONE - if there are pending reads or writes on logical connections, we need to block - // here for the callbacks!!! This only applies to async. Should be fixed by async fixes for - // AD unload/exit. - - // TODO: Make this a BID trace point! - RuntimeHelpers.PrepareConstrainedRegions(); - try - { } - finally - { - if (packetHandle != null) - { - packetHandle.Dispose(); - } - if (asyncAttnPacket != null) - { - asyncAttnPacket.Dispose(); - } - if (sessionHandle != null) - { - sessionHandle.Dispose(); - DecrementPendingCallbacks(true); // Will dispose of GC handle. - } - } - } - - if (_writePacketCache != null) - { - lock (_writePacketLockObject) - { - RuntimeHelpers.PrepareConstrainedRegions(); - try - { } - finally - { - _writePacketCache.Dispose(); - // Do not set _writePacketCache to null, just in case a WriteAsyncCallback completes after this point - } - } - } - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - internal int IncrementPendingCallbacks() - { - int remaining = Interlocked.Increment(ref _pendingCallbacks); - - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, after incrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks); - Debug.Assert(0 < remaining && remaining <= 3, $"_pendingCallbacks values is invalid after incrementing: {remaining}"); - return remaining; - } - - internal void StartSession(int objectID) - { - _allowObjectID = objectID; - } - - /////////////////////////////////////// - // Buffer read methods - data values // - /////////////////////////////////////// - - // look at the next byte without pulling it off the wire, don't just returun _inBytesUsed since we may - // have to go to the network to get the next byte. - internal bool TryPeekByte(out byte value) - { - if (!TryReadByte(out value)) - { - return false; - } - - // now do fixup - _inBytesPacket++; - _inBytesUsed--; - - AssertValidState(); - return true; - } - - // Takes a byte array, an offset, and a len and fills the array from the offset to len number of - // bytes from the in buffer. - public bool TryReadByteArray(Span buff, int len) - { - return TryReadByteArray(buff, len, out _); - } - - // NOTE: This method must be retriable WITHOUT replaying a snapshot - // Every time you call this method increment the offset and decrease len by the value of totalRead - public bool TryReadByteArray(Span buff, int len, out int totalRead) - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadByteArray"); // you need to setup for a thread abort somewhere before you call this method - totalRead = 0; - -#if DEBUG - if (_snapshot != null && _snapshot.DoPend()) - { - _networkPacketTaskSource = new TaskCompletionSource(); - Thread.MemoryBarrier(); - - if (s_forcePendingReadsToWaitForUser) - { - _realNetworkPacketTaskSource = new TaskCompletionSource(); - _realNetworkPacketTaskSource.SetResult(null); - } - else - { - _networkPacketTaskSource.TrySetResult(null); - } - return false; - } -#endif - - Debug.Assert(buff.IsEmpty || buff.Length >= len, "Invalid length sent to ReadByteArray()!"); - - // loop through and read up to array length - while (len > 0) - { - if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) - { - if (!TryPrepareBuffer()) - { - return false; - } - } - - int bytesToRead = Math.Min(len, Math.Min(_inBytesPacket, _inBytesRead - _inBytesUsed)); - Debug.Assert(bytesToRead > 0, "0 byte read in TryReadByteArray"); - if (!buff.IsEmpty) - { - ReadOnlySpan copyFrom = new ReadOnlySpan(_inBuff, _inBytesUsed, bytesToRead); - Span copyTo = buff.Slice(totalRead, bytesToRead); - copyFrom.CopyTo(copyTo); - } - - totalRead += bytesToRead; - _inBytesUsed += bytesToRead; - _inBytesPacket -= bytesToRead; - len -= bytesToRead; - - AssertValidState(); - } - - return true; - } - - // Takes no arguments and returns a byte from the buffer. If the buffer is empty, it is filled - // before the byte is returned. - internal bool TryReadByte(out byte value) - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadByte"); // you need to setup for a thread abort somewhere before you call this method - Debug.Assert(_inBytesUsed >= 0 && _inBytesUsed <= _inBytesRead, "ERROR - TDSParser: _inBytesUsed < 0 or _inBytesUsed > _inBytesRead"); - value = 0; - -#if DEBUG - if (_snapshot != null && _snapshot.DoPend()) - { - _networkPacketTaskSource = new TaskCompletionSource(); - Thread.MemoryBarrier(); - - if (s_forcePendingReadsToWaitForUser) - { - _realNetworkPacketTaskSource = new TaskCompletionSource(); - _realNetworkPacketTaskSource.SetResult(null); - } - else - { - _networkPacketTaskSource.TrySetResult(null); - } - return false; - } -#endif - - if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) - { - if (!TryPrepareBuffer()) - { - return false; - } - } - - // decrement the number of bytes left in the packet - _inBytesPacket--; - - Debug.Assert(_inBytesPacket >= 0, "ERROR - TDSParser: _inBytesPacket < 0"); - - // return the byte from the buffer and increment the counter for number of bytes used in the in buffer - value = (_inBuff[_inBytesUsed++]); - - AssertValidState(); - return true; - } - - internal bool TryReadChar(out char value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - - byte[] buffer; - int offset; - if (((_inBytesUsed + 2) > _inBytesRead) || (_inBytesPacket < 2)) - { - // If the char isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - if (!TryReadByteArray(_bTmp, 2)) - { - value = '\0'; - return false; - } - - buffer = _bTmp; - offset = 0; - } - else - { - // The entire char is in the packet and in the buffer, so just return it - // and take care of the counters. - - buffer = _inBuff; - offset = _inBytesUsed; - - _inBytesUsed += 2; - _inBytesPacket -= 2; - } - - AssertValidState(); - value = (char)((buffer[offset + 1] << 8) + buffer[offset]); - return true; - } - - internal bool TryReadInt16(out short value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - - byte[] buffer; - int offset; - if (((_inBytesUsed + 2) > _inBytesRead) || (_inBytesPacket < 2)) - { - // If the int16 isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - if (!TryReadByteArray(_bTmp, 2)) - { - value = default; - return false; - } - - buffer = _bTmp; - offset = 0; - } - else - { - // The entire int16 is in the packet and in the buffer, so just return it - // and take care of the counters. - - buffer = _inBuff; - offset = _inBytesUsed; - - _inBytesUsed += 2; - _inBytesPacket -= 2; - } - - AssertValidState(); - value = (short)((buffer[offset + 1] << 8) + buffer[offset]); - return true; - } - - internal bool TryReadInt32(out int value) - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadInt32"); // you need to setup for a thread abort somewhere before you call this method - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - if (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4)) - { - // If the int isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - if (!TryReadByteArray(_bTmp, 4)) - { - value = 0; - return false; - } - - AssertValidState(); - value = BitConverter.ToInt32(_bTmp, 0); - return true; - } - else - { - // The entire int is in the packet and in the buffer, so just return it - // and take care of the counters. - - value = BitConverter.ToInt32(_inBuff, _inBytesUsed); - - _inBytesUsed += 4; - _inBytesPacket -= 4; - - AssertValidState(); - return true; - } - } - - // This method is safe to call when doing async without snapshot - internal bool TryReadInt64(out long value) - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadInt64"); // you need to setup for a thread abort somewhere before you call this method - if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) - { - if (!TryPrepareBuffer()) - { - value = 0; - return false; - } - } - - if ((_bTmpRead > 0) || (((_inBytesUsed + 8) > _inBytesRead) || (_inBytesPacket < 8))) - { - // If the long isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - int bytesRead; - if (!TryReadByteArray(_bTmp.AsSpan(start: _bTmpRead), 8 - _bTmpRead, out bytesRead)) - { - Debug.Assert(_bTmpRead + bytesRead <= 8, "Read more data than required"); - _bTmpRead += bytesRead; - value = 0; - return false; - } - else - { - Debug.Assert(_bTmpRead + bytesRead == 8, "TryReadByteArray returned true without reading all data required"); - _bTmpRead = 0; - AssertValidState(); - value = BitConverter.ToInt64(_bTmp, 0); - return true; - } - } - else - { - // The entire long is in the packet and in the buffer, so just return it - // and take care of the counters. - - value = BitConverter.ToInt64(_inBuff, _inBytesUsed); - - _inBytesUsed += 8; - _inBytesPacket -= 8; - - AssertValidState(); - return true; - } - } - - internal bool TryReadUInt16(out ushort value) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - - byte[] buffer; - int offset; - if (((_inBytesUsed + 2) > _inBytesRead) || (_inBytesPacket < 2)) - { - // If the uint16 isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - if (!TryReadByteArray(_bTmp, 2)) - { - value = default; - return false; - } - - buffer = _bTmp; - offset = 0; - } - else - { - // The entire uint16 is in the packet and in the buffer, so just return it - // and take care of the counters. - - buffer = _inBuff; - offset = _inBytesUsed; - - _inBytesUsed += 2; - _inBytesPacket -= 2; - } - - AssertValidState(); - value = (ushort)((buffer[offset + 1] << 8) + buffer[offset]); - return true; - } - - // This method is safe to call when doing async without replay - internal bool TryReadUInt32(out uint value) - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadUInt32"); // you need to setup for a thread abort somewhere before you call this method - if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) - { - if (!TryPrepareBuffer()) - { - value = 0; - return false; - } - } - - if ((_bTmpRead > 0) || (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4))) - { - // If the int isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - int bytesRead; - if (!TryReadByteArray(_bTmp.AsSpan(start: _bTmpRead), 4 - _bTmpRead, out bytesRead)) - { - Debug.Assert(_bTmpRead + bytesRead <= 4, "Read more data than required"); - _bTmpRead += bytesRead; - value = 0; - return false; - } - else - { - Debug.Assert(_bTmpRead + bytesRead == 4, "TryReadByteArray returned true without reading all data required"); - _bTmpRead = 0; - AssertValidState(); - value = BitConverter.ToUInt32(_bTmp, 0); - return true; - } - } - else - { - // The entire int is in the packet and in the buffer, so just return it - // and take care of the counters. - - value = BitConverter.ToUInt32(_inBuff, _inBytesUsed); - - _inBytesUsed += 4; - _inBytesPacket -= 4; - - AssertValidState(); - return true; - } - } - - internal bool TryReadSingle(out float value) - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadSingle"); // you need to setup for a thread abort somewhere before you call this method - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - if (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4)) - { - // If the float isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - if (!TryReadByteArray(_bTmp, 4)) - { - value = default; - return false; - } - - AssertValidState(); - value = BitConverter.ToSingle(_bTmp, 0); - return true; - } - else - { - // The entire float is in the packet and in the buffer, so just return it - // and take care of the counters. - - value = BitConverter.ToSingle(_inBuff, _inBytesUsed); - - _inBytesUsed += 4; - _inBytesPacket -= 4; - - AssertValidState(); - return true; - } - } - - internal bool TryReadDouble(out double value) - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadDouble"); // you need to setup for a thread abort somewhere before you call this method - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - if (((_inBytesUsed + 8) > _inBytesRead) || (_inBytesPacket < 8)) - { - // If the double isn't fully in the buffer, or if it isn't fully in the packet, - // then use ReadByteArray since the logic is there to take care of that. - - if (!TryReadByteArray(_bTmp, 8)) - { - value = default; - return false; - } - - AssertValidState(); - value = BitConverter.ToDouble(_bTmp, 0); - return true; - } - else - { - // The entire double is in the packet and in the buffer, so just return it - // and take care of the counters. - - value = BitConverter.ToDouble(_inBuff, _inBytesUsed); - - _inBytesUsed += 8; - _inBytesPacket -= 8; - - AssertValidState(); - return true; - } - } - - internal bool TryReadString(int length, out string value) - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadString"); // you need to setup for a thread abort somewhere before you call this method - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - int cBytes = length << 1; - byte[] buf; - int offset = 0; - - if (((_inBytesUsed + cBytes) > _inBytesRead) || (_inBytesPacket < cBytes)) - { - if (_bTmp == null || _bTmp.Length < cBytes) - { - _bTmp = new byte[cBytes]; - } - - if (!TryReadByteArray(_bTmp, cBytes)) - { - value = null; - return false; - } - - // assign local to point to parser scratch buffer - buf = _bTmp; - - AssertValidState(); - } - else - { - // assign local to point to _inBuff - buf = _inBuff; - offset = _inBytesUsed; - _inBytesUsed += cBytes; - _inBytesPacket -= cBytes; - - AssertValidState(); - } - - value = System.Text.Encoding.Unicode.GetString(buf, offset, cBytes); - return true; - } - - internal bool TryReadStringWithEncoding(int length, System.Text.Encoding encoding, bool isPlp, out string value) - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadStringWithEncoding"); // you need to setup for a thread abort somewhere before you call this method - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - - if (null == encoding) - { - // Bug 462435:CR: TdsParser.DrainData(stateObj) hitting timeout exception after Connection Resiliency change - // http://vstfdevdiv:8080/web/wi.aspx?pcguid=22f9acc9-569a-41ff-b6ac-fac1b6370209&id=462435 - // Need to skip the current column before throwing the error - this ensures that the state shared between this and the data reader is consistent when calling DrainData - if (isPlp) - { - if (!_parser.TrySkipPlpValue((ulong)length, this, out _)) - { - value = null; - return false; - } - } - else - { - if (!TrySkipBytes(length)) - { - value = null; - return false; - } - } - - _parser.ThrowUnsupportedCollationEncountered(this); - } - byte[] buf = null; - int offset = 0; - - if (isPlp) - { - if (!TryReadPlpBytes(ref buf, 0, int.MaxValue, out length)) - { - value = null; - return false; - } - - AssertValidState(); - } - else - { - if (((_inBytesUsed + length) > _inBytesRead) || (_inBytesPacket < length)) - { - if (_bTmp == null || _bTmp.Length < length) - { - _bTmp = new byte[length]; - } - - if (!TryReadByteArray(_bTmp, length)) - { - value = null; - return false; - } - - // assign local to point to parser scratch buffer - buf = _bTmp; - - AssertValidState(); - } - else - { - // assign local to point to _inBuff - buf = _inBuff; - offset = _inBytesUsed; - _inBytesUsed += length; - _inBytesPacket -= length; - - AssertValidState(); - } - } - - // BCL optimizes to not use char[] underneath - value = encoding.GetString(buf, offset, length); - return true; - } - - internal ulong ReadPlpLength(bool returnPlpNullIfNull) - { - ulong value; - Debug.Assert(_syncOverAsync, "Should not attempt pends in a synchronous call"); - bool result = TryReadPlpLength(returnPlpNullIfNull, out value); - if (!result) - { throw SQL.SynchronousCallMayNotPend(); } - return value; - } - - // Reads the length of either the entire data or the length of the next chunk in a - // partially length prefixed data - // After this call, call ReadPlpBytes/ReadPlpUnicodeChars untill the specified length of data - // is consumed. Repeat this until ReadPlpLength returns 0 in order to read the - // entire stream. - // When this function returns 0, it means the data stream is read completely and the - // plp state in the tdsparser is cleaned. - internal bool TryReadPlpLength(bool returnPlpNullIfNull, out ulong lengthLeft) - { - uint chunklen; - // bool firstchunk = false; - bool isNull = false; - - Debug.Assert(_longlenleft == 0, "Out of synch length read request"); - if (_longlen == 0) - { - // First chunk is being read. Find out what type of chunk it is - long value; - if (!TryReadInt64(out value)) - { - lengthLeft = 0; - return false; - } - _longlen = (ulong)value; - // firstchunk = true; - } - - if (_longlen == TdsEnums.SQL_PLP_NULL) - { - _longlen = 0; - _longlenleft = 0; - isNull = true; - } - else - { - // Data is coming in uint chunks, read length of next chunk - if (!TryReadUInt32(out chunklen)) - { - lengthLeft = 0; - return false; - } - if (chunklen == TdsEnums.SQL_PLP_CHUNK_TERMINATOR) - { - _longlenleft = 0; - _longlen = 0; - } - else - { - _longlenleft = chunklen; - } - } - - AssertValidState(); - - if (isNull && returnPlpNullIfNull) - { - lengthLeft = TdsEnums.SQL_PLP_NULL; - return true; - } - - lengthLeft = _longlenleft; - return true; - } - - internal int ReadPlpBytesChunk(byte[] buff, int offset, int len) - { - Debug.Assert(_syncOverAsync, "Should not attempt pends in a synchronous call"); - Debug.Assert(_longlenleft > 0, "Read when no data available"); - - int value; - int bytesToRead = (int)Math.Min(_longlenleft, (ulong)len); - bool result = TryReadByteArray(buff.AsSpan(start: offset), bytesToRead, out value); - _longlenleft -= (ulong)bytesToRead; - if (!result) - { - throw SQL.SynchronousCallMayNotPend(); - } - return value; - } - - // Reads the requested number of bytes from a plp data stream, or the entire data if - // requested length is -1 or larger than the actual length of data. First call to this method - // should be preceeded by a call to ReadPlpLength or ReadDataLength. - // Returns the actual bytes read. - // NOTE: This method must be retriable WITHOUT replaying a snapshot - // Every time you call this method increment the offst and decrease len by the value of totalBytesRead - internal bool TryReadPlpBytes(ref byte[] buff, int offst, int len, out int totalBytesRead) - { - int bytesRead; - int bytesLeft; - byte[] newbuf; - - if (_longlen == 0) - { - Debug.Assert(_longlenleft == 0); - if (buff == null) - { - buff = new byte[0]; - } - - AssertValidState(); - totalBytesRead = 0; - return true; // No data - } - - Debug.Assert((_longlen != TdsEnums.SQL_PLP_NULL), - "Out of sync plp read request"); - - Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpBytes()!"); - bytesLeft = len; - - // If total length is known up front, allocate the whole buffer in one shot instead of realloc'ing and copying over each time - if (buff == null && _longlen != TdsEnums.SQL_PLP_UNKNOWNLEN) - { - buff = new byte[(Math.Min((int)_longlen, len))]; - } - - if (_longlenleft == 0) - { - if (!TryReadPlpLength(false, out _)) - { - totalBytesRead = 0; - return false; - } - if (_longlenleft == 0) - { // Data read complete - totalBytesRead = 0; - return true; - } - } - - if (buff == null) - { - buff = new byte[_longlenleft]; - } - - totalBytesRead = 0; - - while (bytesLeft > 0) - { - int bytesToRead = (int)Math.Min(_longlenleft, (ulong)bytesLeft); - if (buff.Length < (offst + bytesToRead)) - { - // Grow the array - newbuf = new byte[offst + bytesToRead]; - Buffer.BlockCopy(buff, 0, newbuf, 0, offst); - buff = newbuf; - } - - bool result = TryReadByteArray(buff.AsSpan(start: offst), bytesToRead, out bytesRead); - Debug.Assert(bytesRead <= bytesLeft, "Read more bytes than we needed"); - Debug.Assert((ulong)bytesRead <= _longlenleft, "Read more bytes than is available"); - - bytesLeft -= bytesRead; - offst += bytesRead; - totalBytesRead += bytesRead; - _longlenleft -= (ulong)bytesRead; - if (!result) - { - return false; - } - - if (_longlenleft == 0) - { // Read the next chunk or cleanup state if hit the end - if (!TryReadPlpLength(false, out _)) - { - return false; - } - } - - AssertValidState(); - - // Catch the point where we read the entire plp data stream and clean up state - if (_longlenleft == 0) // Data read complete - break; - } - return true; - } - - - ///////////////////////////////////////// - // Value Skip Logic // - ///////////////////////////////////////// - - - // Reads bytes from the buffer but doesn't return them, in effect simply deleting them. - // Does not handle plp fields, need to use SkipPlpBytesValue for those. - // Does not handle null values or NBC bitmask, ensure the value is not null before calling this method - internal bool TrySkipLongBytes(long num) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - - while (num > 0) - { - int cbSkip = (int)Math.Min(int.MaxValue, num); - if (!TryReadByteArray(Span.Empty, cbSkip)) - { - return false; - } - num -= cbSkip; - } - - return true; - } - - // Reads bytes from the buffer but doesn't return them, in effect simply deleting them. - internal bool TrySkipBytes(int num) - { - Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); - return TryReadByteArray(Span.Empty, num); - } - - ///////////////////////////////////////// - // Network/Packet Reading & Processing // - ///////////////////////////////////////// - - internal void SetSnapshot() - { - _snapshot = new StateSnapshot(this); - _snapshot.Snap(); - _snapshotReplay = false; - } - - internal void ResetSnapshot() - { - _snapshot = null; - _snapshotReplay = false; - } - -#if DEBUG - StackTrace _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 (_snapshot.Replay()) - { -#if DEBUG - if (s_checkNetworkPacketRetryStacks) - { - _snapshot.CheckStack(new StackTrace()); - } -#endif - SqlClientEventSource.Log.TryTraceEvent(" Async packet replay{0}", "INFO"); - return true; - } -#if DEBUG - else - { - if (s_checkNetworkPacketRetryStacks) - { - _lastStack = new 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 _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.PrepareReplay(); - } - - internal void ReadSniSyncOverAsync() - { - if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) - { - throw ADP.ClosedConnectionError(); - } - - IntPtr readPacket = IntPtr.Zero; - 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; - - SNIHandle handle = Handle; - if (handle == null) - { - throw ADP.ClosedConnectionError(); - } - - error = SNINativeMethodWrapper.SNIReadSyncOverAsync(handle, ref readPacket, GetTimeoutRemaining()); - - 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(ADP.s_ptrZero != readPacket, "ReadNetworkPacket cannot be null in synchronous operation!"); - ProcessSniPacket(readPacket, 0); -#if DEBUG - if (s_forcePendingReadsToWaitForUser) - { - _networkPacketTaskSource = new TaskCompletionSource(); - Thread.MemoryBarrier(); - _networkPacketTaskSource.Task.Wait(); - _networkPacketTaskSource = null; - } -#endif - } - else - { // Failure! - Debug.Assert(IntPtr.Zero == readPacket, "unexpected readPacket without corresponding SNIPacketRelease"); - ReadSniError(this, error); - } - } - finally - { - if (shouldDecrement) - { - Interlocked.Decrement(ref _readingCount); - } - - if (readPacket != IntPtr.Zero) - { - // Be sure to release packet, otherwise it will be leaked by native. - SNINativeMethodWrapper.SNIPacketRelease(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 - Thread.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) - { - // Dev11 Bug 390048 : Timing issue between OnTimeout and ReadAsyncCallback results in SqlClient's packet parsing going out of sync - // 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 - Thread.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 - - IntPtr readPacket = IntPtr.Zero; - 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 = new Timer( - new TimerCallback(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); - } - - SNIHandle handle = null; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { } - finally - { - Interlocked.Increment(ref _readingCount); - - handle = Handle; - if (handle != null) - { - - IncrementPendingCallbacks(); - - error = SNINativeMethodWrapper.SNIReadAsync(handle, ref readPacket); - - 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 == null) - { - throw ADP.ClosedConnectionError(); - } - - if (TdsEnums.SNI_SUCCESS == error) - { // Success - process results! - Debug.Assert(ADP.s_ptrZero != readPacket, "ReadNetworkPacket should not have been null on this async operation!"); - ReadAsyncCallback(ADP.s_ptrZero, readPacket, 0); - } - else if (TdsEnums.SNI_SUCCESS_IO_PENDING != error) - { // FAILURE! - Debug.Assert(IntPtr.Zero == 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 (readPacket != IntPtr.Zero) - { - // Be sure to release packet, otherwise it will be leaked by native. - SNINativeMethodWrapper.SNIPacketRelease(readPacket); - } - - AssertValidState(); - } - } - - /// - /// Checks to see if the underlying connection is still alive (used by connection pool resilency) - /// 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; - IntPtr readPacket = IntPtr.Zero; - - 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 = SNINativeMethodWrapper.SNICheckConnection(Handle); - - if ((error != TdsEnums.SNI_SUCCESS) && (error != TdsEnums.SNI_WAIT_TIMEOUT)) - { - // Connection is dead - SqlClientEventSource.Log.TryTraceEvent(" received error {0} on idle connection", (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 - { - if (readPacket != IntPtr.Zero) - { - // Be sure to release packet, otherwise it will be leaked by native. - SNINativeMethodWrapper.SNIPacketRelease(readPacket); - } - - } - } - } - - 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 - /// NOTE: This will mark the connection as broken if it is found to be dead - /// - /// True if the connection is still alive, otherwise false - internal bool ValidateSNIConnection() - { - if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed))) - { - return false; - } - - if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value <= CheckConnectionWindow) - { - return true; - } - - uint error = TdsEnums.SNI_SUCCESS; - SniContext = SniContext.Snix_Connect; - try - { - Interlocked.Increment(ref _readingCount); - SNIHandle handle = Handle; - if (handle != null) - { - error = SNINativeMethodWrapper.SNICheckConnection(handle); - } - } - finally - { - Interlocked.Decrement(ref _readingCount); - } - return (error == TdsEnums.SNI_SUCCESS) || (error == TdsEnums.SNI_WAIT_TIMEOUT); - } - - // This method should only be called by ReadSni! If not - it may have problems with timeouts! - private void ReadSniError(TdsParserStateObject stateObj, uint error) - { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadSniSyncError"); // you need to setup for a thread abort somewhere before you call this method - - if (TdsEnums.SNI_WAIT_TIMEOUT == error) - { - Debug.Assert(_syncOverAsync, "Should never reach here with async on!"); - bool fail = false; - - if (IsTimeoutStateExpired) - { // This is now our second timeout - time to give up. - fail = true; - } - else - { - stateObj.SetTimeoutStateStopped(); - Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point."); - AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); - - if (!stateObj._attentionSent) - { - if (stateObj.Parser.State == TdsParserState.OpenLoggedIn) - { - stateObj.SendAttention(mustTakeWriteLock: true); - - IntPtr syncReadPacket = IntPtr.Zero; - RuntimeHelpers.PrepareConstrainedRegions(); - bool shouldDecrement = false; - try - { - Interlocked.Increment(ref _readingCount); - shouldDecrement = true; - - SNIHandle handle = Handle; - if (handle == null) - { - throw ADP.ClosedConnectionError(); - } - - error = SNINativeMethodWrapper.SNIReadSyncOverAsync(handle, ref syncReadPacket, stateObj.GetTimeoutRemaining()); - - Interlocked.Decrement(ref _readingCount); - shouldDecrement = false; - - if (TdsEnums.SNI_SUCCESS == error) - { - // We will end up letting the run method deal with the expected done:done_attn token stream. - stateObj.ProcessSniPacket(syncReadPacket, 0); - return; - } - else - { - Debug.Assert(IntPtr.Zero == syncReadPacket, "unexpected syncReadPacket without corresponding SNIPacketRelease"); - fail = true; // Subsequent read failed, time to give up. - } - } - finally - { - if (shouldDecrement) - { - Interlocked.Decrement(ref _readingCount); - } - - if (syncReadPacket != IntPtr.Zero) - { - // Be sure to release packet, otherwise it will be leaked by native. - SNINativeMethodWrapper.SNIPacketRelease(syncReadPacket); - } - } - } - else - { - if (_parser._loginWithFailover) - { - // For DB Mirroring Failover during login, never break the connection, just close the TdsParser (Devdiv 846298) - _parser.Disconnect(); - } - else if ((_parser.State == TdsParserState.OpenNotLoggedIn) && (_parser.Connection.ConnectionOptions.MultiSubnetFailover || _parser.Connection.ConnectionOptions.TransparentNetworkIPResolution)) - { - // For MultiSubnet Failover during login, never break the connection, just close the TdsParser - _parser.Disconnect(); - } - else - fail = true; // We aren't yet logged in - just fail. - } - } - } - - if (fail) - { - _parser.State = TdsParserState.Broken; // We failed subsequent read, we have to quit! - _parser.Connection.BreakConnection(); - } - } - else - { - // Caution: ProcessSNIError always returns a fatal error! - AddError(_parser.ProcessSNIError(stateObj)); - } - ThrowExceptionAndWarning(); - - AssertValidState(); - } - - // TODO: - does this need to be MUSTRUN??? - public void ProcessSniPacket(IntPtr packet, uint error) - { - if (error != 0) - { - if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) - { - // Do nothing with callback if closed or broken and error not 0 - callback can occur - // after connection has been closed. PROBLEM IN NETLIB - DESIGN FLAW. - return; - } - - AddError(_parser.ProcessSNIError(this)); - AssertValidState(); - } - else - { - uint dataSize = 0; - uint getDataError = SNINativeMethodWrapper.SNIPacketGetData(packet, _inBuff, ref dataSize); - - if (getDataError == TdsEnums.SNI_SUCCESS) - { - if (_inBuff.Length < dataSize) - { - Debug.Assert(true, "Unexpected dataSize on Read"); - throw SQL.InvalidInternalPacketSize(StringsHelper.GetString(Strings.SqlMisc_InvalidArraySizeMessage)); - } - - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; - _inBytesRead = (int)dataSize; - _inBytesUsed = 0; - - if (_snapshot != null) - { - _snapshot.PushBuffer(_inBuff, _inBytesRead); - if (_snapshotReplay) - { - _snapshot.Replay(); -#if DEBUG - _snapshot.AssertCurrent(); -#endif - } - } - SniReadStatisticsAndTracing(); - SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.ReadNetworkPacketAsyncCallback | INFO | ADV | State Object Id {0}, Packet read. In Buffer {1}, In Bytes Read: {2}", ObjectID, _inBuff, (ushort)_inBytesRead); - AssertValidState(); - } - else - { - throw SQL.ParsingError(ParsingErrorState.ProcessSniPacketFailed); - } - } - } + // Used for blanking out password in trace. + internal int _tracePasswordOffset = 0; + internal int _tracePasswordLength = 0; + internal int _traceChangePasswordOffset = 0; + internal int _traceChangePasswordLength = 0; - private void ChangeNetworkPacketTimeout(int dueTime, int period) + //////////////// + // Properties // + //////////////// + internal SNIHandle Handle { - Timer networkPacketTimeout = _networkPacketTimeout; - if (networkPacketTimeout != null) + get { - try - { - networkPacketTimeout.Change(dueTime, period); - } - catch (ObjectDisposedException) - { - // _networkPacketTimeout is set to null before Disposing, but there is still a slight chance - // that object was disposed after we took a copy - } + return _sessionHandle; } } - public void ReadAsyncCallback(IntPtr key, IntPtr packet, uint error) - { // Key never used. - // Note - it's possible that when native calls managed that an asynchronous exception - // could occur in the native->managed transition, which would - // have two impacts: - // 1) user event not called - // 2) DecrementPendingCallbacks not called, which would mean this object would be leaked due - // to the outstanding GCRoot until AppDomain.Unload. - // We live with the above for the time being due to the constraints of the current - // reliability infrastructure provided by the CLR. - - TaskCompletionSource source = _networkPacketTaskSource; -#if DEBUG - if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null)) - { - source = _realNetworkPacketTaskSource; - } -#endif + internal bool HasOpenResult { get; set; } + + internal bool HasPendingData { get; set; } + internal bool HasReceivedError { get; set; } // Keep track of whether an error was received for the result. + // This is reset upon each done token - there can be + // multiple resultsets in one batch. + internal bool HasReceivedAttention { get; set; } // NOTE: Received is not volatile as it is only ever accessed\modified by TryRun its callees (i.e. single threaded access) - // The mars physical connection can get a callback - // with a packet but no result after the connection is closed. - if (source == null && _parser._pMarsPhysicalConObj == this) - { - return; - } + internal bool HasReceivedColumnMetadata { get; set; } // Used to keep track of when to fire StatementCompleted event. - RuntimeHelpers.PrepareConstrainedRegions(); - bool processFinallyBlock = true; - try + internal uint Status + { + get { - Debug.Assert(IntPtr.Zero == packet || IntPtr.Zero != packet && source != null, "AsyncResult null on callback"); - if (_parser.MARSOn) - { // Only take reset lock on MARS and Async. - CheckSetResetConnectionState(error, CallbackType.Read); + if (_sessionHandle != null) + { + return _sessionHandle.Status; } + else + { // SQL BU DT 395431. + return TdsEnums.SNI_UNINITIALIZED; + } + } + } - ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); + ///////////////////// + // General methods // + ///////////////////// - // The timer thread may be unreliable under high contention scenarios. It cannot be - // assumed that the timeout has happened on the timer thread callback. Check the timeout - // synchrnously and then call OnTimeoutSync to force an atomic change of state. - if (TimeoutHasExpired) - { - OnTimeoutSync(asyncClose: true); - } + private SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async) + { + SNINativeMethodWrapper.ConsumerInfo myInfo = new SNINativeMethodWrapper.ConsumerInfo(); - // try to change to the stopped state but only do so if currently in the running state - // and use cmpexch so that all changes out of the running state are atomic - int previousState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Stopped, TimeoutState.Running); + Debug.Assert(_outBuff.Length == _inBuff.Length, "Unexpected unequal buffers."); - // if the state is anything other than running then this query has reached an end so - // set the correlation _timeoutIdentityValue to 0 to prevent late callbacks executing - if (_timeoutState != TimeoutState.Running) - { - _timeoutIdentityValue = 0; - } + myInfo.defaultBufferSize = _outBuff.Length; // Obtain packet size from outBuff size. - ProcessSniPacket(packet, error); - } - catch (Exception e) - { - processFinallyBlock = ADP.IsCatchableExceptionType(e); - throw; - } - finally + if (async) { - // pendingCallbacks may be 2 after decrementing, this indicates that a fatal timeout is occuring, and therefore we shouldn't complete the task - int pendingCallbacks = DecrementPendingCallbacks(false); // may dispose of GC handle. - if ((processFinallyBlock) && (source != null) && (pendingCallbacks < 2)) - { - if (error == 0) - { - if (_executionContext != null) - { - ExecutionContext.Run(_executionContext, (state) => source.TrySetResult(null), null); - } - else - { - source.TrySetResult(null); - } - } - else - { - if (_executionContext != null) - { - ExecutionContext.Run(_executionContext, (state) => ReadAsyncCallbackCaptureException(source), null); - } - else - { - ReadAsyncCallbackCaptureException(source); - } - } - } - - AssertValidState(); + myInfo.readDelegate = SNILoadHandle.SingletonInstance.ReadAsyncCallbackDispatcher; + myInfo.writeDelegate = SNILoadHandle.SingletonInstance.WriteAsyncCallbackDispatcher; + _gcHandle = GCHandle.Alloc(this, GCHandleType.Normal); + myInfo.key = (IntPtr)_gcHandle; } + return myInfo; } - private void ReadAsyncCallbackCaptureException(TaskCompletionSource source) + internal void CreatePhysicalSNIHandle( + string serverName, + bool ignoreSniOpenTimeout, + long timerExpire, + out byte[] instanceName, + byte[] spnBuffer, + bool flushCache, + bool async, + bool fParallel, + TransparentNetworkResolutionState transparentNetworkResolutionState, + int totalTimeout, + SqlConnectionIPAddressPreference ipPreference, + string cachedFQDN, + string hostNameInCertificate = "") { - bool captureSuccess = false; - try + SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); + + // Translate to SNI timeout values (Int32 milliseconds) + long timeout; + if (long.MaxValue == timerExpire) { - if (_hasErrorOrWarning) - { - // Do the close on another thread, since we don't want to block the callback thread - ThrowExceptionAndWarning(asyncClose: true); - } - else if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) - { - // Connection was closed by another thread before we parsed the packet, so no error was added to the collection - throw ADP.ClosedConnectionError(); - } + timeout = int.MaxValue; } - catch (Exception ex) + else { - if (source.TrySetException(ex)) + timeout = ADP.TimerRemainingMilliseconds(timerExpire); + if (timeout > int.MaxValue) + { + timeout = int.MaxValue; + } + else if (0 > timeout) { - // There was an exception, and it was successfully stored in the task - captureSuccess = true; + timeout = 0; } } - if (!captureSuccess) - { - // Either there was no exception, or the task was already completed - // This is unusual, but possible if a fatal timeout occurred on another thread (which should mean that the connection is now broken) - Debug.Assert(_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed || _parser.Connection.IsConnectionDoomed, "Failed to capture exception while the connection was still healthy"); + // serverName : serverInfo.ExtendedServerName + // may not use this serverName as key - // The safest thing to do is to ensure that the connection is broken and attempt to cancel the task - // This must be done from another thread to not block the callback thread - Task.Factory.StartNew(() => - { - _parser.State = TdsParserState.Broken; - _parser.Connection.BreakConnection(); - source.TrySetCanceled(); - }); - } + _ = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out SQLDNSInfo cachedDNSInfo); + + _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer, ignoreSniOpenTimeout, checked((int)timeout), + out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout, + ipPreference, cachedDNSInfo, hostNameInCertificate); } -#pragma warning disable 420 // a reference to a volatile field will not be treated as volatile + internal void Dispose() + { - public void WriteAsyncCallback(IntPtr key, IntPtr packet, uint sniError) - { // Key never used. - RemovePacketFromPendingList(packet); - try + SafeHandle packetHandle = _sniPacket; + SafeHandle sessionHandle = _sessionHandle; + SafeHandle asyncAttnPacket = _sniAsyncAttnPacket; + _sniPacket = null; + _sessionHandle = null; + _sniAsyncAttnPacket = null; + + DisposeCounters(); + + if (null != sessionHandle || null != packetHandle) { - if (sniError != TdsEnums.SNI_SUCCESS) + // Comment CloseMARSSession + // UNDONE - if there are pending reads or writes on logical connections, we need to block + // here for the callbacks!!! This only applies to async. Should be fixed by async fixes for + // AD unload/exit. + + // TODO: Make this a BID trace point! + RuntimeHelpers.PrepareConstrainedRegions(); + try + { } + finally { - SqlClientEventSource.Log.TryTraceEvent(" write async returned error code {0}", (int)sniError); - try + if (packetHandle != null) { - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(asyncClose: true); + packetHandle.Dispose(); } - catch (Exception e) + if (asyncAttnPacket != null) { - TaskCompletionSource writeCompletionSource = _writeCompletionSource; - if (writeCompletionSource != null) - { - writeCompletionSource.TrySetException(e); - } - else - { - _delayedWriteAsyncCallbackException = e; - - // Ensure that _delayedWriteAsyncCallbackException is set before checking _writeCompletionSource - Thread.MemoryBarrier(); - - // Double check that _writeCompletionSource hasn't been created in the meantime - writeCompletionSource = _writeCompletionSource; - if (writeCompletionSource != null) - { - Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - writeCompletionSource.TrySetException(delayedException); - } - } - } - - return; + asyncAttnPacket.Dispose(); + } + if (sessionHandle != null) + { + sessionHandle.Dispose(); + DecrementPendingCallbacks(true); // Will dispose of GC handle. } - } - else - { - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; } } - finally + + if (_writePacketCache != null) { -#if DEBUG - if (SqlCommand.DebugForceAsyncWriteDelay > 0) + lock (_writePacketLockObject) { - new Timer(obj => + RuntimeHelpers.PrepareConstrainedRegions(); + try + { } + finally { - Interlocked.Decrement(ref _asyncWriteCount); - TaskCompletionSource writeCompletionSource = _writeCompletionSource; - if (_asyncWriteCount == 0 && writeCompletionSource != null) - { - writeCompletionSource.TrySetResult(null); - } - }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite); - } - else - { -#else - { -#endif - Interlocked.Decrement(ref _asyncWriteCount); + _writePacketCache.Dispose(); + // Do not set _writePacketCache to null, just in case a WriteAsyncCallback completes after this point + } } } -#if DEBUG - if (SqlCommand.DebugForceAsyncWriteDelay > 0) - { - return; - } -#endif - TaskCompletionSource completionSource = _writeCompletionSource; - if (_asyncWriteCount == 0 && completionSource != null) - { - completionSource.TrySetResult(null); - } } -#pragma warning restore 420 - ///////////////////////////////////////// - // Network/Packet Writing & Processing // + // Network/Packet Reading & Processing // ///////////////////////////////////////// - - // - // Takes a secure string and offsets and saves them for a write latter when the information is written out to SNI Packet - // This method is provided to better handle the life cycle of the clear text of the secure string - // This method also ensures that the clear text is not held in the unpined managed buffer so that it avoids getting moved around by CLR garbage collector - // TdsParserStaticMethods.EncryptPassword operation is also done in the unmanaged buffer for the clear text later - // - internal void WriteSecureString(SecureString secureString) + internal void SetSnapshot() { - TdsParser.ReliabilitySection.Assert("unreliable call to WriteSecureString"); // you need to setup for a thread abort somewhere before you call this method - - Debug.Assert(_securePasswords[0] == null || _securePasswords[1] == null, "There are more than two secure passwords"); - - int index = _securePasswords[0] != null ? 1 : 0; - - _securePasswords[index] = secureString; - _securePasswordOffsetsInBuffer[index] = _outBytesUsed; - - // loop through and write the entire array - int lengthInBytes = secureString.Length * 2; - - // It is guaranteed both secure password and secure change password should fit into the first packet - // Given current TDS format and implementation it is not possible that one of secure string is the last item and exactly fill up the output buffer - // if this ever happens and it is correct situation, the packet needs to be written out after _outBytesUsed is update - Debug.Assert((_outBytesUsed + lengthInBytes) < _outBuff.Length, "Passwords cannot be split into two different packet or the last item which fully fill up _outBuff!!!"); + _snapshot = new StateSnapshot(this); + _snapshot.Snap(); + _snapshotReplay = false; + } - _outBytesUsed += lengthInBytes; + internal void ResetSnapshot() + { + _snapshot = null; + _snapshotReplay = false; } - // ResetSecurePasswordsInformation: clears information regarding secure passwords when login is done; called from TdsParser.TdsLogin - internal void ResetSecurePasswordsInfomation() + private IntPtr ReadSyncOverAsync(int timeout, out uint error) { - for (int i = 0; i < _securePasswords.Length; ++i) + SNIHandle handle = Handle; + if (handle == null) { - _securePasswords[i] = null; - _securePasswordOffsetsInBuffer[i] = 0; + throw ADP.ClosedConnectionError(); } + IntPtr readPacket = IntPtr.Zero; + error = SNINativeMethodWrapper.SNIReadSyncOverAsync(handle, ref readPacket, timeout); + return readPacket; } - internal Task WaitForAccumulatedWrites() + private void CreateSessionHandle(TdsParserStateObject physicalConnection, bool async) { - // Checked for stored exceptions -#pragma warning disable 420 // A reference to a volatile field will not be treated as volatile - Disabling since the Interlocked APIs are volatile aware - Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } -#pragma warning restore 420 + SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); - if (_asyncWriteCount == 0) - { - return null; - } + SQLDNSInfo cachedDNSInfo; + SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCache, out cachedDNSInfo); - _writeCompletionSource = new TaskCompletionSource(); - Task task = _writeCompletionSource.Task; + _sessionHandle = new SNIHandle(myInfo, physicalConnection.Handle, _parser.Connection.ConnectionOptions.IPAddressPreference, cachedDNSInfo); + } - // Ensure that _writeCompletionSource is set before checking state - Thread.MemoryBarrier(); + private bool IsFailedHandle() => _sessionHandle.Status != TdsEnums.SNI_SUCCESS; - // Now that we have set _writeCompletionSource, check if parser is closed or broken - if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) - { - throw ADP.ClosedConnectionError(); - } + private bool IsPacketEmpty(IntPtr packet) => packet == IntPtr.Zero; - // Check for stored exceptions -#pragma warning disable 420 // A reference to a volatile field will not be treated as volatile - Disabling since the Interlocked APIs are volatile aware - delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } -#pragma warning restore 420 + private bool IsValidPacket(IntPtr packet) => packet != IntPtr.Zero; - // If there are no outstanding writes, see if we can shortcut and return null - if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null))) - { - task = null; - } + private bool CheckPacket(IntPtr packet, TaskCompletionSource source) => IntPtr.Zero == packet || IntPtr.Zero != packet && source != null; - return task; + private void ReleasePacket(IntPtr packet) => SNINativeMethodWrapper.SNIPacketRelease(packet); + + private IntPtr ReadAsync(SessionHandle handle, out uint error) + { + IntPtr readPacket = IntPtr.Zero; + error = SNINativeMethodWrapper.SNIReadAsync(handle.NativeHandle, ref readPacket); + return readPacket; } - // Takes in a single byte and writes it to the buffer. If the buffer is full, it is flushed - // and then the buffer is re-initialized in flush() and then the byte is put in the buffer. - internal void WriteByte(byte b) + private uint CheckConnection() { - TdsParser.ReliabilitySection.Assert("unreliable call to WriteByte"); // you need to setup for a thread abort somewhere before you call this method + SNIHandle handle = Handle; + return handle == null ? TdsEnums.SNI_SUCCESS : SNINativeMethodWrapper.SNICheckConnection(handle); + } - Debug.Assert(_outBytesUsed <= _outBuff.Length, "ERROR - TDSParser: _outBytesUsed > _outBuff.Length"); + private uint SNIPacketGetData(IntPtr packet, byte[] _inBuff, ref uint dataSize) + { + return SNINativeMethodWrapper.SNIPacketGetData(packet, _inBuff, ref dataSize); + } - // check to make sure we haven't used the full amount of space available in the buffer, if so, flush it - if (_outBytesUsed == _outBuff.Length) + private void FreeGcHandle(int remaining, bool release) + { + if ((0 == remaining || release) && _gcHandle.IsAllocated) { - WritePacket(TdsEnums.SOFTFLUSH, canAccumulate: true); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, FREEING HANDLE!", ObjectID); + _gcHandle.Free(); } - // set byte in buffer and increment the counter for number of bytes used in the out buffer - _outBuff[_outBytesUsed++] = b; } + ///////////////////////////////////////// + // Network/Packet Writing & Processing // + ///////////////////////////////////////// + // // Takes a byte array and writes it to the buffer. // @@ -2584,7 +562,7 @@ private Task SNIWritePacket(SNIHandle handle, SNIPacket packet, out uint sniErro #if DEBUG else if (!sync && !canAccumulate && SqlCommand.DebugForceAsyncWriteDelay > 0) { - // Executed synchronously - callback will not be called + // Executed synchronously - callback will not be called TaskCompletionSource completion = new TaskCompletionSource(); uint error = sniError; new Timer(obj => @@ -2645,7 +623,7 @@ private Task SNIWritePacket(SNIHandle handle, SNIPacket packet, out uint sniErro return task; } -#pragma warning restore 420 +#pragma warning restore 420 // Sends an attention signal - executing thread will consume attn. internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false) @@ -2694,7 +672,7 @@ internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = fa } uint sniError; - _parser._asyncWrite = false; // stop async write + _parser._asyncWrite = false; // stop async write SNIWritePacket(Handle, attnPacket, out sniError, canAccumulate: false, callerHasConnectionLock: false, asyncClose); SqlClientEventSource.Log.TryTraceEvent(" Send Attention ASync.", "Info"); } @@ -2925,7 +903,6 @@ void AssertValidState() Debug.Assert(_inBytesPacket >= 0, "Packet must not be negative"); } - ////////////////////////////////////////////// // Errors and Warnings // ////////////////////////////////////////////// @@ -3170,7 +1147,7 @@ internal void AssertStateIsClean() Debug.Assert(_asyncWriteCount == 0, "StateObj still has outstanding async writes"); Debug.Assert(_delayedWriteAsyncCallbackException == null, "StateObj has an unobserved exceptions from an async write"); // Attention\Cancellation\Timeouts - Debug.Assert(!_attentionReceived && !_attentionSent && !_attentionSending, $"StateObj is still dealing with attention: Sent: {_attentionSent}, Received: {_attentionReceived}, Sending: {_attentionSending}"); + Debug.Assert(!HasReceivedAttention && !_attentionSent && !_attentionSending, $"StateObj is still dealing with attention: Sent: {_attentionSent}, Received: {HasReceivedAttention}, Sending: {_attentionSending}"); Debug.Assert(!_cancelled, "StateObj still has cancellation set"); Debug.Assert(_timeoutState == TimeoutState.Stopped, "StateObj still has internal timeout set"); // Errors and Warnings @@ -3261,7 +1238,7 @@ class PacketData public byte[] Buffer; public int Read; #if DEBUG - public StackTrace Stack; + public string Stack; #endif } @@ -3350,7 +1327,7 @@ internal void AssertCurrent() { Debug.Assert(_snapshotInBuffCurrent == _snapshotInBuffs.Count, "Should not be reading new packets when not replaying last packet"); } - internal void CheckStack(StackTrace trace) + internal void CheckStack(string trace) { PacketData prev = _snapshotInBuffs[_snapshotInBuffCurrent - 1]; if (prev.Stack == null) @@ -3362,7 +1339,7 @@ internal void CheckStack(StackTrace trace) Debug.Assert(_stateObj._permitReplayStackTraceToDiffer || prev.Stack.ToString() == trace.ToString(), "The stack trace on subsequent replays should be the same"); } } -#endif +#endif internal bool Replay() { @@ -3385,8 +1362,8 @@ internal void Snap() _snapshotInBuffCurrent = 0; _snapshotInBytesUsed = _stateObj._inBytesUsed; _snapshotInBytesPacket = _stateObj._inBytesPacket; - _snapshotPendingData = _stateObj._pendingData; - _snapshotErrorTokenReceived = _stateObj._errorTokenReceived; + _snapshotPendingData = _stateObj.HasPendingData; + _snapshotErrorTokenReceived = _stateObj.HasReceivedError; _snapshotMessageStatus = _stateObj._messageStatus; // _nullBitmapInfo must be cloned before it is updated _snapshotNullBitmapInfo = _stateObj._nullBitmapInfo; @@ -3395,9 +1372,9 @@ internal void Snap() _snapshotCleanupMetaData = _stateObj._cleanupMetaData; // _cleanupAltMetaDataSetArray must be cloned bofore it is updated _snapshotCleanupAltMetaDataSetArray = _stateObj._cleanupAltMetaDataSetArray; - _snapshotHasOpenResult = _stateObj._hasOpenResult; - _snapshotReceivedColumnMetadata = _stateObj._receivedColMetaData; - _snapshotAttentionReceived = _stateObj._attentionReceived; + _snapshotHasOpenResult = _stateObj.HasOpenResult; + _snapshotReceivedColumnMetadata = _stateObj.HasReceivedColumnMetadata; + _snapshotAttentionReceived = _stateObj.HasReceivedAttention; #if DEBUG _rollingPend = 0; _rollingPendCount = 0; @@ -3418,26 +1395,26 @@ internal void ResetSnapshotState() _stateObj._inBytesUsed = _snapshotInBytesUsed; _stateObj._inBytesPacket = _snapshotInBytesPacket; - _stateObj._pendingData = _snapshotPendingData; - _stateObj._errorTokenReceived = _snapshotErrorTokenReceived; + _stateObj.HasPendingData = _snapshotPendingData; + _stateObj.HasReceivedError = _snapshotErrorTokenReceived; _stateObj._messageStatus = _snapshotMessageStatus; _stateObj._nullBitmapInfo = _snapshotNullBitmapInfo; _stateObj._cleanupMetaData = _snapshotCleanupMetaData; _stateObj._cleanupAltMetaDataSetArray = _snapshotCleanupAltMetaDataSetArray; // Make sure to go through the appropriate increment/decrement methods if changing HasOpenResult - if (!_stateObj._hasOpenResult && _snapshotHasOpenResult) + if (!_stateObj.HasOpenResult && _snapshotHasOpenResult) { _stateObj.IncrementAndObtainOpenResultCount(_stateObj._executedUnderTransaction); } - else if (_stateObj._hasOpenResult && !_snapshotHasOpenResult) + else if (_stateObj.HasOpenResult && !_snapshotHasOpenResult) { _stateObj.DecrementOpenResultCount(); } //else _stateObj._hasOpenResult is already == _snapshotHasOpenResult - _stateObj._receivedColMetaData = _snapshotReceivedColumnMetadata; - _stateObj._attentionReceived = _snapshotAttentionReceived; + _stateObj.HasReceivedColumnMetadata = _snapshotReceivedColumnMetadata; + _stateObj.HasReceivedAttention = _snapshotAttentionReceived; // Reset partially read state (these only need to be maintained if doing async without snapshot) _stateObj._bTmpRead = 0; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index 8f48131f8d..3ca1c4d59d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -443,6 +443,29 @@ internal static Exception CreateSqlException(MsalException msalException, SqlCon return SqlException.CreateException(sqlErs, "", sender); } + internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, int dueTime, int period) + { + // Don't capture the current ExecutionContext and its AsyncLocals onto + // a global timer causing them to live forever + bool restoreFlow = false; + try + { + if (!ExecutionContext.IsFlowSuppressed()) + { + ExecutionContext.SuppressFlow(); + restoreFlow = true; + } + + return new Timer(callback, state, dueTime, period); + } + finally + { + // Restore the current ExecutionContext + if (restoreFlow) + ExecutionContext.RestoreFlow(); + } + } + #endregion #region CommandBuilder, Command, BulkCopy @@ -1498,29 +1521,6 @@ internal static IntPtr IntPtrOffset(IntPtr pbase, int offset) #endregion #else #region netcore project only - internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, int dueTime, int period) - { - // Don't capture the current ExecutionContext and its AsyncLocals onto - // a global timer causing them to live forever - bool restoreFlow = false; - try - { - if (!ExecutionContext.IsFlowSuppressed()) - { - ExecutionContext.SuppressFlow(); - restoreFlow = true; - } - - return new Timer(callback, state, dueTime, period); - } - finally - { - // Restore the current ExecutionContext - if (restoreFlow) - ExecutionContext.RestoreFlow(); - } - } - // // COM+ exceptions // diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSessionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSessionPool.cs index ed53a831c4..2bf48344a4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSessionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSessionPool.cs @@ -181,11 +181,9 @@ internal void PutSession(TdsParserStateObject session) { // Session is good to re-use and our cache has space SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} keeping session {1} cachedCount={2}", ObjectID, session.ObjectID, _cachedCount); -#if NETFRAMEWORK - Debug.Assert(!session._pendingData, "pending data on a pooled session?"); -#else + Debug.Assert(!session.HasPendingData, "pending data on a pooled session?"); -#endif + _freeStateObjects[_freeStateObjectCount] = session; _freeStateObjectCount++; } 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 62c02e4db7..15afaa0a0c 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,10 @@ namespace Microsoft.Data.SqlClient { +#if NETFRAMEWORK + using PacketHandle = IntPtr; +#endif + sealed internal class LastIOTimer { internal long _value; @@ -20,6 +24,8 @@ sealed internal class LastIOTimer partial class TdsParserStateObject { + private static readonly ContextCallback s_readAsyncCallbackComplete = ReadAsyncCallbackComplete; + private static int s_objectTypeCount; // EventSource counter internal readonly int _objectID = Interlocked.Increment(ref s_objectTypeCount); @@ -133,7 +139,7 @@ public TimeoutState(int value) internal volatile bool _attentionSending; private readonly TimerCallback _onTimeoutAsync; - // Below 2 properties are used to enforce timeout delays in code to + // Below 2 properties are used to enforce timeout delays in code to // reproduce issues related to theadpool starvation and timeout delay. // It should always be set to false by default, and only be enabled during testing. internal bool _enforceTimeoutDelay = false; @@ -160,6 +166,7 @@ public TimeoutState(int value) // 3) post session close - no attention is allowed private bool _cancelled; private const int WaitForCancellationLockPollTimeout = 100; + private readonly WeakReference _cancellationOwner = new WeakReference(null); // Cache the transaction for which this command was executed so upon completion we can // decrement the appropriate result count. @@ -269,11 +276,8 @@ public TimeoutState(int value) internal static bool s_forcePendingReadsToWaitForUser = false; internal TaskCompletionSource _realNetworkPacketTaskSource; - // Field is never assigned to, and will always have its default value -#pragma warning disable 0649 // Set to true to enable checking the call stacks match when packet retry occurs. internal static bool s_checkNetworkPacketRetryStacks = false; -#pragma warning restore 0649 #endif ////////////////// @@ -297,6 +301,38 @@ internal TdsParserStateObject(TdsParser parser) _lastSuccessfulIOTimer = new LastIOTimer(); } + internal TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async) + { + // Construct a MARS session + Debug.Assert(null != parser, "no parser?"); + _parser = parser; + _onTimeoutAsync = OnTimeoutAsync; + SniContext = SniContext.Snix_GetMarsSession; + + Debug.Assert(null != _parser._physicalStateObj, "no physical session?"); + Debug.Assert(null != _parser._physicalStateObj._inBuff, "no in buffer?"); + Debug.Assert(null != _parser._physicalStateObj._outBuff, "no out buffer?"); + Debug.Assert(_parser._physicalStateObj._outBuff.Length == + _parser._physicalStateObj._inBuff.Length, "Unexpected unequal buffers."); + + // Determine packet size based on physical connection buffer lengths. + SetPacketSize(_parser._physicalStateObj._outBuff.Length); + + CreateSessionHandle(physicalConnection, async); + + if (IsFailedHandle()) + { + AddError(parser.ProcessSNIError(this)); + ThrowExceptionAndWarning(); + } + + // we post a callback that represents the call to dispose; once the + // object is disposed, the next callback will cause the GC Handle to + // be released. + IncrementPendingCallbacks(); + _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer; + } + //////////////// // Properties // //////////////// @@ -400,7 +436,7 @@ internal int GetTimeoutRemaining() int remaining; if (0 != _timeoutMilliseconds) { - remaining = (int)Math.Min((long)int.MaxValue, _timeoutMilliseconds); + remaining = (int)Math.Min(int.MaxValue, _timeoutMilliseconds); _timeoutTime = TdsParserStaticMethods.GetTimeout(_timeoutMilliseconds); _timeoutMilliseconds = 0; } @@ -511,11 +547,40 @@ internal bool IsNullCompressionBitSet(int columnOrdinal) return _nullBitmapInfo.IsGuaranteedNull(columnOrdinal); } - private partial struct NullBitmap + private struct NullBitmap { private byte[] _nullBitmap; private int _columnsCount; // set to 0 if not used or > 0 for NBC rows + internal bool TryInitialize(TdsParserStateObject stateObj, int columnsCount) + { + _columnsCount = columnsCount; + // 1-8 columns need 1 byte + // 9-16: 2 bytes, and so on + int bitmapArrayLength = (columnsCount + 7) / 8; + + // allow reuse of previously allocated bitmap + if (_nullBitmap == null || _nullBitmap.Length != bitmapArrayLength) + { + _nullBitmap = new byte[bitmapArrayLength]; + } + + // read the null bitmap compression information from TDS + if (!stateObj.TryReadByteArray(_nullBitmap, _nullBitmap.Length)) + { + return false; + } + + SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.NullBitmap.Initialize | INFO | ADV | State Object Id {0}, NBCROW bitmap received, column count = {1}", stateObj._objectID, columnsCount); +#if NETFRAMEWORK + SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParserStateObject.NullBitmap.Initialize | INFO | ADV | State Object Id {0}, NBCROW bitmap data. Null Bitmap {1}, Null bitmap length: {2}", stateObj.ObjectID, _nullBitmap, (ushort)_nullBitmap.Length); +#else + SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParserStateObject.NullBitmap.Initialize | INFO | ADV | State Object Id {0}, Null Bitmap length {1}, NBCROW bitmap data: {2}", stateObj._objectID, (ushort)_nullBitmap.Length, _nullBitmap); +#endif + + return true; + } + internal bool ReferenceEquals(NullBitmap obj) { return object.ReferenceEquals(_nullBitmap, obj._nullBitmap); @@ -557,7 +622,6 @@ internal bool IsGuaranteedNull(int columnOrdinal) } } - ///////////////////// // General methods // ///////////////////// @@ -571,6 +635,70 @@ internal void Activate(object owner) Debug.Assert(result == 1, "invalid deactivate count"); } + // This method is only called by the command or datareader as a result of a user initiated + // cancel request. + internal void Cancel(object caller) + { + Debug.Assert(caller != null, "Null caller for Cancel!"); + Debug.Assert(caller is SqlCommand || caller is SqlDataReader, "Calling API with invalid caller type: " + caller.GetType()); + + bool hasLock = false; + try + { + // Keep looping until we either grabbed the lock (and therefore sent attention) or the connection closes\breaks + while ((!hasLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) + { + Monitor.TryEnter(this, WaitForCancellationLockPollTimeout, ref hasLock); + if (hasLock) + { // Lock for the time being - since we need to synchronize the attention send. + // This lock is also protecting against concurrent close and async continuations + + // Ensure that, once we have the lock, that we are still the owner + if ((!_cancelled) && (_cancellationOwner.Target == caller)) + { + _cancelled = true; + + if (HasPendingData && !_attentionSent) + { + bool hasParserLock = false; + // Keep looping until we have the parser lock (and so are allowed to write), or the connection closes\breaks + while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) + { + try + { + _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: WaitForCancellationLockPollTimeout, lockTaken: ref hasParserLock); + if (hasParserLock) + { + _parser.Connection.ThreadHasParserLockForClose = true; + SendAttention(); + } + } + finally + { + if (hasParserLock) + { + if (_parser.Connection.ThreadHasParserLockForClose) + { + _parser.Connection.ThreadHasParserLockForClose = false; + } + _parser.Connection._parserLock.Release(); + } + } + } + } + } + } + } + } + finally + { + if (hasLock) + { + Monitor.Exit(this); + } + } + } + // CancelRequest - use to cancel while writing a request to the server // // o none of the request might have been sent to the server, simply reset the buffer, @@ -647,6 +775,44 @@ internal void CloseSession() Parser.PutSession(this); } + private void ResetCancelAndProcessAttention() + { + // This method is shared by CloseSession initiated by DataReader.Close or completed + // command execution, as well as the session reclamation code for cases where the + // DataReader is opened and then GC'ed. + lock (this) + { + // Reset cancel state. + _cancelled = false; + _cancellationOwner.Target = null; + + if (_attentionSent) + { + // Make sure we're cleaning up the AttentionAck if Cancel happened before taking the lock. + // We serialize Cancel/CloseSession to prevent a race condition between these two states. + // The problem is that both sending and receiving attentions are time taking + // operations. +#if NETFRAMEWORK && DEBUG + TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); + try + { + tdsReliabilitySection.Start(); +#endif + Parser.ProcessPendingAck(this); +#if NETFRAMEWORK && DEBUG + } + finally + { + tdsReliabilitySection.Stop(); + } +#endif + } + SetTimeoutStateStopped(); + } + } + internal bool Deactivate() { bool goodForReuse = false; @@ -712,6 +878,26 @@ internal void DecrementOpenResultCount() HasOpenResult = false; } +#if NETFRAMEWORK + [System.Runtime.ConstrainedExecution.ReliabilityContract( + System.Runtime.ConstrainedExecution.Consistency.WillNotCorruptState, + System.Runtime.ConstrainedExecution.Cer.Success)] +#endif + internal int DecrementPendingCallbacks(bool release) + { + int remaining = Interlocked.Decrement(ref _pendingCallbacks); +#if NETFRAMEWORK + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, after decrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks); +#else + SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.DecrementPendingCallbacks | ADV | State Object Id {0}, after decrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); +#endif + FreeGcHandle(remaining, release); + // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed + // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it + Debug.Assert((remaining == -1 && SessionHandle.IsNull) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}"); + return remaining; + } + internal void DisposeCounters() { Timer networkPacketTimeout = _networkPacketTimeout; @@ -735,6 +921,23 @@ internal void DisposeCounters() } } +#if NETFRAMEWORK + [System.Runtime.ConstrainedExecution.ReliabilityContract( + System.Runtime.ConstrainedExecution.Consistency.WillNotCorruptState, + System.Runtime.ConstrainedExecution.Cer.Success)] +#endif + internal int IncrementPendingCallbacks() + { + int remaining = Interlocked.Increment(ref _pendingCallbacks); +#if NETFRAMEWORK + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, after incrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks); +#else + SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.IncrementPendingCallbacks | ADV | State Object Id {0}, after incrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); +#endif + Debug.Assert(0 < remaining && remaining <= 3, $"_pendingCallbacks values is invalid after incrementing: {remaining}"); + return remaining; + } + internal int IncrementAndObtainOpenResultCount(SqlInternalTransaction transaction) { HasOpenResult = true; @@ -756,7 +959,7 @@ internal int IncrementAndObtainOpenResultCount(SqlInternalTransaction transactio internal void SetTimeoutSeconds(int timeout) { - SetTimeoutMilliseconds((long)timeout * 1000L); + SetTimeoutMilliseconds(timeout * 1000L); } internal void SetTimeoutMilliseconds(long timeout) @@ -774,6 +977,11 @@ internal void SetTimeoutMilliseconds(long timeout) } } + internal void StartSession(object cancellationOwner) + { + _cancellationOwner.Target = cancellationOwner; + } + internal void ThrowExceptionAndWarning(bool callerHasConnectionLock = false, bool asyncClose = false) { _parser.ThrowExceptionAndWarning(this, callerHasConnectionLock, asyncClose); @@ -844,8 +1052,8 @@ internal bool TryProcessHeader() { // All read _partialHeaderBytesRead = 0; - _inBytesPacket = ((int)_partialHeaderBuffer[TdsEnums.HEADER_LEN_FIELD_OFFSET] << 8 | - (int)_partialHeaderBuffer[TdsEnums.HEADER_LEN_FIELD_OFFSET + 1]) - _inputHeaderLen; + _inBytesPacket = (_partialHeaderBuffer[TdsEnums.HEADER_LEN_FIELD_OFFSET] << 8 | + _partialHeaderBuffer[TdsEnums.HEADER_LEN_FIELD_OFFSET + 1]) - _inputHeaderLen; _messageStatus = _partialHeaderBuffer[1]; _spid = _partialHeaderBuffer[TdsEnums.SPID_OFFSET] << 8 | @@ -1064,6 +1272,1940 @@ internal bool SetPacketSize(int size) return false; } + + /////////////////////////////////////// + // Buffer read methods - data values // + /////////////////////////////////////// + + // look at the next byte without pulling it off the wire, don't just return _inBytesUsed since we may + // have to go to the network to get the next byte. + internal bool TryPeekByte(out byte value) + { + if (!TryReadByte(out value)) + { + return false; + } + + // now do fixup + _inBytesPacket++; + _inBytesUsed--; + + AssertValidState(); + return true; + } + + // Takes a byte array, an offset, and a len and fills the array from the offset to len number of + // bytes from the in buffer. + public bool TryReadByteArray(Span buff, int len) + { + return TryReadByteArray(buff, len, out _); + } + + // NOTE: This method must be retriable WITHOUT replaying a snapshot + // Every time you call this method increment the offset and decrease len by the value of totalRead + public bool TryReadByteArray(Span buff, int len, out int totalRead) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to ReadByteArray"); // you need to setup for a thread abort somewhere before you call this method +#endif + + totalRead = 0; + +#if DEBUG + if (_snapshot != null && _snapshot.DoPend()) + { + _networkPacketTaskSource = new TaskCompletionSource(); + Interlocked.MemoryBarrier(); + + if (s_forcePendingReadsToWaitForUser) + { + _realNetworkPacketTaskSource = new TaskCompletionSource(); + _realNetworkPacketTaskSource.SetResult(null); + } + else + { + _networkPacketTaskSource.TrySetResult(null); + } + return false; + } +#endif + + Debug.Assert(buff == null || buff.Length >= len, "Invalid length sent to ReadByteArray()!"); + + // loop through and read up to array length + while (len > 0) + { + if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) + { + if (!TryPrepareBuffer()) + { + return false; + } + } + + int bytesToRead = Math.Min(len, Math.Min(_inBytesPacket, _inBytesRead - _inBytesUsed)); + Debug.Assert(bytesToRead > 0, "0 byte read in TryReadByteArray"); + if (!buff.IsEmpty) + { + ReadOnlySpan copyFrom = new ReadOnlySpan(_inBuff, _inBytesUsed, bytesToRead); + Span copyTo = buff.Slice(totalRead, bytesToRead); + copyFrom.CopyTo(copyTo); + } + + totalRead += bytesToRead; + _inBytesUsed += bytesToRead; + _inBytesPacket -= bytesToRead; + len -= bytesToRead; + + AssertValidState(); + } + + return true; + } + + // Takes no arguments and returns a byte from the buffer. If the buffer is empty, it is filled + // before the byte is returned. + internal bool TryReadByte(out byte value) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to ReadByte"); // you need to setup for a thread abort somewhere before you call this method +#endif + + Debug.Assert(_inBytesUsed >= 0 && _inBytesUsed <= _inBytesRead, "ERROR - TDSParser: _inBytesUsed < 0 or _inBytesUsed > _inBytesRead"); + value = 0; + +#if DEBUG + if (_snapshot != null && _snapshot.DoPend()) + { + _networkPacketTaskSource = new TaskCompletionSource(); + Interlocked.MemoryBarrier(); + + if (s_forcePendingReadsToWaitForUser) + { + _realNetworkPacketTaskSource = new TaskCompletionSource(); + _realNetworkPacketTaskSource.SetResult(null); + } + else + { + _networkPacketTaskSource.TrySetResult(null); + } + return false; + } +#endif + + if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) + { + if (!TryPrepareBuffer()) + { + return false; + } + } + + // decrement the number of bytes left in the packet + _inBytesPacket--; + + Debug.Assert(_inBytesPacket >= 0, "ERROR - TDSParser: _inBytesPacket < 0"); + + // return the byte from the buffer and increment the counter for number of bytes used in the in buffer + value = (_inBuff[_inBytesUsed++]); + + AssertValidState(); + return true; + } + + internal bool TryReadChar(out char value) + { + Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); + + Span buffer = stackalloc byte[2]; + if (((_inBytesUsed + 2) > _inBytesRead) || (_inBytesPacket < 2)) + { + // If the char isn't fully in the buffer, or if it isn't fully in the packet, + // then use ReadByteArray since the logic is there to take care of that. + if (!TryReadByteArray(buffer, 2)) + { + value = '\0'; + return false; + } + } + else + { + // The entire char is in the packet and in the buffer, so just return it + // and take care of the counters. + buffer = _inBuff.AsSpan(_inBytesUsed, 2); + _inBytesUsed += 2; + _inBytesPacket -= 2; + } + + AssertValidState(); + value = (char)((buffer[1] << 8) + buffer[0]); + return true; + } + + internal bool TryReadInt16(out short value) + { + Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); + + Span buffer = stackalloc byte[2]; + if (((_inBytesUsed + 2) > _inBytesRead) || (_inBytesPacket < 2)) + { + // If the int16 isn't fully in the buffer, or if it isn't fully in the packet, + // then use ReadByteArray since the logic is there to take care of that. + if (!TryReadByteArray(buffer, 2)) + { + value = default; + return false; + } + } + else + { + // The entire int16 is in the packet and in the buffer, so just return it + // and take care of the counters. + buffer = _inBuff.AsSpan(_inBytesUsed, 2); + _inBytesUsed += 2; + _inBytesPacket -= 2; + } + + AssertValidState(); + value = (short)((buffer[1] << 8) + buffer[0]); + return true; + } + + internal bool TryReadInt32(out int value) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to ReadInt32"); // you need to setup for a thread abort somewhere before you call this method +#endif + + Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); + Span buffer = stackalloc byte[4]; + if (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4)) + { + // If the int isn't fully in the buffer, or if it isn't fully in the packet, + // then use ReadByteArray since the logic is there to take care of that. + if (!TryReadByteArray(buffer, 4)) + { + value = 0; + return false; + } + } + else + { + // The entire int is in the packet and in the buffer, so just return it + // and take care of the counters. + buffer = _inBuff.AsSpan(_inBytesUsed, 4); + _inBytesUsed += 4; + _inBytesPacket -= 4; + } + + AssertValidState(); + value = (buffer[3] << 24) + (buffer[2] << 16) + (buffer[1] << 8) + buffer[0]; + return true; + + } + + // This method is safe to call when doing async without snapshot + internal bool TryReadInt64(out long value) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to ReadInt64"); // you need to setup for a thread abort somewhere before you call this method +#endif + + if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) + { + if (!TryPrepareBuffer()) + { + value = 0; + return false; + } + } + + if ((_bTmpRead > 0) || (((_inBytesUsed + 8) > _inBytesRead) || (_inBytesPacket < 8))) + { + // If the long isn't fully in the buffer, or if it isn't fully in the packet, + // then use ReadByteArray since the logic is there to take care of that. + + int bytesRead; + if (!TryReadByteArray(_bTmp.AsSpan(_bTmpRead), 8 - _bTmpRead, out bytesRead)) + { + Debug.Assert(_bTmpRead + bytesRead <= 8, "Read more data than required"); + _bTmpRead += bytesRead; + value = 0; + return false; + } + else + { + Debug.Assert(_bTmpRead + bytesRead == 8, "TryReadByteArray returned true without reading all data required"); + _bTmpRead = 0; + AssertValidState(); + value = BitConverter.ToInt64(_bTmp, 0); + return true; + } + } + else + { + // The entire long is in the packet and in the buffer, so just return it + // and take care of the counters. + + value = BitConverter.ToInt64(_inBuff, _inBytesUsed); + + _inBytesUsed += 8; + _inBytesPacket -= 8; + + AssertValidState(); + return true; + } + } + + internal bool TryReadUInt16(out ushort value) + { + Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); + + Span buffer = stackalloc byte[2]; + if (((_inBytesUsed + 2) > _inBytesRead) || (_inBytesPacket < 2)) + { + // If the uint16 isn't fully in the buffer, or if it isn't fully in the packet, + // then use ReadByteArray since the logic is there to take care of that. + if (!TryReadByteArray(buffer, 2)) + { + value = default; + return false; + } + } + else + { + // The entire uint16 is in the packet and in the buffer, so just return it + // and take care of the counters. + buffer = _inBuff.AsSpan(_inBytesUsed, 2); + _inBytesUsed += 2; + _inBytesPacket -= 2; + } + + AssertValidState(); + value = (ushort)((buffer[1] << 8) + buffer[0]); + return true; + } + + // This method is safe to call when doing async without replay + internal bool TryReadUInt32(out uint value) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to ReadUInt32"); // you need to setup for a thread abort somewhere before you call this method +#endif + + if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) + { + if (!TryPrepareBuffer()) + { + value = 0; + return false; + } + } + + if ((_bTmpRead > 0) || (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4))) + { + // If the int isn't fully in the buffer, or if it isn't fully in the packet, + // then use ReadByteArray since the logic is there to take care of that. + + int bytesRead; + if (!TryReadByteArray(_bTmp.AsSpan(_bTmpRead), 4 - _bTmpRead, out bytesRead)) + { + Debug.Assert(_bTmpRead + bytesRead <= 4, "Read more data than required"); + _bTmpRead += bytesRead; + value = 0; + return false; + } + else + { + Debug.Assert(_bTmpRead + bytesRead == 4, "TryReadByteArray returned true without reading all data required"); + _bTmpRead = 0; + AssertValidState(); + value = BitConverter.ToUInt32(_bTmp, 0); + return true; + } + } + else + { + // The entire int is in the packet and in the buffer, so just return it + // and take care of the counters. + + value = BitConverter.ToUInt32(_inBuff, _inBytesUsed); + + _inBytesUsed += 4; + _inBytesPacket -= 4; + + AssertValidState(); + return true; + } + } + + internal bool TryReadSingle(out float value) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to ReadSingle"); // you need to setup for a thread abort somewhere before you call this method +#endif + + Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); + if (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4)) + { + // If the float isn't fully in the buffer, or if it isn't fully in the packet, + // then use ReadByteArray since the logic is there to take care of that. + + if (!TryReadByteArray(_bTmp, 4)) + { + value = default; + return false; + } + + AssertValidState(); + value = BitConverter.ToSingle(_bTmp, 0); + return true; + } + else + { + // The entire float is in the packet and in the buffer, so just return it + // and take care of the counters. + + value = BitConverter.ToSingle(_inBuff, _inBytesUsed); + + _inBytesUsed += 4; + _inBytesPacket -= 4; + + AssertValidState(); + return true; + } + } + + internal bool TryReadDouble(out double value) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to ReadDouble"); // you need to setup for a thread abort somewhere before you call this method +#endif + + Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); + if (((_inBytesUsed + 8) > _inBytesRead) || (_inBytesPacket < 8)) + { + // If the double isn't fully in the buffer, or if it isn't fully in the packet, + // then use ReadByteArray since the logic is there to take care of that. + + if (!TryReadByteArray(_bTmp, 8)) + { + value = default; + return false; + } + + AssertValidState(); + value = BitConverter.ToDouble(_bTmp, 0); + return true; + } + else + { + // The entire double is in the packet and in the buffer, so just return it + // and take care of the counters. + + value = BitConverter.ToDouble(_inBuff, _inBytesUsed); + + _inBytesUsed += 8; + _inBytesPacket -= 8; + + AssertValidState(); + return true; + } + } + + internal bool TryReadString(int length, out string value) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to ReadString"); // you need to setup for a thread abort somewhere before you call this method +#endif + + Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); + int cBytes = length << 1; + byte[] buf; + int offset = 0; + + if (((_inBytesUsed + cBytes) > _inBytesRead) || (_inBytesPacket < cBytes)) + { + if (_bTmp == null || _bTmp.Length < cBytes) + { + _bTmp = new byte[cBytes]; + } + + if (!TryReadByteArray(_bTmp, cBytes)) + { + value = null; + return false; + } + + // assign local to point to parser scratch buffer + buf = _bTmp; + + AssertValidState(); + } + else + { + // assign local to point to _inBuff + buf = _inBuff; + offset = _inBytesUsed; + _inBytesUsed += cBytes; + _inBytesPacket -= cBytes; + + AssertValidState(); + } + + value = System.Text.Encoding.Unicode.GetString(buf, offset, cBytes); + return true; + } + internal bool TryReadStringWithEncoding(int length, System.Text.Encoding encoding, bool isPlp, out string value) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to ReadStringWithEncoding"); // you need to setup for a thread abort somewhere before you call this method +#endif + + Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); + + if (null == encoding) + { + // Need to skip the current column before throwing the error - this ensures that the state shared between this and the data reader is consistent when calling DrainData + if (isPlp) + { + if (!_parser.TrySkipPlpValue((ulong)length, this, out _)) + { + value = null; + return false; + } + } + else + { + if (!TrySkipBytes(length)) + { + value = null; + return false; + } + } + + _parser.ThrowUnsupportedCollationEncountered(this); + } + byte[] buf = null; + int offset = 0; + + if (isPlp) + { + if (!TryReadPlpBytes(ref buf, 0, int.MaxValue, out length)) + { + value = null; + return false; + } + + AssertValidState(); + } + else + { + if (((_inBytesUsed + length) > _inBytesRead) || (_inBytesPacket < length)) + { + if (_bTmp == null || _bTmp.Length < length) + { + _bTmp = new byte[length]; + } + + if (!TryReadByteArray(_bTmp, length)) + { + value = null; + return false; + } + + // assign local to point to parser scratch buffer + buf = _bTmp; + + AssertValidState(); + } + else + { + // assign local to point to _inBuff + buf = _inBuff; + offset = _inBytesUsed; + _inBytesUsed += length; + _inBytesPacket -= length; + + AssertValidState(); + } + } + + // BCL optimizes to not use char[] underneath + value = encoding.GetString(buf, offset, length); + return true; + } + + internal ulong ReadPlpLength(bool returnPlpNullIfNull) + { + ulong value; + Debug.Assert(_syncOverAsync, "Should not attempt pends in a synchronous call"); + bool result = TryReadPlpLength(returnPlpNullIfNull, out value); + if (!result) + { + throw SQL.SynchronousCallMayNotPend(); + } + return value; + } + + // Reads the length of either the entire data or the length of the next chunk in a + // partially length prefixed data + // After this call, call ReadPlpBytes/ReadPlpUnicodeChars until the specified length of data + // is consumed. Repeat this until ReadPlpLength returns 0 in order to read the + // entire stream. + // When this function returns 0, it means the data stream is read completely and the + // plp state in the tdsparser is cleaned. + internal bool TryReadPlpLength(bool returnPlpNullIfNull, out ulong lengthLeft) + { + uint chunklen; + // bool firstchunk = false; + bool isNull = false; + + Debug.Assert(_longlenleft == 0, "Out of synch length read request"); + if (_longlen == 0) + { + // First chunk is being read. Find out what type of chunk it is + long value; + if (!TryReadInt64(out value)) + { + lengthLeft = 0; + return false; + } + _longlen = (ulong)value; + // firstchunk = true; + } + + if (_longlen == TdsEnums.SQL_PLP_NULL) + { + _longlen = 0; + _longlenleft = 0; + isNull = true; + } + else + { + // Data is coming in uint chunks, read length of next chunk + if (!TryReadUInt32(out chunklen)) + { + lengthLeft = 0; + return false; + } + if (chunklen == TdsEnums.SQL_PLP_CHUNK_TERMINATOR) + { + _longlenleft = 0; + _longlen = 0; + } + else + { + _longlenleft = chunklen; + } + } + + AssertValidState(); + + if (isNull && returnPlpNullIfNull) + { + lengthLeft = TdsEnums.SQL_PLP_NULL; + return true; + } + + lengthLeft = _longlenleft; + return true; + } + + internal int ReadPlpBytesChunk(byte[] buff, int offset, int len) + { + Debug.Assert(_syncOverAsync, "Should not attempt pends in a synchronous call"); + Debug.Assert(_longlenleft > 0, "Read when no data available"); + + int value; + int bytesToRead = (int)Math.Min(_longlenleft, (ulong)len); + bool result = TryReadByteArray(buff.AsSpan(offset), bytesToRead, out value); + _longlenleft -= (ulong)bytesToRead; + if (!result) + { + throw SQL.SynchronousCallMayNotPend(); + } + return value; + } + + // Reads the requested number of bytes from a plp data stream, or the entire data if + // requested length is -1 or larger than the actual length of data. First call to this method + // should be preceeded by a call to ReadPlpLength or ReadDataLength. + // Returns the actual bytes read. + // NOTE: This method must be retriable WITHOUT replaying a snapshot + // Every time you call this method increment the offset and decrease len by the value of totalBytesRead + internal bool TryReadPlpBytes(ref byte[] buff, int offset, int len, out int totalBytesRead) + { + int bytesRead; + int bytesLeft; + byte[] newbuf; + + if (_longlen == 0) + { + Debug.Assert(_longlenleft == 0); + if (buff == null) + { + buff = Array.Empty(); + } + + AssertValidState(); + totalBytesRead = 0; + return true; // No data + } + + Debug.Assert(_longlen != TdsEnums.SQL_PLP_NULL, "Out of sync plp read request"); + Debug.Assert((buff == null && offset == 0) || (buff.Length >= offset + len), "Invalid length sent to ReadPlpBytes()!"); + + bytesLeft = len; + + // If total length is known up front, allocate the whole buffer in one shot instead of realloc'ing and copying over each time + if (buff == null && _longlen != TdsEnums.SQL_PLP_UNKNOWNLEN) + { +#if !NETFRAMEWORK // This condition should be removed when StateSnapshot has been merged. + if (_snapshot != null) + { + // if there is a snapshot and it contains a stored plp buffer take it + // and try to use it if it is the right length + buff = _snapshot._plpBuffer; + _snapshot._plpBuffer = null; + } + + if ((ulong)(buff?.Length ?? 0) != _longlen) +#endif + { + // if the buffer is null or the wrong length create one to use + buff = new byte[(Math.Min((int)_longlen, len))]; + } + } + + if (_longlenleft == 0) + { + if (!TryReadPlpLength(false, out _)) + { + totalBytesRead = 0; + return false; + } + if (_longlenleft == 0) + { // Data read complete + totalBytesRead = 0; + return true; + } + } + + if (buff == null) + { + buff = new byte[_longlenleft]; + } + + totalBytesRead = 0; + + while (bytesLeft > 0) + { + int bytesToRead = (int)Math.Min(_longlenleft, (ulong)bytesLeft); + if (buff.Length < (offset + bytesToRead)) + { + // Grow the array + newbuf = new byte[offset + bytesToRead]; + Buffer.BlockCopy(buff, 0, newbuf, 0, offset); + buff = newbuf; + } + + bool result = TryReadByteArray(buff.AsSpan(offset), bytesToRead, out bytesRead); + Debug.Assert(bytesRead <= bytesLeft, "Read more bytes than we needed"); + Debug.Assert((ulong)bytesRead <= _longlenleft, "Read more bytes than is available"); + + bytesLeft -= bytesRead; + offset += bytesRead; + totalBytesRead += bytesRead; + _longlenleft -= (ulong)bytesRead; + if (!result) + { +#if !NETFRAMEWORK // This condition should be removed when StateSnapshot has been merged. + if (_snapshot != null) + { + // a partial read has happened so store the target buffer in the snapshot + // so it can be re-used when another packet arrives and we read again + _snapshot._plpBuffer = buff; + } +#endif + return false; + } + + if (_longlenleft == 0) + { + // Read the next chunk or cleanup state if hit the end + if (!TryReadPlpLength(false, out _)) + { +#if !NETFRAMEWORK // This condition should be removed when StateSnapshot has been merged. + if (_snapshot != null) + { + // a partial read has happened so store the target buffer in the snapshot + // so it can be re-used when another packet arrives and we read again + _snapshot._plpBuffer = buff; + } +#endif + return false; + } + } + + AssertValidState(); + + // Catch the point where we read the entire plp data stream and clean up state + if (_longlenleft == 0) // Data read complete + break; + } + return true; + } + + ///////////////////////////////////////// + // Value Skip Logic // + ///////////////////////////////////////// + + // Reads bytes from the buffer but doesn't return them, in effect simply deleting them. + // Does not handle plp fields, need to use SkipPlpBytesValue for those. + // Does not handle null values or NBC bitmask, ensure the value is not null before calling this method + internal bool TrySkipLongBytes(long num) + { + Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); + + while (num > 0) + { + int cbSkip = (int)Math.Min(int.MaxValue, num); + if (!TryReadByteArray(Span.Empty, cbSkip)) + { + return false; + } + num -= cbSkip; + } + + return true; + } + + // Reads bytes from the buffer but doesn't return them, in effect simply deleting them. + internal bool TrySkipBytes(int num) + { + Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); + return TryReadByteArray(Span.Empty, num); + } + + ///////////////////////////////////////// + // Network/Packet Reading & Processing // + ///////////////////////////////////////// + /// +#if DEBUG + private string _lastStack; +#endif + + internal bool TryReadNetworkPacket() + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to TryReadNetworkPacket"); // you need to setup for a thread abort somewhere before you call this method +#endif + +#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 (_snapshot.Replay()) + { +#if DEBUG + if (s_checkNetworkPacketRetryStacks) + { + _snapshot.CheckStack(Environment.StackTrace); + } +#endif +#if NETFRAMEWORK + SqlClientEventSource.Log.TryTraceEvent(" Async packet replay{0}", "INFO"); +#endif + return true; + } +#if DEBUG + else + { + if (s_checkNetworkPacketRetryStacks) + { + _lastStack = Environment.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 _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.PrepareReplay(); + } + + internal void ReadSniSyncOverAsync() + { + if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) + { + throw ADP.ClosedConnectionError(); + } + + PacketHandle readPacket = default; + + uint error; + +#if NETFRAMEWORK + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); +#endif + bool shouldDecrement = false; + try + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to ReadSniSync"); // you need to setup for a thread abort somewhere before you call this method +#endif + 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(); +#if NETFRAMEWORK + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); +#endif + 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; +#if NETFRAMEWORK + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); +#endif + 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; +#if NETFRAMEWORK + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); + try + { } + finally +#endif + { + 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 (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 (!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; +#if NETFRAMEWORK + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); + try +#endif + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to IsConnectionAlive"); // you need to setup for a thread abort somewhere before you call this method + +#endif + SniContext = SniContext.Snix_Connect; + + error = CheckConnection(); + if ((error != TdsEnums.SNI_SUCCESS) && (error != TdsEnums.SNI_WAIT_TIMEOUT)) + { + // Connection is dead +#if NETFRAMEWORK + SqlClientEventSource.Log.TryTraceEvent(" received error {0} on idle connection", (int)error); + +#else + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.IsConnectionAlive | Info | State Object Id {0}, received error {1} on idle connection", _objectID, (int)error); +#endif + 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; + } + } +#if NETFRAMEWORK + finally + { + } +#endif + } + } + + 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 + /// NOTE: This will mark the connection as broken if it is found to be dead + /// + /// True if the connection is still alive, otherwise false + internal bool ValidateSNIConnection() + { + if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed))) + { + return false; + } + + if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value <= CheckConnectionWindow) + { + return true; + } + + uint error = TdsEnums.SNI_SUCCESS; + SniContext = SniContext.Snix_Connect; + try + { + Interlocked.Increment(ref _readingCount); + error = CheckConnection(); + } + finally + { + Interlocked.Decrement(ref _readingCount); + } + return (error == TdsEnums.SNI_SUCCESS) || (error == TdsEnums.SNI_WAIT_TIMEOUT); + } + + // This method should only be called by ReadSni! If not - it may have problems with timeouts! + private void ReadSniError(TdsParserStateObject stateObj, uint error) + { + if (TdsEnums.SNI_WAIT_TIMEOUT == error) + { + Debug.Assert(_syncOverAsync, "Should never reach here with async on!"); + bool fail = false; + + if (IsTimeoutStateExpired) + { // This is now our second timeout - time to give up. + fail = true; + } + else + { + stateObj.SetTimeoutStateStopped(); + Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point."); + AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); + + if (!stateObj._attentionSent) + { + if (stateObj.Parser.State == TdsParserState.OpenLoggedIn) + { + stateObj.SendAttention(mustTakeWriteLock: true); + + PacketHandle syncReadPacket = default; + +#if NETFRAMEWORK + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); +#endif + bool shouldDecrement = false; + try + { + Interlocked.Increment(ref _readingCount); + shouldDecrement = true; + + syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error); + + Interlocked.Decrement(ref _readingCount); + shouldDecrement = false; + + if (TdsEnums.SNI_SUCCESS == error) + { + // We will end up letting the run method deal with the expected done:done_attn token stream. + stateObj.ProcessSniPacket(syncReadPacket, 0); + return; + } + else + { + Debug.Assert(!IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease"); + fail = true; // Subsequent read failed, time to give up. + } + } + finally + { + if (shouldDecrement) + { + Interlocked.Decrement(ref _readingCount); + } + + if (!IsPacketEmpty(syncReadPacket)) + { + ReleasePacket(syncReadPacket); + } + } + } + else + { + if (_parser._loginWithFailover) + { + // For DbMirroring Failover during login, never break the connection, just close the TdsParser + _parser.Disconnect(); + } + else if ((_parser.State == TdsParserState.OpenNotLoggedIn) && (_parser.Connection.ConnectionOptions.MultiSubnetFailover || TransparentNetworkIPResolution)) + { + // For MultiSubnet Failover during login, never break the connection, just close the TdsParser + _parser.Disconnect(); + } + else + fail = true; // We aren't yet logged in - just fail. + } + } + } + + if (fail) + { + _parser.State = TdsParserState.Broken; // We failed subsequent read, we have to quit! + _parser.Connection.BreakConnection(); + } + } + else + { + // Caution: ProcessSNIError always returns a fatal error! + AddError(_parser.ProcessSNIError(stateObj)); + } + ThrowExceptionAndWarning(); + + AssertValidState(); + } + + public void ProcessSniPacket(PacketHandle packet, uint error) + { + if (error != 0) + { + if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) + { + // Do nothing with callback if closed or broken and error not 0 - callback can occur + // after connection has been closed. PROBLEM IN NETLIB - DESIGN FLAW. + return; + } + + AddError(_parser.ProcessSNIError(this)); + AssertValidState(); + } + else + { + uint dataSize = 0; + + uint getDataError = SNIPacketGetData(packet, _inBuff, ref dataSize); + + if (getDataError == TdsEnums.SNI_SUCCESS) + { + if (_inBuff.Length < dataSize) + { + Debug.Assert(true, "Unexpected dataSize on Read"); + throw SQL.InvalidInternalPacketSize(StringsHelper.GetString(Strings.SqlMisc_InvalidArraySizeMessage)); + } + + _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; + _inBytesRead = (int)dataSize; + _inBytesUsed = 0; + + if (_snapshot != null) + { + _snapshot.PushBuffer(_inBuff, _inBytesRead); + if (_snapshotReplay) + { + _snapshot.Replay(); +#if DEBUG + _snapshot.AssertCurrent(); +#endif + } + } + + SniReadStatisticsAndTracing(); + +#if NETFRAMEWORK + SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.ReadNetworkPacketAsyncCallback | INFO | ADV | State Object Id {0}, Packet read. In Buffer {1}, In Bytes Read: {2}", ObjectID, _inBuff, (ushort)_inBytesRead); +#endif + + AssertValidState(); + } + else + { +#if NETFRAMEWORK + throw SQL.ParsingError(ParsingErrorState.ProcessSniPacketFailed); +#else + throw SQL.ParsingError(); +#endif + } + } + } + + private void ChangeNetworkPacketTimeout(int dueTime, int period) + { + Timer networkPacketTimeout = _networkPacketTimeout; + if (networkPacketTimeout != null) + { + try + { + networkPacketTimeout.Change(dueTime, period); + } + catch (ObjectDisposedException) + { + // _networkPacketTimeout is set to null before Disposing, but there is still a slight chance + // that object was disposed after we took a copy + } + } + } + + public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) + { + // Key never used. + // Note - it's possible that when native calls managed that an asynchronous exception + // could occur in the native->managed transition, which would + // have two impacts: + // 1) user event not called + // 2) DecrementPendingCallbacks not called, which would mean this object would be leaked due + // to the outstanding GCRoot until AppDomain.Unload. + // We live with the above for the time being due to the constraints of the current + // reliability infrastructure provided by the CLR. +#if !NETFRAMEWORK + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.ReadAsyncCallback | Info | State Object Id {0}, received error {1} on idle connection", _objectID, (int)error); +#endif + + TaskCompletionSource source = _networkPacketTaskSource; +#if DEBUG + if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null)) + { + source = _realNetworkPacketTaskSource; + } +#endif + + // The mars physical connection can get a callback + // with a packet but no result after the connection is closed. + if (source == null && _parser._pMarsPhysicalConObj == this) + { + return; + } + +#if NETFRAMEWORK + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); +#endif + bool processFinallyBlock = true; + try + { + Debug.Assert(CheckPacket(packet, source) && source != null, "AsyncResult null on callback"); + + if (_parser.MARSOn) + { + // Only take reset lock on MARS and Async. + CheckSetResetConnectionState(error, CallbackType.Read); + } + + ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); + + // The timer thread may be unreliable under high contention scenarios. It cannot be + // assumed that the timeout has happened on the timer thread callback. Check the timeout + // synchrnously and then call OnTimeoutSync to force an atomic change of state. + if (TimeoutHasExpired) + { + OnTimeoutSync(true); + } + + // try to change to the stopped state but only do so if currently in the running state + // and use cmpexch so that all changes out of the running state are atomic + int previousState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Stopped, TimeoutState.Running); + + // if the state is anything other than running then this query has reached an end so + // set the correlation _timeoutIdentityValue to 0 to prevent late callbacks executing + if (_timeoutState != TimeoutState.Running) + { + _timeoutIdentityValue = 0; + } + + ProcessSniPacket(packet, error); + } + catch (Exception e) + { + processFinallyBlock = ADP.IsCatchableExceptionType(e); + throw; + } + finally + { + // pendingCallbacks may be 2 after decrementing, this indicates that a fatal timeout is occurring, and therefore we shouldn't complete the task + int pendingCallbacks = DecrementPendingCallbacks(false); // may dispose of GC handle. + if ((processFinallyBlock) && (source != null) && (pendingCallbacks < 2)) + { + if (error == 0) + { + if (_executionContext != null) + { + ExecutionContext.Run(_executionContext, s_readAsyncCallbackComplete, source); + } + else + { + source.TrySetResult(null); + } + } + else + { + if (_executionContext != null) + { + ExecutionContext.Run(_executionContext, state => ReadAsyncCallbackCaptureException((TaskCompletionSource)state), source); + } + else + { + ReadAsyncCallbackCaptureException(source); + } + } + } + + AssertValidState(); + } + } + + private static void ReadAsyncCallbackComplete(object state) + { + TaskCompletionSource source = (TaskCompletionSource)state; + source.TrySetResult(null); + } + + private void ReadAsyncCallbackCaptureException(TaskCompletionSource source) + { + bool captureSuccess = false; + try + { + if (_hasErrorOrWarning) + { + // Do the close on another thread, since we don't want to block the callback thread + ThrowExceptionAndWarning(asyncClose: true); + } + else if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) + { + // Connection was closed by another thread before we parsed the packet, so no error was added to the collection + throw ADP.ClosedConnectionError(); + } + } + catch (Exception ex) + { + if (source.TrySetException(ex)) + { + // There was an exception, and it was successfully stored in the task + captureSuccess = true; + } + } + + if (!captureSuccess) + { + // Either there was no exception, or the task was already completed + // This is unusual, but possible if a fatal timeout occurred on another thread (which should mean that the connection is now broken) + Debug.Assert(_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed || _parser.Connection.IsConnectionDoomed, "Failed to capture exception while the connection was still healthy"); + + // The safest thing to do is to ensure that the connection is broken and attempt to cancel the task + // This must be done from another thread to not block the callback thread + Task.Factory.StartNew(() => + { + _parser.State = TdsParserState.Broken; + _parser.Connection.BreakConnection(); + source.TrySetCanceled(); + }); + } + } + + public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) + { // Key never used. + RemovePacketFromPendingList(packet); + try + { + if (sniError != TdsEnums.SNI_SUCCESS) + { +#if NETFRAMEWORK + SqlClientEventSource.Log.TryTraceEvent(" write async returned error code {0}", (int)sniError); +#else + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.WriteAsyncCallback | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError); +#endif + try + { + AddError(_parser.ProcessSNIError(this)); + ThrowExceptionAndWarning(asyncClose: true); + } + catch (Exception e) + { + TaskCompletionSource writeCompletionSource = _writeCompletionSource; + if (writeCompletionSource != null) + { + writeCompletionSource.TrySetException(e); + } + else + { + _delayedWriteAsyncCallbackException = e; + + // Ensure that _delayedWriteAsyncCallbackException is set before checking _writeCompletionSource + Interlocked.MemoryBarrier(); + + // Double check that _writeCompletionSource hasn't been created in the meantime + writeCompletionSource = _writeCompletionSource; + if (writeCompletionSource != null) + { + Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); + if (delayedException != null) + { + writeCompletionSource.TrySetException(delayedException); + } + } + } + + return; + } + } + else + { + _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; + } + } + finally + { +#if DEBUG + if (SqlCommand.DebugForceAsyncWriteDelay > 0) + { + new Timer(obj => + { + Interlocked.Decrement(ref _asyncWriteCount); + TaskCompletionSource writeCompletionSource = _writeCompletionSource; + if (_asyncWriteCount == 0 && writeCompletionSource != null) + { + writeCompletionSource.TrySetResult(null); + } + }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite); + } + else + { +#else + { +#endif + Interlocked.Decrement(ref _asyncWriteCount); + } + } +#if DEBUG + if (SqlCommand.DebugForceAsyncWriteDelay > 0) + { + return; + } +#endif + TaskCompletionSource completionSource = _writeCompletionSource; + if (_asyncWriteCount == 0 && completionSource != null) + { + completionSource.TrySetResult(null); + } + } + + ///////////////////////////////////////// + // Network/Packet Writing & Processing // + ///////////////////////////////////////// + internal void WriteSecureString(SecureString secureString) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to WriteSecureString"); // you need to setup for a thread abort somewhere before you call this method +#endif + + Debug.Assert(_securePasswords[0] == null || _securePasswords[1] == null, "There are more than two secure passwords"); + + int index = _securePasswords[0] != null ? 1 : 0; + + _securePasswords[index] = secureString; + _securePasswordOffsetsInBuffer[index] = _outBytesUsed; + + // loop through and write the entire array + int lengthInBytes = secureString.Length * 2; + + // It is guaranteed both secure password and secure change password should fit into the first packet + // Given current TDS format and implementation it is not possible that one of secure string is the last item and exactly fill up the output buffer + // if this ever happens and it is correct situation, the packet needs to be written out after _outBytesUsed is update + Debug.Assert((_outBytesUsed + lengthInBytes) < _outBuff.Length, "Passwords cannot be split into two different packet or the last item which fully fill up _outBuff!!!"); + + _outBytesUsed += lengthInBytes; + } + + internal void ResetSecurePasswordsInformation() + { + for (int i = 0; i < _securePasswords.Length; ++i) + { + _securePasswords[i] = null; + _securePasswordOffsetsInBuffer[i] = 0; + } + } + + internal Task WaitForAccumulatedWrites() + { + // Checked for stored exceptions + Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); + if (delayedException != null) + { + throw delayedException; + } + + if (_asyncWriteCount == 0) + { + return null; + } + + _writeCompletionSource = new TaskCompletionSource(); + Task task = _writeCompletionSource.Task; + + // Ensure that _writeCompletionSource is set before checking state + Interlocked.MemoryBarrier(); + + // Now that we have set _writeCompletionSource, check if parser is closed or broken + if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) + { + throw ADP.ClosedConnectionError(); + } + + // Check for stored exceptions + delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); + if (delayedException != null) + { + throw delayedException; + } + + // If there are no outstanding writes, see if we can shortcut and return null + if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null))) + { + task = null; + } + + return task; + } + + // Takes in a single byte and writes it to the buffer. If the buffer is full, it is flushed + // and then the buffer is re-initialized in flush() and then the byte is put in the buffer. + internal void WriteByte(byte b) + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to WriteByte"); // you need to setup for a thread abort somewhere before you call this method +#endif + + Debug.Assert(_outBytesUsed <= _outBuff.Length, "ERROR - TDSParser: _outBytesUsed > _outBuff.Length"); + + // check to make sure we haven't used the full amount of space available in the buffer, if so, flush it + if (_outBytesUsed == _outBuff.Length) + { + WritePacket(TdsEnums.SOFTFLUSH, canAccumulate: true); + } + // set byte in buffer and increment the counter for number of bytes used in the out buffer + _outBuff[_outBytesUsed++] = b; + } + /* // leave this in. comes handy if you have to do Console.WriteLine style debugging ;)