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 1565a618a2..4c87480d92 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 @@ -3,12 +3,8 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; -using System.Security; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; @@ -24,7 +20,7 @@ internal abstract partial class TdsParserStateObject // Async private StateSnapshot _cachedSnapshot; - private SnapshottedStateFlags _snapshottedState; + ////////////////// // Constructors // @@ -80,33 +76,6 @@ 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; - } - } - ///////////////////// // General methods // ///////////////////// @@ -282,929 +251,48 @@ 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()); + ///////////////////////////////////////// + // Network/Packet Reading & Processing // + ///////////////////////////////////////// -#if DEBUG - if (s_failAsyncPends) + internal void SetSnapshot() + { + StateSnapshot snapshot = _snapshot; + if (snapshot is null) { - throw new InvalidOperationException("Attempted to pend a read when _failAsyncPends test hook was enabled"); + snapshot = Interlocked.Exchange(ref _cachedSnapshot, null) ?? new StateSnapshot(); } - if (s_forceSyncOverAsyncAfterFirstPend) + else { - _syncOverAsync = true; + snapshot.Clear(); } -#endif - Debug.Assert((_snapshot != null) ^ _asyncReadWithoutSnapshot, "Must have either _snapshot set up or _asyncReadWithoutSnapshot enabled (but not both) to pend a read"); - - return false; + _snapshot = snapshot; + _snapshot.Snap(this); + _snapshotReplay = false; } - internal void PrepareReplaySnapshot() + internal void ResetSnapshot() { - _networkPacketTaskSource = null; - _snapshot.PrepareReplay(); + 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 void ReadSniSyncOverAsync() { if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) @@ -1271,198 +359,7 @@ internal void ReadSniSyncOverAsync() 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"); @@ -1841,22 +738,7 @@ public void ProcessSniPacket(PacketHandle packet, uint error) } } - 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 - } - } - } + private void SetBufferSecureStrings() { @@ -2085,264 +967,49 @@ public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) } 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) - { - return WriteBytes(span, span.Length, 0, canAccumulate, completion); - } - - internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null) - { - return WriteBytes(ReadOnlySpan.Empty, len, offsetBuffer, canAccumulate, completion, b); - } - - // - // Takes a span or a byte array and writes it to the buffer - // If you pass in a span and a null array then the span wil be used. - // If you pass in a non-null array then the array will be used and the span is ignored. - // if the span cannot be written into the current packet then the remaining contents of the span are copied to a - // new heap allocated array that will used to callback into the method to continue the write operation. - private Task WriteBytes(ReadOnlySpan b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null, byte[] array = null) - { - if (array != null) - { - b = new ReadOnlySpan(array, offsetBuffer, len); - } - try - { - bool async = _parser._asyncWrite; // NOTE: We are capturing this now for the assert after the Task is returned, since WritePacket will turn off async if there is an exception - Debug.Assert(async || _asyncWriteCount == 0); - // Do we have to send out in packet size chunks, or can we rely on netlib layer to break it up? - // would prefer to do something like: - // - // if (len > what we have room for || len > out buf) - // flush buffer - // UnsafeNativeMethods.Write(b) - // - - int offset = offsetBuffer; - - Debug.Assert(b.Length >= len, "Invalid length sent to WriteBytes()!"); - - // loop through and write the entire array - do - { - if ((_outBytesUsed + len) > _outBuff.Length) - { - // If the remainder of the data won't fit into the buffer, then we have to put - // whatever we can into the buffer, and flush that so we can then put more into - // the buffer on the next loop of the while. - - int remainder = _outBuff.Length - _outBytesUsed; - - // write the remainder - Span copyTo = _outBuff.AsSpan(_outBytesUsed, remainder); - ReadOnlySpan copyFrom = b.Slice(0, remainder); - - Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length{copyFrom.Length:D} should be the same"); - - copyFrom.CopyTo(copyTo); - - offset += remainder; - _outBytesUsed += remainder; - len -= remainder; - b = b.Slice(remainder, len); - - Task packetTask = WritePacket(TdsEnums.SOFTFLUSH, canAccumulate); - - if (packetTask != null) - { - Task task = null; - Debug.Assert(async, "Returned task in sync mode"); - if (completion == null) - { - completion = new TaskCompletionSource(); - task = completion.Task; // we only care about return from topmost call, so do not access Task property in other cases - } - - if (array == null) - { - byte[] tempArray = new byte[len]; - Span copyTempTo = tempArray.AsSpan(); - - Debug.Assert(copyTempTo.Length == b.Length, $"copyTempTo.Length:{copyTempTo.Length} and copyTempFrom.Length:{b.Length:D} should be the same"); - - b.CopyTo(copyTempTo); - array = tempArray; - offset = 0; - } - - WriteBytesSetupContinuation(array, len, completion, offset, packetTask); - return task; - } - } - else - { - //((stateObj._outBytesUsed + len) <= stateObj._outBuff.Length ) - // Else the remainder of the string will fit into the buffer, so copy it into the - // buffer and then break out of the loop. - - Span copyTo = _outBuff.AsSpan(_outBytesUsed, len); - ReadOnlySpan copyFrom = b.Slice(0, len); - - Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length:{copyFrom.Length:D} should be the same"); - - copyFrom.CopyTo(copyTo); - - // handle out buffer bytes used counter - _outBytesUsed += len; - break; - } - } while (len > 0); - - if (completion != null) - { - completion.SetResult(null); + _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; } - return null; } - catch (Exception e) + finally { - if (completion != null) +#if DEBUG + if (SqlCommand.DebugForceAsyncWriteDelay > 0) { - completion.SetException(e); - return null; + new Timer(obj => + { + Interlocked.Decrement(ref _asyncWriteCount); + TaskCompletionSource writeCompletionSource = _writeCompletionSource; + if (_asyncWriteCount == 0 && writeCompletionSource != null) + { + writeCompletionSource.TrySetResult(null); + } + }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite); } else { - throw; +#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 // + ///////////////////////////////////////// + // This is in its own method to avoid always allocating the lambda in WriteBytes private void WriteBytesSetupContinuation(byte[] array, int len, TaskCompletionSource completion, int offset, Task packetTask) { @@ -2351,99 +1018,6 @@ private void WriteBytesSetupContinuation(byte[] array, int len, TaskCompletionSo ); } - // Dumps contents of buffer to SNI for network write. - internal Task WritePacket(byte flushMode, bool canAccumulate = false) - { - TdsParserState state = _parser.State; - if ((state == TdsParserState.Closed) || (state == TdsParserState.Broken)) - { - throw ADP.ClosedConnectionError(); - } - - if ( - // This appears to be an optimization to avoid writing empty packets in 2005 - // However, since we don't know the version prior to login Is2005OrNewer was always false prior to login - // So removing the Is2005OrNewer check causes issues since the login packet happens to meet the rest of the conditions below - // So we need to avoid this check prior to login completing - state == TdsParserState.OpenLoggedIn && - !_bulkCopyOpperationInProgress && // ignore the condition checking for bulk copy - _outBytesUsed == (_outputHeaderLen + BitConverter.ToInt32(_outBuff, _outputHeaderLen)) - && _outputPacketCount == 0 - || _outBytesUsed == _outputHeaderLen - && _outputPacketCount == 0) - { - return null; - } - - byte status; - byte packetNumber = _outputPacketNumber; - - // Set Status byte based whether this is end of message or not - bool willCancel = (_cancelled) && (_parser._asyncWrite); - if (willCancel) - { - status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE; - ResetPacketCounters(); - } - else if (TdsEnums.HARDFLUSH == flushMode) - { - status = TdsEnums.ST_EOM; - ResetPacketCounters(); - } - else if (TdsEnums.SOFTFLUSH == flushMode) - { - status = TdsEnums.ST_BATCH; - _outputPacketNumber++; - _outputPacketCount++; - } - else - { - status = TdsEnums.ST_EOM; - Debug.Fail($"Unexpected argument {flushMode,-2:x2} to WritePacket"); - } - - _outBuff[0] = _outputMessageType; // Message Type - _outBuff[1] = status; - _outBuff[2] = (byte)(_outBytesUsed >> 8); // length - upper byte - _outBuff[3] = (byte)(_outBytesUsed & 0xff); // length - lower byte - _outBuff[4] = 0; // channel - _outBuff[5] = 0; - _outBuff[6] = packetNumber; // packet - _outBuff[7] = 0; // window - - Task task = null; - _parser.CheckResetConnection(this); // HAS SIDE EFFECTS - re-org at a later time if possible - - task = WriteSni(canAccumulate); - AssertValidState(); - - if (willCancel) - { - // If we have been canceled, then ensure that we write the ATTN packet as well - task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket); - } - return task; - } - - private void CancelWritePacket() - { - Debug.Assert(_cancelled, "Should not call CancelWritePacket if _cancelled is not set"); - - _parser.Connection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we are holding the lock - try - { - // Send the attention and wait for the ATTN_ACK - SendAttention(); - ResetCancelAndProcessAttention(); - - // Let the caller know that we've given up - throw SQL.OperationCancelled(); - } - finally - { - _parser.Connection.ThreadHasParserLockForClose = false; - } - } #pragma warning disable 0420 // a reference to a volatile field will not be treated as volatile @@ -2724,22 +1298,6 @@ private Task WriteSni(bool canAccumulate) // Statistics, Tracing, and related methods // ////////////////////////////////////////////// - private void SniReadStatisticsAndTracing() - { - SqlStatistics statistics = Parser.Statistics; - if (null != statistics) - { - if (statistics.WaitForReply) - { - statistics.SafeIncrement(ref statistics._serverRoundtrips); - statistics.ReleaseAndUpdateNetworkServerTimer(); - } - - statistics.SafeAdd(ref statistics._bytesReceived, _inBytesRead); - statistics.SafeIncrement(ref statistics._buffersReceived); - } - } - private void SniWriteStatisticsAndTracing() { SqlStatistics statistics = _parser.Statistics; @@ -2751,350 +1309,17 @@ private void SniWriteStatisticsAndTracing() } } - [Conditional("DEBUG")] - private void AssertValidState() - { - if (_inBytesUsed < 0 || _inBytesRead < 0) - { - Debug.Fail($"Invalid TDS Parser State: either _inBytesUsed or _inBytesRead is negative: {_inBytesUsed}, {_inBytesRead}"); - } - else if (_inBytesUsed > _inBytesRead) - { - Debug.Fail($"Invalid TDS Parser State: _inBytesUsed > _inBytesRead: {_inBytesUsed} > {_inBytesRead}"); - } - - Debug.Assert(_inBytesPacket >= 0, "Packet must not be negative"); - } - - ////////////////////////////////////////////// // Errors and Warnings // ////////////////////////////////////////////// - /// - /// True if there is at least one error or warning (not counting the pre-attention errors\warnings) - /// - internal bool HasErrorOrWarning - { - get - { - return _hasErrorOrWarning; - } - } - - /// - /// Adds an error to the error collection - /// - /// - internal void AddError(SqlError error) - { - Debug.Assert(error != null, "Trying to add a null error"); - - // Switch to sync once we see an error - _syncOverAsync = true; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = true; - if (_errors == null) - { - _errors = new SqlErrorCollection(); - } - _errors.Add(error); - } - } - - /// - /// Gets the number of errors currently in the error collection - /// - internal int ErrorCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_errors != null) - { - count = _errors.Count; - } - } - return count; - } - } - - /// - /// Adds an warning to the warning collection - /// - /// - internal void AddWarning(SqlError error) - { - Debug.Assert(error != null, "Trying to add a null error"); - - // Switch to sync once we see a warning - _syncOverAsync = true; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = true; - if (_warnings == null) - { - _warnings = new SqlErrorCollection(); - } - _warnings.Add(error); - } - } - - /// - /// Gets the number of warnings currently in the warning collection - /// - internal int WarningCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_warnings != null) - { - count = _warnings.Count; - } - } - return count; - } - } + protected abstract PacketHandle EmptyReadPacket { get; } - internal int PreAttentionErrorCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_preAttentionErrors != null) - { - count = _preAttentionErrors.Count; - } - } - return count; - } - } - - /// - /// Gets the number of errors currently in the pre-attention warning collection - /// - internal int PreAttentionWarningCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_preAttentionWarnings != null) - { - count = _preAttentionWarnings.Count; - } - } - return count; - } - } - - /// - /// Gets the full list of errors and warnings (including the pre-attention ones), then wipes all error and warning lists - /// - /// If true, the connection should be broken - /// An array containing all of the errors and warnings - internal SqlErrorCollection GetFullErrorAndWarningCollection(out bool broken) - { - SqlErrorCollection allErrors = new SqlErrorCollection(); - broken = false; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = false; - - // Merge all error lists, then reset them - AddErrorsToCollection(_errors, ref allErrors, ref broken); - AddErrorsToCollection(_warnings, ref allErrors, ref broken); - _errors = null; - _warnings = null; - - // We also process the pre-attention error lists here since, if we are here and they are populated, then an error occurred while sending attention so we should show the errors now (otherwise they'd be lost) - AddErrorsToCollection(_preAttentionErrors, ref allErrors, ref broken); - AddErrorsToCollection(_preAttentionWarnings, ref allErrors, ref broken); - _preAttentionErrors = null; - _preAttentionWarnings = null; - } - - return allErrors; - } - - private void AddErrorsToCollection(SqlErrorCollection inCollection, ref SqlErrorCollection collectionToAddTo, ref bool broken) - { - if (inCollection != null) - { - foreach (SqlError error in inCollection) - { - collectionToAddTo.Add(error); - broken |= (error.Class >= TdsEnums.FATAL_ERROR_CLASS); - } - } - } - - /// - /// Stores away current errors and warnings so that an attention can be processed - /// - internal void StoreErrorAndWarningForAttention() - { - lock (_errorAndWarningsLock) - { - Debug.Assert(_preAttentionErrors == null && _preAttentionWarnings == null, "Can't store errors for attention because there are already errors stored"); - - _hasErrorOrWarning = false; - - _preAttentionErrors = _errors; - _preAttentionWarnings = _warnings; - - _errors = null; - _warnings = null; - } - } - - /// - /// Restores errors and warnings that were stored in order to process an attention - /// - internal void RestoreErrorAndWarningAfterAttention() - { - lock (_errorAndWarningsLock) - { - Debug.Assert(_errors == null && _warnings == null, "Can't restore errors after attention because there are already other errors"); - - _hasErrorOrWarning = (((_preAttentionErrors != null) && (_preAttentionErrors.Count > 0)) || ((_preAttentionWarnings != null) && (_preAttentionWarnings.Count > 0))); - - _errors = _preAttentionErrors; - _warnings = _preAttentionWarnings; - _preAttentionErrors = null; - _preAttentionWarnings = null; - } - } - - /// - /// Checks if an error is stored in _error and, if so, throws an error - /// - internal void CheckThrowSNIException() - { - if (HasErrorOrWarning) - { - ThrowExceptionAndWarning(); - } - } - - /// - /// Debug Only: Ensures that the TdsParserStateObject has no lingering state and can safely be re-used - /// - [Conditional("DEBUG")] - internal void AssertStateIsClean() - { - // If our TdsParser is closed or broken, then we don't really care about our state - TdsParser parser = _parser; - if ((parser != null) && (parser.State != TdsParserState.Closed) && (parser.State != TdsParserState.Broken)) - { - // Async reads - Debug.Assert(_snapshot == null && !_snapshotReplay, "StateObj has leftover snapshot state"); - Debug.Assert(!_asyncReadWithoutSnapshot, "StateObj has AsyncReadWithoutSnapshot still enabled"); - Debug.Assert(_executionContext == null, "StateObj has a stored execution context from an async read"); - // Async writes - 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(!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 - Debug.Assert(!_hasErrorOrWarning, "StateObj still has stored errors or warnings"); - } - } - -#if DEBUG - internal void CompletePendingReadWithSuccess(bool resetForcePendingReadsToWait) - { - TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; - TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; - - Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); - Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); - - try - { - if (realNetworkPacketTaskSource != null) - { - // Wait for the real read to complete - realNetworkPacketTaskSource.Task.Wait(); - } - } - finally - { - if (networkPacketTaskSource != null) - { - if (resetForcePendingReadsToWait) - { - s_forcePendingReadsToWaitForUser = false; - } - networkPacketTaskSource.TrySetResult(null); - } - } - } - - internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendingReadsToWait) - { - TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; - TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; - - Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); - Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); - - try - { - if (realNetworkPacketTaskSource != null) - { - // Wait for the real read to complete - realNetworkPacketTaskSource.Task.Wait(); - } - } - finally - { - if (networkPacketTaskSource != null) - { - if (resetForcePendingReadsToWait) - { - s_forcePendingReadsToWaitForUser = false; - } - - AddError(new SqlError(errorCode, 0x00, TdsEnums.FATAL_ERROR_CLASS, _parser.Server, string.Empty, string.Empty, 0)); - try - { - ThrowExceptionAndWarning(); - } - catch (Exception ex) - { - networkPacketTaskSource.TrySetException(ex); - } - } - } - } -#endif - internal void CloneCleanupAltMetaDataSetArray() - { - if (_snapshot != null) - { - _snapshot.CloneCleanupAltMetaDataSetArray(); - } - } private sealed class StateSnapshot { 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..e2caedb211 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 @@ -860,7 +860,7 @@ private bool TryCleanPartialRead() } #if DEBUG - if (_stateObj._pendingData) + if (_stateObj.HasPendingData) { byte token; if (!_stateObj.TryPeekByte(out token)) @@ -1067,7 +1067,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 +1333,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 +3473,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 +3563,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 +3620,7 @@ private bool TryHasMoreRows(out bool moreRows) moreRows = false; return false; } - if (_stateObj._pendingData) + if (_stateObj.HasPendingData) { if (!_stateObj.TryPeekByte(out b)) { @@ -3964,7 +3964,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 +3996,7 @@ private bool TryReadInternal(bool setTimeout, out bool more) } } - if (!_stateObj._pendingData) + if (!_stateObj.HasPendingData) { if (!TryCloseInternal(false /*closeReader*/)) { @@ -4020,7 +4020,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 +4061,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 70673ef343..a2dfce2f6b 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 @@ -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' + // HasPendingData 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..db17d407db 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 @@ -9,7 +9,6 @@ using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; -using System.Security; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; @@ -20,10 +19,7 @@ internal partial class TdsParserStateObject { 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. + // 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,24 +28,18 @@ 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 // @@ -101,17 +91,6 @@ internal SNIHandle Handle } } - internal bool HasOpenResult - { - get => _hasOpenResult; - set => _hasOpenResult = value; - } - - internal bool HasPendingData - { - get => _pendingData; - set => _pendingData = value; - } internal uint Status { @@ -128,33 +107,6 @@ internal uint Status } } - 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 // ///////////////////// @@ -182,7 +134,7 @@ internal void Cancel(int objectID) { _cancelled = true; - if (_pendingData && !_attentionSent) + if (HasPendingData && !_attentionSent) { bool hasParserLock = false; // Keep looping until we have the parser lock (and so are allowed to write), or the conneciton closes\breaks @@ -415,883 +367,27 @@ 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; + _snapshot = new StateSnapshot(this); + _snapshot.Snap(); + _snapshotReplay = false; } - internal void PrepareReplaySnapshot() + internal void ResetSnapshot() { - _networkPacketTaskSource = null; - _snapshot.PrepareReplay(); + _snapshot = null; + _snapshotReplay = false; } +#if DEBUG + StackTrace _lastStack; +#endif + internal void ReadSniSyncOverAsync() { if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) @@ -1364,200 +460,6 @@ internal void ReadSniSyncOverAsync() } } - 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"); @@ -1963,23 +865,6 @@ public void ProcessSniPacket(IntPtr packet, uint error) } } - 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, IntPtr packet, uint error) { // Key never used. // Note - it's possible that when native calls managed that an asynchronous exception @@ -2171,341 +1056,150 @@ public void WriteAsyncCallback(IntPtr key, IntPtr packet, uint sniError) } 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); - } - } - -#pragma warning restore 420 - - ///////////////////////////////////////// - // Network/Packet Writing & 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) - { - 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!!!"); - - _outBytesUsed += lengthInBytes; - } - - // ResetSecurePasswordsInformation: clears information regarding secure passwords when login is done; called from TdsParser.TdsLogin - internal void ResetSecurePasswordsInfomation() - { - for (int i = 0; i < _securePasswords.Length; ++i) - { - _securePasswords[i] = null; - _securePasswordOffsetsInBuffer[i] = 0; - } - } - - internal Task WaitForAccumulatedWrites() - { - // 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 - - if (_asyncWriteCount == 0) - { - return null; - } - - _writeCompletionSource = new TaskCompletionSource(); - Task task = _writeCompletionSource.Task; - - // Ensure that _writeCompletionSource is set before checking state - Thread.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 -#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 - - // 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) - { - TdsParser.ReliabilitySection.Assert("unreliable call to WriteByte"); // you need to setup for a thread abort somewhere before you call this method - - 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; - } - - // - // Takes a byte array and writes it to the buffer. - // - internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null) - { - try - { - TdsParser.ReliabilitySection.Assert("unreliable call to WriteByteArray"); // you need to setup for a thread abort somewhere before you call this method - - bool async = _parser._asyncWrite; // NOTE: We are capturing this now for the assert after the Task is returned, since WritePacket will turn off async if there is an exception - Debug.Assert(async || _asyncWriteCount == 0); - // Do we have to send out in packet size chunks, or can we rely on netlib layer to break it up? - // would prefer to to do something like: - // - // if (len > what we have room for || len > out buf) - // flush buffer - // UnsafeNativeMethods.Write(b) - // - - int offset = offsetBuffer; - - Debug.Assert(b.Length >= len, "Invalid length sent to WriteByteArray()!"); - - // loop through and write the entire array - do - { - if ((_outBytesUsed + len) > _outBuff.Length) - { - // If the remainder of the string won't fit into the buffer, then we have to put - // whatever we can into the buffer, and flush that so we can then put more into - // the buffer on the next loop of the while. - - int remainder = _outBuff.Length - _outBytesUsed; - - // write the remainder - Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, remainder); - - // handle counters - offset += remainder; - _outBytesUsed += remainder; - len -= remainder; - - Task packetTask = WritePacket(TdsEnums.SOFTFLUSH, canAccumulate); - - if (packetTask != null) - { - Task task = null; - Debug.Assert(async, "Returned task in sync mode"); - if (completion == null) - { - completion = new TaskCompletionSource(); - task = completion.Task; // we only care about return from topmost call, so do not access Task property in other cases - } - WriteByteArraySetupContinuation(b, len, completion, offset, packetTask); - return task; - } - - } - else - { //((stateObj._outBytesUsed + len) <= stateObj._outBuff.Length ) - // Else the remainder of the string will fit into the buffer, so copy it into the - // buffer and then break out of the loop. - - Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, len); - - // handle out buffer bytes used counter - _outBytesUsed += len; - break; - } - } while (len > 0); - - if (completion != null) - { - completion.SetResult(null); - } - return null; - } - catch (Exception e) - { - if (completion != null) - { - completion.SetException(e); - return null; - } - else - { - throw; - } - } - } - - // This is in its own method to avoid always allocating the lambda in WriteByteArray - private void WriteByteArraySetupContinuation(byte[] b, int len, TaskCompletionSource completion, int offset, Task packetTask) - { - AsyncHelper.ContinueTask(packetTask, completion, - () => WriteByteArray(b, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion), - connectionToDoom: _parser.Connection - ); - } - - // Dumps contents of buffer to SNI for network write. - internal Task WritePacket(byte flushMode, bool canAccumulate = false) - { - TdsParserState state = _parser.State; - if ((state == TdsParserState.Closed) || (state == TdsParserState.Broken)) - { - throw ADP.ClosedConnectionError(); - } - - if ( - // This appears to be an optimization to avoid writing empty packets in 2005 - // However, since we don't know the version prior to login Is2005OrNewer was always false prior to login - // So removing the Is2005OrNewer check causes issues since the login packet happens to meet the rest of the conditions below - // So we need to avoid this check prior to login completing - state == TdsParserState.OpenLoggedIn && - !_bulkCopyOpperationInProgress // ignore the condition checking for bulk copy (SQL BU 414551) - && _outBytesUsed == (_outputHeaderLen + BitConverter.ToInt32(_outBuff, _outputHeaderLen)) - && _outputPacketCount == 0 - || _outBytesUsed == _outputHeaderLen - && _outputPacketCount == 0) - { - return null; - } - - byte status; - byte packetNumber = _outputPacketNumber; - - // Set Status byte based whether this is end of message or not - bool willCancel = (_cancelled) && (_parser._asyncWrite); - if (willCancel) - { - status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE; - ResetPacketCounters(); - } - else if (TdsEnums.HARDFLUSH == flushMode) - { - status = TdsEnums.ST_EOM; - ResetPacketCounters(); +#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); + } } - else if (TdsEnums.SOFTFLUSH == flushMode) +#if DEBUG + if (SqlCommand.DebugForceAsyncWriteDelay > 0) { - status = TdsEnums.ST_BATCH; - _outputPacketNumber++; - _outputPacketCount++; + return; } - else +#endif + TaskCompletionSource completionSource = _writeCompletionSource; + if (_asyncWriteCount == 0 && completionSource != null) { - status = TdsEnums.ST_EOM; - Debug.Fail($"Unexpected argument {flushMode,-2:x2} to WritePacket"); + completionSource.TrySetResult(null); } + } - _outBuff[0] = _outputMessageType; // Message Type - _outBuff[1] = status; - _outBuff[2] = (byte)(_outBytesUsed >> 8); // length - upper byte - _outBuff[3] = (byte)(_outBytesUsed & 0xff); // length - lower byte - _outBuff[4] = 0; // channel - _outBuff[5] = 0; - _outBuff[6] = packetNumber; // packet - _outBuff[7] = 0; // window - - Task task = null; - _parser.CheckResetConnection(this); // HAS SIDE EFFECTS - re-org at a later time if possible +#pragma warning restore 420 - task = WriteSni(canAccumulate); - AssertValidState(); + ///////////////////////////////////////// + // Network/Packet Writing & Processing // + ///////////////////////////////////////// - if (willCancel) - { - // If we have been cancelled, then ensure that we write the ATTN packet as well - task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket, _parser.Connection); - } - return task; - } - private void CancelWritePacket() - { - Debug.Assert(_cancelled, "Should not call CancelWritePacket if _cancelled is not set"); + - _parser.Connection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we are holding the lock - try - { - // Send the attention and wait for the ATTN_ACK - SendAttention(); - ResetCancelAndProcessAttention(); + + + //// + //// Takes a byte array and writes it to the buffer. + //// + //internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null) + //{ + // try + // { + // TdsParser.ReliabilitySection.Assert("unreliable call to WriteByteArray"); // you need to setup for a thread abort somewhere before you call this method + + // bool async = _parser._asyncWrite; // NOTE: We are capturing this now for the assert after the Task is returned, since WritePacket will turn off async if there is an exception + // Debug.Assert(async || _asyncWriteCount == 0); + // // Do we have to send out in packet size chunks, or can we rely on netlib layer to break it up? + // // would prefer to to do something like: + // // + // // if (len > what we have room for || len > out buf) + // // flush buffer + // // UnsafeNativeMethods.Write(b) + // // + + // int offset = offsetBuffer; + + // Debug.Assert(b.Length >= len, "Invalid length sent to WriteByteArray()!"); + + // // loop through and write the entire array + // do + // { + // if ((_outBytesUsed + len) > _outBuff.Length) + // { + // // If the remainder of the string won't fit into the buffer, then we have to put + // // whatever we can into the buffer, and flush that so we can then put more into + // // the buffer on the next loop of the while. + + // int remainder = _outBuff.Length - _outBytesUsed; + + // // write the remainder + // Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, remainder); + + // // handle counters + // offset += remainder; + // _outBytesUsed += remainder; + // len -= remainder; + + // Task packetTask = WritePacket(TdsEnums.SOFTFLUSH, canAccumulate); + + // if (packetTask != null) + // { + // Task task = null; + // Debug.Assert(async, "Returned task in sync mode"); + // if (completion == null) + // { + // completion = new TaskCompletionSource(); + // task = completion.Task; // we only care about return from topmost call, so do not access Task property in other cases + // } + // WriteByteArraySetupContinuation(b, len, completion, offset, packetTask); + // return task; + // } + + // } + // else + // { //((stateObj._outBytesUsed + len) <= stateObj._outBuff.Length ) + // // Else the remainder of the string will fit into the buffer, so copy it into the + // // buffer and then break out of the loop. + + // Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, len); + + // // handle out buffer bytes used counter + // _outBytesUsed += len; + // break; + // } + // } while (len > 0); + + // if (completion != null) + // { + // completion.SetResult(null); + // } + // return null; + // } + // catch (Exception e) + // { + // if (completion != null) + // { + // completion.SetException(e); + // return null; + // } + // else + // { + // throw; + // } + // } + //} - // Let the caller know that we've given up - throw SQL.OperationCancelled(); - } - finally - { - _parser.Connection.ThreadHasParserLockForClose = false; - } + // This is in its own method to avoid always allocating the lambda in WriteByteArray + private void WriteBytesSetupContinuation(byte[] b, int len, TaskCompletionSource completion, int offset, Task packetTask) + { + AsyncHelper.ContinueTask(packetTask, completion, + () => WriteByteArray(b, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion), + connectionToDoom: _parser.Connection + ); } #pragma warning disable 420 // a reference to a volatile field will not be treated as volatile @@ -2849,23 +1543,7 @@ private void RemovePacketFromPendingList(IntPtr pointer) ////////////////////////////////////////////// // Statistics, Tracing, and related methods // ////////////////////////////////////////////// - - private void SniReadStatisticsAndTracing() - { - SqlStatistics statistics = Parser.Statistics; - if (null != statistics) - { - if (statistics.WaitForReply) - { - statistics.SafeIncrement(ref statistics._serverRoundtrips); - statistics.ReleaseAndUpdateNetworkServerTimer(); - } - - statistics.SafeAdd(ref statistics._bytesReceived, _inBytesRead); - statistics.SafeIncrement(ref statistics._buffersReceived); - } - } - + private void SniWriteStatisticsAndTracing() { SqlStatistics statistics = _parser.Statistics; @@ -2908,353 +1586,10 @@ private void SniWriteStatisticsAndTracing() SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.WritePacket | INFO | ADV | State Object Id {0}, Packet sent. Out buffer: {1}, Out Bytes Used: {2}", ObjectID, _outBuff, (ushort)_outBytesUsed); } - [Conditional("DEBUG")] - void AssertValidState() - { - if (_inBytesUsed < 0 || _inBytesRead < 0) - { - Debug.Fail($"Invalid TDS Parser State: either _inBytesUsed or _inBytesRead is negative: {_inBytesUsed}, {_inBytesRead}"); - } - else if (_inBytesUsed > _inBytesRead) - { - Debug.Fail($"Invalid TDS Parser State: _inBytesUsed > _inBytesRead: {_inBytesUsed} > {_inBytesRead}"); - } - - // TODO: add more state validations here, remember to call AssertValidState every place the relevant fields change - - Debug.Assert(_inBytesPacket >= 0, "Packet must not be negative"); - } - - ////////////////////////////////////////////// // Errors and Warnings // ////////////////////////////////////////////// - /// - /// True if there is at least one error or warning (not counting the pre-attention errors\warnings) - /// - internal bool HasErrorOrWarning - { - get - { - return _hasErrorOrWarning; - } - } - - /// - /// Adds an error to the error collection - /// - /// - internal void AddError(SqlError error) - { - Debug.Assert(error != null, "Trying to add a null error"); - - // Switch to sync once we see an error - _syncOverAsync = true; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = true; - if (_errors == null) - { - _errors = new SqlErrorCollection(); - } - _errors.Add(error); - } - } - - /// - /// Gets the number of errors currently in the error collection - /// - internal int ErrorCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_errors != null) - { - count = _errors.Count; - } - } - return count; - } - } - - /// - /// Adds an warning to the warning collection - /// - /// - internal void AddWarning(SqlError error) - { - Debug.Assert(error != null, "Trying to add a null error"); - - // Switch to sync once we see a warning - _syncOverAsync = true; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = true; - if (_warnings == null) - { - _warnings = new SqlErrorCollection(); - } - _warnings.Add(error); - } - } - - /// - /// Gets the number of warnings currently in the warning collection - /// - internal int WarningCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_warnings != null) - { - count = _warnings.Count; - } - } - return count; - } - } - - /// - /// Gets the number of errors currently in the pre-attention error collection - /// - internal int PreAttentionErrorCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_preAttentionErrors != null) - { - count = _preAttentionErrors.Count; - } - } - return count; - } - } - - /// - /// Gets the number of errors currently in the pre-attention warning collection - /// - internal int PreAttentionWarningCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_preAttentionWarnings != null) - { - count = _preAttentionWarnings.Count; - } - } - return count; - } - } - - /// - /// Gets the full list of errors and warnings (including the pre-attention ones), then wipes all error and warning lists - /// - /// If true, the connection should be broken - /// An array containing all of the errors and warnings - internal SqlErrorCollection GetFullErrorAndWarningCollection(out bool broken) - { - SqlErrorCollection allErrors = new SqlErrorCollection(); - broken = false; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = false; - - // Merge all error lists, then reset them - AddErrorsToCollection(_errors, ref allErrors, ref broken); - AddErrorsToCollection(_warnings, ref allErrors, ref broken); - _errors = null; - _warnings = null; - - // We also process the pre-attention error lists here since, if we are here and they are populated, then an error occurred while sending attention so we should show the errors now (otherwise they'd be lost) - AddErrorsToCollection(_preAttentionErrors, ref allErrors, ref broken); - AddErrorsToCollection(_preAttentionWarnings, ref allErrors, ref broken); - _preAttentionErrors = null; - _preAttentionWarnings = null; - } - - return allErrors; - } - - private void AddErrorsToCollection(SqlErrorCollection inCollection, ref SqlErrorCollection collectionToAddTo, ref bool broken) - { - if (inCollection != null) - { - foreach (SqlError error in inCollection) - { - collectionToAddTo.Add(error); - broken |= (error.Class >= TdsEnums.FATAL_ERROR_CLASS); - } - } - } - - /// - /// Stores away current errors and warnings so that an attention can be processed - /// - internal void StoreErrorAndWarningForAttention() - { - lock (_errorAndWarningsLock) - { - Debug.Assert(_preAttentionErrors == null && _preAttentionWarnings == null, "Can't store errors for attention because there are already errors stored"); - - _hasErrorOrWarning = false; - - _preAttentionErrors = _errors; - _preAttentionWarnings = _warnings; - - _errors = null; - _warnings = null; - } - } - - /// - /// Restores errors and warnings that were stored in order to process an attention - /// - internal void RestoreErrorAndWarningAfterAttention() - { - lock (_errorAndWarningsLock) - { - Debug.Assert(_errors == null && _warnings == null, "Can't restore errors after attention because there are already other errors"); - - _hasErrorOrWarning = (((_preAttentionErrors != null) && (_preAttentionErrors.Count > 0)) || ((_preAttentionWarnings != null) && (_preAttentionWarnings.Count > 0))); - - _errors = _preAttentionErrors; - _warnings = _preAttentionWarnings; - - _preAttentionErrors = null; - _preAttentionWarnings = null; - } - } - - /// - /// Checks if an error is stored in _error and, if so, throws an error - /// - internal void CheckThrowSNIException() - { - if (HasErrorOrWarning) - { - ThrowExceptionAndWarning(); - } - } - - /// - /// Debug Only: Ensures that the TdsParserStateObject has no lingering state and can safely be re-used - /// - [Conditional("DEBUG")] - internal void AssertStateIsClean() - { - // If our TdsParser is closed or broken, then we don't really care about our state - TdsParser parser = _parser; - if ((parser != null) && (parser.State != TdsParserState.Closed) && (parser.State != TdsParserState.Broken)) - { - // Async reads - Debug.Assert(_snapshot == null && !_snapshotReplay, "StateObj has leftover snapshot state"); - Debug.Assert(!_asyncReadWithoutSnapshot, "StateObj has AsyncReadWithoutSnapshot still enabled"); - Debug.Assert(_executionContext == null, "StateObj has a stored execution context from an async read"); - // Async writes - 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(!_cancelled, "StateObj still has cancellation set"); - Debug.Assert(_timeoutState == TimeoutState.Stopped, "StateObj still has internal timeout set"); - // Errors and Warnings - Debug.Assert(!_hasErrorOrWarning, "StateObj still has stored errors or warnings"); - } - } - -#if DEBUG - internal void CompletePendingReadWithSuccess(bool resetForcePendingReadsToWait) - { - TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; - TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; - - Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); - Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); - - try - { - if (realNetworkPacketTaskSource != null) - { - // Wait for the real read to complete - realNetworkPacketTaskSource.Task.Wait(); - } - } - finally - { - if (networkPacketTaskSource != null) - { - if (resetForcePendingReadsToWait) - { - s_forcePendingReadsToWaitForUser = false; - } - - networkPacketTaskSource.TrySetResult(null); - } - } - } - - internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendingReadsToWait) - { - TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; - TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; - - Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); - Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); - - try - { - if (realNetworkPacketTaskSource != null) - { - // Wait for the real read to complete - realNetworkPacketTaskSource.Task.Wait(); - } - } - finally - { - if (networkPacketTaskSource != null) - { - if (resetForcePendingReadsToWait) - { - s_forcePendingReadsToWaitForUser = false; - } - - AddError(new SqlError(errorCode, 0x00, TdsEnums.FATAL_ERROR_CLASS, _parser.Server, string.Empty, string.Empty, 0)); - try - { - ThrowExceptionAndWarning(); - } - catch (Exception ex) - { - networkPacketTaskSource.TrySetException(ex); - } - } - } - } -#endif - - internal void CloneCleanupAltMetaDataSetArray() - { - if (_snapshot != null) - { - _snapshot.CloneCleanupAltMetaDataSetArray(); - } - } class PacketData { @@ -3271,11 +1606,7 @@ class StateSnapshot private int _snapshotInBuffCurrent = 0; private int _snapshotInBytesUsed = 0; private int _snapshotInBytesPacket = 0; - private bool _snapshotPendingData = false; - private bool _snapshotErrorTokenReceived = false; - private bool _snapshotHasOpenResult = false; - private bool _snapshotReceivedColumnMetadata = false; - private bool _snapshotAttentionReceived; + private SnapshottedStateFlags _state = SnapshottedStateFlags.None; private byte _snapshotMessageStatus; private NullBitmap _snapshotNullBitmapInfo; @@ -3283,6 +1614,7 @@ class StateSnapshot private ulong _snapshotLongLenLeft; private _SqlMetaDataSet _snapshotCleanupMetaData; private _SqlMetaDataSetCollection _snapshotCleanupAltMetaDataSetArray; + internal byte[] _plpBuffer; private readonly TdsParserStateObject _stateObj; @@ -3385,8 +1717,6 @@ internal void Snap() _snapshotInBuffCurrent = 0; _snapshotInBytesUsed = _stateObj._inBytesUsed; _snapshotInBytesPacket = _stateObj._inBytesPacket; - _snapshotPendingData = _stateObj._pendingData; - _snapshotErrorTokenReceived = _stateObj._errorTokenReceived; _snapshotMessageStatus = _stateObj._messageStatus; // _nullBitmapInfo must be cloned before it is updated _snapshotNullBitmapInfo = _stateObj._nullBitmapInfo; @@ -3395,9 +1725,8 @@ 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; + + _state = _stateObj._snapshottedState; #if DEBUG _rollingPend = 0; _rollingPendCount = 0; @@ -3415,29 +1744,24 @@ internal void ResetSnapshotState() _snapshotInBuffCurrent = 0; Replay(); - _stateObj._inBytesUsed = _snapshotInBytesUsed; _stateObj._inBytesPacket = _snapshotInBytesPacket; - _stateObj._pendingData = _snapshotPendingData; - _stateObj._errorTokenReceived = _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 && ((_state & SnapshottedStateFlags.OpenResult) == SnapshottedStateFlags.OpenResult)) { _stateObj.IncrementAndObtainOpenResultCount(_stateObj._executedUnderTransaction); } - else if (_stateObj._hasOpenResult && !_snapshotHasOpenResult) + else if (_stateObj.HasOpenResult && ((_state & SnapshottedStateFlags.OpenResult) != SnapshottedStateFlags.OpenResult)) { _stateObj.DecrementOpenResultCount(); } - //else _stateObj._hasOpenResult is already == _snapshotHasOpenResult - - _stateObj._receivedColMetaData = _snapshotReceivedColumnMetadata; - _stateObj._attentionReceived = _snapshotAttentionReceived; + _stateObj._snapshottedState = _state; // Reset partially read state (these only need to be maintained if doing async without snapshot) _stateObj._bTmpRead = 0; @@ -3456,6 +1780,7 @@ internal void PrepareReplay() { ResetSnapshotState(); } + } } } 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 86f51fba2a..fdeadea150 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -80,7 +80,7 @@ public TimeoutState(int value) // Out buffer variables internal byte[] _outBuff; // internal write buffer - initialize on login internal int _outBytesUsed = TdsEnums.HEADER_LEN; // number of bytes used in internal write buffer - initialize past header - + // In buffer variables /// @@ -122,6 +122,7 @@ public TimeoutState(int value) // Async variables private int _pendingCallbacks; // we increment this before each async read/write call and decrement it in the callback. We use this to determine when to release the GcHandle... + private SnapshottedStateFlags _snapshottedState; // Timeout variables private long _timeoutMilliseconds; @@ -466,7 +467,7 @@ 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 @@ -510,6 +511,30 @@ internal bool IsGuaranteedNull(int columnOrdinal) byte testByte = _nullBitmap[columnOrdinal >> 3]; return (testBit & testByte) != 0; } + + 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; + } } @@ -943,7 +968,7 @@ internal void ResetPacketCounters() _outputPacketNumber = 1; _outputPacketCount = 0; } - + internal bool SetPacketSize(int size) { if (size > TdsEnums.MAX_PACKET_SIZE) @@ -1056,5 +1081,1818 @@ private void DumpBuffer() { } } */ + + /////////////////////////////////////// + // 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) + { +#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.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) + { +#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. + + if (!TryReadByteArray(_bTmp.AsSpan(_bTmpRead), 8 - _bTmpRead, out int bytesRead)) + { + Debug.Assert(_bTmpRead + bytesRead <= 8, "Read more data than required"); + _bTmpRead += bytesRead; + value = 0; + return false; + } + + Debug.Assert(_bTmpRead + bytesRead == 8, "TryReadByteArray returned true without reading all data required"); + _bTmpRead = 0; + AssertValidState(); + value = BitConverter.ToInt64(_bTmp, 0); + return true; + } + // 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. + + if (!TryReadByteArray(_bTmp.AsSpan(start: _bTmpRead), 4 - _bTmpRead, out int bytesRead)) + { + Debug.Assert(_bTmpRead + bytesRead <= 4, "Read more data than required"); + _bTmpRead += bytesRead; + value = 0; + return false; + } + + Debug.Assert(_bTmpRead + bytesRead == 4, "TryReadByteArray returned true without reading all data required"); + _bTmpRead = 0; + AssertValidState(); + value = BitConverter.ToUInt32(_bTmp, 0); + return true; + } + // 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; + } + // 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; + } + // 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) + { + // 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) + { + Debug.Assert(_syncOverAsync, "Should not attempt pends in a synchronous call"); + bool result = TryReadPlpLength(returnPlpNullIfNull, out ulong 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) + { + 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 + if (!TryReadInt64(out long value)) + { + lengthLeft = 0; + return false; + } + _longlen = (ulong)value; + } + + 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 uint 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 bytesToRead = (int)Math.Min(_longlenleft, (ulong)len); + bool result = TryReadByteArray(buff.AsSpan(offset), bytesToRead, out int 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; + } + + 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); + } + + + + ///////////////////////////////////////// + // 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 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) + { +#if NETFRAMEWORK + _snapshot.CheckStack(new StackTrace()); +#else + _snapshot.CheckStack(Environment.StackTrace); +#endif + } +#endif + return true; + } +#if DEBUG + + if (s_checkNetworkPacketRetryStacks) + { +#if NETFRAMEWORK + _lastStack = new StackTrace(); +#else + _lastStack = Environment.StackTrace; +#endif + } +#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 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) + { + // 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(); +#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; + } + + 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 + } + } + } + + ///////////////////////////////////////// + // Network/Packet Writing & 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) + { +#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; + } + + // ResetSecurePasswordsInformation: clears information regarding secure passwords when login is done; called from TdsParser.TdsLogin + 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) + { +#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; + } + + internal Task WriteByteSpan(ReadOnlySpan span, bool canAccumulate = true, TaskCompletionSource completion = null) + { + return WriteBytes(span, span.Length, 0, canAccumulate, completion); + } + + internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null) + { + + return WriteBytes(ReadOnlySpan.Empty, len, offsetBuffer, canAccumulate, completion, b); + } + + // + // Takes a span or a byte array and writes it to the buffer + // If you pass in a span and a null array then the span wil be used. + // If you pass in a non-null array then the array will be used and the span is ignored. + // if the span cannot be written into the current packet then the remaining contents of the span are copied to a + // new heap allocated array that will used to callback into the method to continue the write operation. + private Task WriteBytes(ReadOnlySpan b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null, byte[] array = null) + { + if (array != null) + { + b = new ReadOnlySpan(array, offsetBuffer, len); + } + try + { +#if NETFRAMEWORK + TdsParser.ReliabilitySection.Assert("unreliable call to WriteByteArray"); // you need to setup for a thread abort somewhere before you call this method +#endif + bool async = _parser._asyncWrite; // NOTE: We are capturing this now for the assert after the Task is returned, since WritePacket will turn off async if there is an exception + Debug.Assert(async || _asyncWriteCount == 0); + // Do we have to send out in packet size chunks, or can we rely on netlib layer to break it up? + // would prefer to do something like: + // + // if (len > what we have room for || len > out buf) + // flush buffer + // UnsafeNativeMethods.Write(b) + // + + int offset = offsetBuffer; + + Debug.Assert(b.Length >= len, "Invalid length sent to WriteBytes()!"); + + // loop through and write the entire array + do + { + if ((_outBytesUsed + len) > _outBuff.Length) + { + // If the remainder of the data won't fit into the buffer, then we have to put + // whatever we can into the buffer, and flush that so we can then put more into + // the buffer on the next loop of the while. + + int remainder = _outBuff.Length - _outBytesUsed; + + // write the remainder + Span copyTo = _outBuff.AsSpan(_outBytesUsed, remainder); + ReadOnlySpan copyFrom = b.Slice(0, remainder); + + Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length{copyFrom.Length:D} should be the same"); + + copyFrom.CopyTo(copyTo); + + offset += remainder; + _outBytesUsed += remainder; + len -= remainder; + b = b.Slice(remainder, len); + + Task packetTask = WritePacket(TdsEnums.SOFTFLUSH, canAccumulate); + + if (packetTask != null) + { + Task task = null; + Debug.Assert(async, "Returned task in sync mode"); + if (completion == null) + { + completion = new TaskCompletionSource(); + task = completion.Task; // we only care about return from topmost call, so do not access Task property in other cases + } + + if (array == null) + { + byte[] tempArray = new byte[len]; + Span copyTempTo = tempArray.AsSpan(); + + Debug.Assert(copyTempTo.Length == b.Length, $"copyTempTo.Length:{copyTempTo.Length} and copyTempFrom.Length:{b.Length:D} should be the same"); + + b.CopyTo(copyTempTo); + array = tempArray; + offset = 0; + } + + WriteBytesSetupContinuation(array, len, completion, offset, packetTask); + return task; + } + } + else + { + //((stateObj._outBytesUsed + len) <= stateObj._outBuff.Length ) + // Else the remainder of the string will fit into the buffer, so copy it into the + // buffer and then break out of the loop. + + Span copyTo = _outBuff.AsSpan(_outBytesUsed, len); + ReadOnlySpan copyFrom = b.Slice(0, len); + + Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length:{copyFrom.Length:D} should be the same"); + + copyFrom.CopyTo(copyTo); + + // handle out buffer bytes used counter + _outBytesUsed += len; + break; + } + } while (len > 0); + + if (completion != null) + { + completion.SetResult(null); + } + return null; + } + catch (Exception e) + { + if (completion != null) + { + completion.SetException(e); + return null; + } + + throw; + } + } + + // Dumps contents of buffer to SNI for network write. + internal Task WritePacket(byte flushMode, bool canAccumulate = false) + { + TdsParserState state = _parser.State; + if ((state == TdsParserState.Closed) || (state == TdsParserState.Broken)) + { + throw ADP.ClosedConnectionError(); + } + + if ( + // This appears to be an optimization to avoid writing empty packets in 2005 + // However, since we don't know the version prior to login Is2005OrNewer was always false prior to login + // So removing the Is2005OrNewer check causes issues since the login packet happens to meet the rest of the conditions below + // So we need to avoid this check prior to login completing + state == TdsParserState.OpenLoggedIn && + !_bulkCopyOpperationInProgress && // ignore the condition checking for bulk copy + _outBytesUsed == (_outputHeaderLen + BitConverter.ToInt32(_outBuff, _outputHeaderLen)) + && _outputPacketCount == 0 + || _outBytesUsed == _outputHeaderLen + && _outputPacketCount == 0) + { + return null; + } + + byte status; + byte packetNumber = _outputPacketNumber; + + // Set Status byte based whether this is end of message or not + bool willCancel = (_cancelled) && (_parser._asyncWrite); + if (willCancel) + { + status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE; + ResetPacketCounters(); + } + else if (TdsEnums.HARDFLUSH == flushMode) + { + status = TdsEnums.ST_EOM; + ResetPacketCounters(); + } + else if (TdsEnums.SOFTFLUSH == flushMode) + { + status = TdsEnums.ST_BATCH; + _outputPacketNumber++; + _outputPacketCount++; + } + else + { + status = TdsEnums.ST_EOM; + Debug.Fail($"Unexpected argument {flushMode,-2:x2} to WritePacket"); + } + + _outBuff[0] = _outputMessageType; // Message Type + _outBuff[1] = status; + _outBuff[2] = (byte)(_outBytesUsed >> 8); // length - upper byte + _outBuff[3] = (byte)(_outBytesUsed & 0xff); // length - lower byte + _outBuff[4] = 0; // channel + _outBuff[5] = 0; + _outBuff[6] = packetNumber; // packet + _outBuff[7] = 0; // window + + Task task = null; + _parser.CheckResetConnection(this); // HAS SIDE EFFECTS - re-org at a later time if possible + + task = WriteSni(canAccumulate); + AssertValidState(); + + if (willCancel) + { + // If we have been canceled, then ensure that we write the ATTN packet as well + task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket); + } + return task; + } + + private void CancelWritePacket() + { + Debug.Assert(_cancelled, "Should not call CancelWritePacket if _cancelled is not set"); + + _parser.Connection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we are holding the lock + try + { + // Send the attention and wait for the ATTN_ACK + SendAttention(); + ResetCancelAndProcessAttention(); + + // Let the caller know that we've given up + throw SQL.OperationCancelled(); + } + finally + { + _parser.Connection.ThreadHasParserLockForClose = false; + } + } + + ////////////////////////////////////////////// + // Statistics, Tracing, and related methods // + ////////////////////////////////////////////// + + private void SniReadStatisticsAndTracing() + { + SqlStatistics statistics = Parser.Statistics; + if (null != statistics) + { + if (statistics.WaitForReply) + { + statistics.SafeIncrement(ref statistics._serverRoundtrips); + statistics.ReleaseAndUpdateNetworkServerTimer(); + } + + statistics.SafeAdd(ref statistics._bytesReceived, _inBytesRead); + statistics.SafeIncrement(ref statistics._buffersReceived); + } + } + + [Conditional("DEBUG")] + private void AssertValidState() + { + if (_inBytesUsed < 0 || _inBytesRead < 0) + { + Debug.Fail($"Invalid TDS Parser State: either _inBytesUsed or _inBytesRead is negative: {_inBytesUsed}, {_inBytesRead}"); + } + else if (_inBytesUsed > _inBytesRead) + { + Debug.Fail($"Invalid TDS Parser State: _inBytesUsed > _inBytesRead: {_inBytesUsed} > {_inBytesRead}"); + } + + // TODO: add more state validations here, remember to call AssertValidState every place the relevant fields change + + Debug.Assert(_inBytesPacket >= 0, "Packet must not be negative"); + } + + ////////////////////////////////////////////// + // Errors and Warnings // + ////////////////////////////////////////////// + + /// + /// True if there is at least one error or warning (not counting the pre-attention errors\warnings) + /// + internal bool HasErrorOrWarning + { + get + { + return _hasErrorOrWarning; + } + } + + /// + /// Adds an error to the error collection + /// + /// + internal void AddError(SqlError error) + { + Debug.Assert(error != null, "Trying to add a null error"); + + // Switch to sync once we see an error + _syncOverAsync = true; + + lock (_errorAndWarningsLock) + { + _hasErrorOrWarning = true; + if (_errors == null) + { + _errors = new SqlErrorCollection(); + } + _errors.Add(error); + } + } + + /// + /// Gets the number of errors currently in the error collection + /// + internal int ErrorCount + { + get + { + int count = 0; + lock (_errorAndWarningsLock) + { + if (_errors != null) + { + count = _errors.Count; + } + } + return count; + } + } + + /// + /// Adds an warning to the warning collection + /// + /// + internal void AddWarning(SqlError error) + { + Debug.Assert(error != null, "Trying to add a null error"); + + // Switch to sync once we see a warning + _syncOverAsync = true; + + lock (_errorAndWarningsLock) + { + _hasErrorOrWarning = true; + if (_warnings == null) + { + _warnings = new SqlErrorCollection(); + } + _warnings.Add(error); + } + } + + /// + /// Gets the number of warnings currently in the warning collection + /// + internal int WarningCount + { + get + { + int count = 0; + lock (_errorAndWarningsLock) + { + if (_warnings != null) + { + count = _warnings.Count; + } + } + return count; + } + } + + /// + /// Gets the number of errors currently in the pre-attention error collection + /// + internal int PreAttentionErrorCount + { + get + { + int count = 0; + lock (_errorAndWarningsLock) + { + if (_preAttentionErrors != null) + { + count = _preAttentionErrors.Count; + } + } + return count; + } + } + + /// + /// Gets the number of errors currently in the pre-attention warning collection + /// + internal int PreAttentionWarningCount + { + get + { + int count = 0; + lock (_errorAndWarningsLock) + { + if (_preAttentionWarnings != null) + { + count = _preAttentionWarnings.Count; + } + } + return count; + } + } + + /// + /// Gets the full list of errors and warnings (including the pre-attention ones), then wipes all error and warning lists + /// + /// If true, the connection should be broken + /// An array containing all of the errors and warnings + internal SqlErrorCollection GetFullErrorAndWarningCollection(out bool broken) + { + SqlErrorCollection allErrors = new SqlErrorCollection(); + broken = false; + + lock (_errorAndWarningsLock) + { + _hasErrorOrWarning = false; + + // Merge all error lists, then reset them + AddErrorsToCollection(_errors, ref allErrors, ref broken); + AddErrorsToCollection(_warnings, ref allErrors, ref broken); + _errors = null; + _warnings = null; + + // We also process the pre-attention error lists here since, if we are here and they are populated, then an error occurred while sending attention so we should show the errors now (otherwise they'd be lost) + AddErrorsToCollection(_preAttentionErrors, ref allErrors, ref broken); + AddErrorsToCollection(_preAttentionWarnings, ref allErrors, ref broken); + _preAttentionErrors = null; + _preAttentionWarnings = null; + } + + return allErrors; + } + + private void AddErrorsToCollection(SqlErrorCollection inCollection, ref SqlErrorCollection collectionToAddTo, ref bool broken) + { + if (inCollection != null) + { + foreach (SqlError error in inCollection) + { + collectionToAddTo.Add(error); + broken |= (error.Class >= TdsEnums.FATAL_ERROR_CLASS); + } + } + } + + /// + /// Stores away current errors and warnings so that an attention can be processed + /// + internal void StoreErrorAndWarningForAttention() + { + lock (_errorAndWarningsLock) + { + Debug.Assert(_preAttentionErrors == null && _preAttentionWarnings == null, "Can't store errors for attention because there are already errors stored"); + + _hasErrorOrWarning = false; + + _preAttentionErrors = _errors; + _preAttentionWarnings = _warnings; + + _errors = null; + _warnings = null; + } + } + + /// + /// Restores errors and warnings that were stored in order to process an attention + /// + internal void RestoreErrorAndWarningAfterAttention() + { + lock (_errorAndWarningsLock) + { + Debug.Assert(_errors == null && _warnings == null, "Can't restore errors after attention because there are already other errors"); + + _hasErrorOrWarning = (((_preAttentionErrors != null) && (_preAttentionErrors.Count > 0)) || ((_preAttentionWarnings != null) && (_preAttentionWarnings.Count > 0))); + + _errors = _preAttentionErrors; + _warnings = _preAttentionWarnings; + + _preAttentionErrors = null; + _preAttentionWarnings = null; + } + } + + /// + /// Checks if an error is stored in _error and, if so, throws an error + /// + internal void CheckThrowSNIException() + { + if (HasErrorOrWarning) + { + ThrowExceptionAndWarning(); + } + } + + /// + /// Debug Only: Ensures that the TdsParserStateObject has no lingering state and can safely be re-used + /// + [Conditional("DEBUG")] + internal void AssertStateIsClean() + { + // If our TdsParser is closed or broken, then we don't really care about our state + TdsParser parser = _parser; + if ((parser != null) && (parser.State != TdsParserState.Closed) && (parser.State != TdsParserState.Broken)) + { + // Async reads + Debug.Assert(_snapshot == null && !_snapshotReplay, "StateObj has leftover snapshot state"); + Debug.Assert(!_asyncReadWithoutSnapshot, "StateObj has AsyncReadWithoutSnapshot still enabled"); + Debug.Assert(_executionContext == null, "StateObj has a stored execution context from an async read"); + // Async writes + 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(!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 + Debug.Assert(!_hasErrorOrWarning, "StateObj still has stored errors or warnings"); + } + } + + #if DEBUG + internal void CompletePendingReadWithSuccess(bool resetForcePendingReadsToWait) + { + TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; + TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; + + Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); + Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); + + try + { + if (realNetworkPacketTaskSource != null) + { + // Wait for the real read to complete + realNetworkPacketTaskSource.Task.Wait(); + } + } + finally + { + if (networkPacketTaskSource != null) + { + if (resetForcePendingReadsToWait) + { + s_forcePendingReadsToWaitForUser = false; + } + + networkPacketTaskSource.TrySetResult(null); + } + } + } + + internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendingReadsToWait) + { + TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; + TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; + + Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); + Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); + + try + { + if (realNetworkPacketTaskSource != null) + { + // Wait for the real read to complete + realNetworkPacketTaskSource.Task.Wait(); + } + } + finally + { + if (networkPacketTaskSource != null) + { + if (resetForcePendingReadsToWait) + { + s_forcePendingReadsToWaitForUser = false; + } + + AddError(new SqlError(errorCode, 0x00, TdsEnums.FATAL_ERROR_CLASS, _parser.Server, string.Empty, string.Empty, 0)); + try + { + ThrowExceptionAndWarning(); + } + catch (Exception ex) + { + networkPacketTaskSource.TrySetException(ex); + } + } + } + } +#endif + internal void CloneCleanupAltMetaDataSetArray() + { + if (_snapshot != null) + { + _snapshot.CloneCleanupAltMetaDataSetArray(); + } + } + } } diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/SqlCommandRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/SqlCommandRunner.cs index 5caed95c35..3c64d797cd 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/SqlCommandRunner.cs +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/SqlCommandRunner.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Text; using System.Threading.Tasks; using System.Xml; using BenchmarkDotNet.Attributes; @@ -13,6 +14,8 @@ public class SqlCommandRunner : BaseRunner private static SqlConnection s_sqlConnection; private Table _table; private string _query; + private readonly string _query_data_varbinary = "SELECT foo FROM data_varbinary"; + private readonly string _query_data_varchar = "SELECT foo FROM data_varchar"; [GlobalSetup] public void Setup() @@ -25,12 +28,46 @@ public void Setup() .InsertBulkRows(s_config.Benchmarks.SqlCommandRunnerConfig.RowCount, s_sqlConnection); _query = $"SELECT * FROM {_table.Name}"; + + using var cmd = s_sqlConnection.CreateCommand(); + cmd.CommandText = @" +IF OBJECT_ID('dbo.data_varbinary', 'U') IS NOT NULL +DROP TABLE dbo.data_varbinary; +CREATE TABLE dbo.data_varbinary (id INT, foo VARBINARY(MAX)) +"; + cmd.ExecuteNonQuery(); + + cmd.CommandText = "INSERT INTO dbo.data_varbinary (id, foo) VALUES (@id, @foo)"; + cmd.Parameters.AddWithValue("id", 1); + cmd.Parameters.AddWithValue("foo", new byte[1024 * 1024 * 10]); + cmd.ExecuteNonQuery(); + + using var cmd2 = s_sqlConnection.CreateCommand(); + cmd2.CommandText = @" +IF OBJECT_ID('dbo.data_varchar', 'U') IS NOT NULL +DROP TABLE dbo.data_varchar; +CREATE TABLE dbo.data_varchar (id INT, foo VARCHAR(MAX)) +"; + cmd2.ExecuteNonQuery(); + + cmd2.CommandText = "INSERT INTO dbo.data_varchar (id, foo) VALUES (@id, @foo)"; + cmd2.Parameters.AddWithValue("id", 1); + cmd2.Parameters.AddWithValue("foo", Encoding.UTF8.GetString(new byte[1024 * 1024 * 10])); + cmd2.ExecuteNonQuery(); } [GlobalCleanup] public void Dispose() { _table.DropTable(s_sqlConnection); + using var cmd = s_sqlConnection.CreateCommand(); + cmd.CommandText = @"IF OBJECT_ID('dbo.data_varbinary', 'U') IS NOT NULL + DROP TABLE data_varbinary;"; + cmd.ExecuteNonQuery(); + using var cmd2 = s_sqlConnection.CreateCommand(); + cmd2.CommandText = @"IF OBJECT_ID('dbo.data_varchar', 'U') IS NOT NULL + DROP TABLE data_varchar;"; + cmd2.ExecuteNonQuery(); s_sqlConnection.Close(); SqlConnection.ClearAllPools(); } @@ -47,8 +84,44 @@ public void ExecuteReader() [Benchmark] public async Task ExecuteReaderAsync() { - using SqlCommand sqlCommand = new(_query, s_sqlConnection); - using SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); + await using SqlCommand sqlCommand = new(_query, s_sqlConnection); + await using SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { } + } + + [Benchmark] + public void VARBINARY_MAX_ExecuteReader() + { + using SqlCommand sqlCommand = new(_query_data_varbinary, s_sqlConnection); + using SqlDataReader reader = sqlCommand.ExecuteReader(); + while (reader.Read()) + { } + } + + [Benchmark] + public async Task VARBINARY_MAX_ExecuteReaderAsync() + { + await using SqlCommand sqlCommand = new(_query_data_varbinary, s_sqlConnection); + await using SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { } + } + + [Benchmark] + public void VARCHAR_MAX_ExecuteReader() + { + using SqlCommand sqlCommand = new(_query_data_varchar, s_sqlConnection); + using SqlDataReader reader = sqlCommand.ExecuteReader(); + while (reader.Read()) + { } + } + + [Benchmark] + public async Task VARCHAR_MAX_ExecuteReaderAsync() + { + await using SqlCommand sqlCommand = new(_query_data_varchar, s_sqlConnection); + await using SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); while (await reader.ReadAsync()) { } } @@ -63,7 +136,7 @@ public void ExecuteScalar() [Benchmark] public async Task ExecuteScalarAsync() { - using SqlCommand sqlCommand = new(_query, s_sqlConnection); + await using SqlCommand sqlCommand = new(_query, s_sqlConnection); _ = await sqlCommand.ExecuteScalarAsync(); } @@ -77,7 +150,7 @@ public void ExecuteNonQuery() [Benchmark] public async Task ExecuteNonQueryAsync() { - using SqlCommand sqlCommand = new(_query, s_sqlConnection); + await using SqlCommand sqlCommand = new(_query, s_sqlConnection); await sqlCommand.ExecuteNonQueryAsync(); } @@ -93,10 +166,45 @@ public void ExecuteXmlReader() [Benchmark] public async Task ExecuteXmlReaderAsync() { - using SqlCommand sqlCommand = new(_query + " FOR XML AUTO, BINARY BASE64", s_sqlConnection); + await using SqlCommand sqlCommand = new(_query + " FOR XML AUTO, BINARY BASE64", s_sqlConnection); using XmlReader reader = await sqlCommand.ExecuteXmlReaderAsync(); while (await reader.ReadAsync()) { } } + + [Benchmark] + public void VARBINARY_MAX_ExecuteScalar() + { + using var cmd = new SqlCommand(_query_data_varbinary, s_sqlConnection); + + _ = cmd.ExecuteScalar(); + + } + + [Benchmark] + public async Task VARBINARY_MAX_ExecuteScalarAsync() + { + await using var cmd = new SqlCommand(_query_data_varbinary, s_sqlConnection); + + _ = await cmd.ExecuteScalarAsync(); + } + + [Benchmark] + public void VARCHAR_MAX_ExecuteScalar() + { + using var cmd = new SqlCommand(_query_data_varchar, s_sqlConnection); + + _ = cmd.ExecuteScalar(); + + } + + [Benchmark] + public async Task VARCHAR_MAX_ExecuteScalarAsync() + { + await using var cmd = new SqlCommand(_query_data_varchar, s_sqlConnection); + + _ = await cmd.ExecuteScalarAsync(); + } + } }