diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs index 14e997883644..abd90e359bad 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -1419,9 +1419,14 @@ internal void WriteInt(int v, TdsParserStateObject stateObj) // internal void WriteFloat(float v, TdsParserStateObject stateObj) { +#if netcoreapp // BitConverter.TryGetBytes is only availble in netcoreapp 2.1> at this time, review support when changing + Span bytes = stackalloc byte[sizeof(float)]; + BitConverter.TryWriteBytes(bytes, v); + stateObj.WriteByteSpan(bytes); +#else byte[] bytes = BitConverter.GetBytes(v); - - stateObj.WriteByteArray(bytes, bytes.Length, 0); + stateObj.WriteByteArray(bytes, bytes.Length, 0); +#endif } // @@ -1493,9 +1498,14 @@ internal void WriteUnsignedLong(ulong uv, TdsParserStateObject stateObj) // internal void WriteDouble(double v, TdsParserStateObject stateObj) { +#if netcoreapp // BitConverter.TryGetBytes is only availble in netcoreapp 2.1> at this time, review support when changing + Span bytes = stackalloc byte[sizeof(double)]; + BitConverter.TryWriteBytes(bytes, v); + stateObj.WriteByteSpan(bytes); +#else byte[] bytes = BitConverter.GetBytes(v); - stateObj.WriteByteArray(bytes, bytes.Length, 0); +#endif } internal void PrepareResetConnection(bool preserveTransaction) diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs index a55e4b59ff10..fea7447e4f5f 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs @@ -2984,11 +2984,29 @@ internal void WriteByte(byte b) _outBuff[_outBytesUsed++] = b; } - // - // Takes a byte array and writes it to the buffer. - // + + 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 @@ -3003,26 +3021,31 @@ internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumu int offset = offsetBuffer; - Debug.Assert(b.Length >= len, "Invalid length sent to WriteByteArray()!"); + 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 string won't fit into the buffer, then we have to put + // 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 - Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, 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); - // handle counters offset += remainder; _outBytesUsed += remainder; len -= remainder; + b = b.Slice(remainder, len); Task packetTask = WritePacket(TdsEnums.SOFTFLUSH, canAccumulate); @@ -3035,16 +3058,35 @@ internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumu 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); + + 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 ) + { + //((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); + 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; @@ -3072,12 +3114,13 @@ internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumu } } - // 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) + // 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) { AsyncHelper.ContinueTask(packetTask, completion, - () => WriteByteArray(b, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion), - connectionToDoom: _parser.Connection); + () => WriteBytes(ReadOnlySpan.Empty, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion, array), + connectionToDoom: _parser.Connection + ); } // Dumps contents of buffer to SNI for network write.