Skip to content

Commit 9d726d2

Browse files
committed
Reuse a rented byte array.
Signed-off-by: Bradley Grainger <[email protected]>
1 parent cb2bb16 commit 9d726d2

File tree

1 file changed

+49
-41
lines changed

1 file changed

+49
-41
lines changed

src/MySqlConnector/Core/SingleCommandPayloadCreator.cs

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Buffers;
2+
using System.Buffers.Binary;
13
using System.Runtime.InteropServices;
24
using MySqlConnector.Logging;
35
using MySqlConnector.Protocol;
@@ -24,60 +26,66 @@ public async ValueTask WritePrologueAsync(MySqlConnection connection, CommandLis
2426
var preparedStatement = preparedStatements.Statements[commandListPosition.PreparedStatementIndex];
2527
if (preparedStatement.Parameters is { } parameters)
2628
{
27-
// check each parameter
28-
for (var i = 0; i < parameters.Length; i++)
29+
byte[]? buffer = null;
30+
try
2931
{
30-
// look up this parameter in the command's parameter collection and check if it is a Stream
31-
// NOTE: full parameter checks will be performed (and throw any necessary exceptions) in WritePreparedStatement
32-
var parameterName = preparedStatement.Statement.NormalizedParameterNames![i];
33-
var parameterIndex = parameterName is not null ? (command.RawParameters?.UnsafeIndexOf(parameterName) ?? -1) : preparedStatement.Statement.ParameterIndexes[i];
34-
if (command.RawParameters is { } rawParameters && parameterIndex >= 0 && parameterIndex < rawParameters.Count && rawParameters[parameterIndex] is { Value: Stream stream and not MemoryStream })
32+
// check each parameter
33+
for (var i = 0; i < parameters.Length; i++)
3534
{
36-
// send almost-full packets, but don't send exactly ProtocolUtility.MaxPacketSize bytes in one payload (as that's ambiguous to whether another packet follows).
37-
const int maxDataSize = 16_000_000;
38-
int totalBytesRead;
39-
while (true)
35+
// look up this parameter in the command's parameter collection and check if it is a Stream
36+
// NOTE: full parameter checks will be performed (and throw any necessary exceptions) in WritePreparedStatement
37+
var parameterName = preparedStatement.Statement.NormalizedParameterNames![i];
38+
var parameterIndex = parameterName is not null ? (command.RawParameters?.UnsafeIndexOf(parameterName) ?? -1) : preparedStatement.Statement.ParameterIndexes[i];
39+
if (command.RawParameters is { } rawParameters && parameterIndex >= 0 && parameterIndex < rawParameters.Count && rawParameters[parameterIndex] is { Value: Stream stream and not MemoryStream })
4040
{
41-
// write seven-byte COM_STMT_SEND_LONG_DATA header: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_send_long_data.html
42-
var writer = new ByteBufferWriter(maxDataSize);
43-
writer.Write((byte) CommandKind.StatementSendLongData);
44-
writer.Write(preparedStatement.StatementId);
45-
writer.Write((ushort) i);
41+
// seven-byte COM_STMT_SEND_LONG_DATA header: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_send_long_data.html
42+
const int packetHeaderLength = 7;
4643

47-
// keep reading from the stream until we've filled the buffer to send
48-
#if NET7_0_OR_GREATER
49-
if (ioBehavior == IOBehavior.Synchronous)
50-
totalBytesRead = stream.ReadAtLeast(writer.GetSpan(maxDataSize).Slice(0, maxDataSize), maxDataSize, throwOnEndOfStream: false);
51-
else
52-
totalBytesRead = await stream.ReadAtLeastAsync(writer.GetMemory(maxDataSize).Slice(0, maxDataSize), maxDataSize, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false);
53-
writer.Advance(totalBytesRead);
54-
#else
55-
totalBytesRead = 0;
56-
int bytesRead;
57-
do
44+
// send almost-full packets, but don't send exactly ProtocolUtility.MaxPacketSize bytes in one payload (as that's ambiguous to whether another packet follows).
45+
const int maxDataSize = 16_000_000;
46+
int totalBytesRead;
47+
while (true)
5848
{
59-
var sizeToRead = maxDataSize - totalBytesRead;
60-
ReadOnlyMemory<byte> bufferMemory = writer.GetMemory(sizeToRead);
61-
if (!MemoryMarshal.TryGetArray(bufferMemory, out var arraySegment))
62-
throw new InvalidOperationException("Failed to get array segment from buffer memory.");
49+
buffer ??= ArrayPool<byte>.Shared.Rent(packetHeaderLength + maxDataSize);
50+
buffer[0] = (byte) CommandKind.StatementSendLongData;
51+
BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(1), preparedStatement.StatementId);
52+
BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(5), (ushort) i);
53+
54+
// keep reading from the stream until we've filled the buffer to send
55+
#if NET7_0_OR_GREATER
6356
if (ioBehavior == IOBehavior.Synchronous)
64-
bytesRead = stream.Read(arraySegment.Array!, arraySegment.Offset, sizeToRead);
57+
totalBytesRead = stream.ReadAtLeast(buffer.AsSpan(packetHeaderLength, maxDataSize), maxDataSize, throwOnEndOfStream: false);
6558
else
66-
bytesRead = await stream.ReadAsync(arraySegment.Array!, arraySegment.Offset, sizeToRead, cancellationToken).ConfigureAwait(false);
67-
totalBytesRead += bytesRead;
68-
writer.Advance(bytesRead);
69-
} while (bytesRead > 0);
59+
totalBytesRead = await stream.ReadAtLeastAsync(buffer.AsMemory(packetHeaderLength, maxDataSize), maxDataSize, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false);
60+
#else
61+
totalBytesRead = 0;
62+
int bytesRead;
63+
do
64+
{
65+
var sizeToRead = maxDataSize - totalBytesRead;
66+
if (ioBehavior == IOBehavior.Synchronous)
67+
bytesRead = stream.Read(buffer, packetHeaderLength + totalBytesRead, sizeToRead);
68+
else
69+
bytesRead = await stream.ReadAsync(buffer, packetHeaderLength + totalBytesRead, sizeToRead, cancellationToken).ConfigureAwait(false);
70+
totalBytesRead += bytesRead;
71+
} while (bytesRead > 0);
7072
#endif
7173

72-
if (totalBytesRead == 0)
73-
break;
74+
if (totalBytesRead == 0)
75+
break;
7476

75-
// send StatementSendLongData; MySQL Server will keep appending the sent data to the parameter value
76-
using var payload = writer.ToPayloadData();
77-
await connection.Session.SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
77+
// send StatementSendLongData; MySQL Server will keep appending the sent data to the parameter value
78+
using var payload = new PayloadData(buffer.AsMemory(0, packetHeaderLength + totalBytesRead), isPooled: false);
79+
await connection.Session.SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
80+
}
7881
}
7982
}
8083
}
84+
finally
85+
{
86+
if (buffer is not null)
87+
ArrayPool<byte>.Shared.Return(buffer);
88+
}
8189
}
8290
}
8391
}

0 commit comments

Comments
 (0)