Skip to content

Commit 635aa9b

Browse files
committed
Fix StackOverflowException when reading large buffers.
This was similar to #239 but occurred in InsertLargeBlobAsync when reading over a slow connection. The 'ContinueWith' code chained too many continuations together recursively. The compiler-generated 'await' state machine avoids this problem.
1 parent eca7943 commit 635aa9b

File tree

2 files changed

+23
-55
lines changed

2 files changed

+23
-55
lines changed

src/MySqlConnector/MySqlConnector.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<RepositoryType>git</RepositoryType>
2323
<RepositoryUrl>https://github.com/mysql-net/MySqlConnector.git</RepositoryUrl>
2424
<DebugType>embedded</DebugType>
25+
<LangVersion>latest</LangVersion>
2526
</PropertyGroup>
2627

2728
<ItemGroup Condition=" '$(OS)' == 'Windows_NT' ">

src/MySqlConnector/Protocol/Serialization/BufferedByteReader.cs

Lines changed: 22 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ namespace MySqlConnector.Protocol.Serialization
66
{
77
internal sealed class BufferedByteReader
88
{
9-
public BufferedByteReader()
10-
{
11-
m_buffer = new byte[16384];
12-
}
9+
public BufferedByteReader() => m_buffer = new byte[16384];
1310

1411
public ValueTask<ArraySegment<byte>> ReadBytesAsync(IByteHandler byteHandler, int count, IOBehavior ioBehavior)
1512
{
@@ -32,62 +29,32 @@ public ValueTask<ArraySegment<byte>> ReadBytesAsync(IByteHandler byteHandler, in
3229
return ReadBytesAsync(byteHandler, new ArraySegment<byte>(buffer, m_remainingData.Count, buffer.Length - m_remainingData.Count), count, ioBehavior);
3330
}
3431

35-
private ValueTask<ArraySegment<byte>> ReadBytesAsync(IByteHandler byteHandler, ArraySegment<byte> buffer, int totalBytesToRead, IOBehavior ioBehavior)
36-
{
37-
// keep reading data synchronously while it is available
38-
var readBytesTask = byteHandler.ReadBytesAsync(buffer, ioBehavior);
39-
while (readBytesTask.IsCompletedSuccessfully)
40-
{
41-
ValueTask<ArraySegment<byte>> result;
42-
if (HasReadAllData(readBytesTask.Result, ref buffer, totalBytesToRead, out result))
43-
return result;
44-
45-
readBytesTask = byteHandler.ReadBytesAsync(buffer, ioBehavior);
46-
}
47-
48-
// call .ContinueWith (as a separate method, so that the temporary class for the lambda is only allocated if necessary)
49-
return AddContinuation(readBytesTask, byteHandler, buffer, totalBytesToRead, ioBehavior);
50-
51-
ValueTask<ArraySegment<byte>> AddContinuation(ValueTask<int> readBytesTask_, IByteHandler byteHandler_, ArraySegment<byte> buffer_, int totalBytesToRead_, IOBehavior ioBehavior_) =>
52-
readBytesTask_.ContinueWith(x => HasReadAllData(x, ref buffer_, totalBytesToRead_, out var result_) ? result_ : ReadBytesAsync(byteHandler_, buffer_, totalBytesToRead_, ioBehavior_));
53-
}
54-
55-
/// <summary>
56-
/// Returns <c>true</c> if all the required data has been read, then sets <paramref name="result"/> to a <see cref="ValueTask{ArraySegment{byte}}"/> representing that data.
57-
/// Otherwise, returns <c>false</c> and updates <paramref name="buffer"/> to where more data should be placed when it's read.
58-
/// </summary>
59-
/// <param name="readBytesCount">The number of bytes that have just been read into <paramref name="buffer"/>.</param>
60-
/// <param name="buffer">The <see cref="ArraySegment{byte}"/> that contains all the data read so far, and that will receive more data read in the future. It is assumed that data is stored
61-
/// at the beginning of the array owned by <paramref name="buffer"/> and that <code>Offset</code> indicates where to place future data.</param>
62-
/// <param name="totalBytesToRead">The total number of bytes that need to be read.</param>
63-
/// <param name="result">On success, a <see cref="ValueTask{ArraySegment{byte}}"/> representing all the data that was read.</param>
64-
/// <returns><c>true</c> if all data has been read; otherwise, <c>false</c>.</returns>
65-
private bool HasReadAllData(int readBytesCount, ref ArraySegment<byte> buffer, int totalBytesToRead, out ValueTask<ArraySegment<byte>> result)
32+
private async ValueTask<ArraySegment<byte>> ReadBytesAsync(IByteHandler byteHandler, ArraySegment<byte> buffer, int totalBytesToRead, IOBehavior ioBehavior)
6633
{
67-
if (readBytesCount == 0)
68-
{
69-
var data = m_remainingData;
70-
m_remainingData = default(ArraySegment<byte>);
71-
result = new ValueTask<ArraySegment<byte>>(data);
72-
return true;
73-
}
74-
75-
var bufferSize = buffer.Offset + readBytesCount;
76-
if (bufferSize >= totalBytesToRead)
34+
while (true)
7735
{
78-
var bufferBytes = new ArraySegment<byte>(buffer.Array, 0, bufferSize);
79-
var requestedBytes = bufferBytes.Slice(0, totalBytesToRead);
80-
m_remainingData = bufferBytes.Slice(totalBytesToRead);
81-
result = new ValueTask<ArraySegment<byte>>(requestedBytes);
82-
return true;
36+
var readBytesCount = await byteHandler.ReadBytesAsync(buffer, ioBehavior).ConfigureAwait(false);
37+
if (readBytesCount == 0)
38+
{
39+
var data = m_remainingData;
40+
m_remainingData = default;
41+
return data;
42+
}
43+
44+
var bufferSize = buffer.Offset + readBytesCount;
45+
if (bufferSize >= totalBytesToRead)
46+
{
47+
var bufferBytes = new ArraySegment<byte>(buffer.Array, 0, bufferSize);
48+
var requestedBytes = bufferBytes.Slice(0, totalBytesToRead);
49+
m_remainingData = bufferBytes.Slice(totalBytesToRead);
50+
return requestedBytes;
51+
}
52+
53+
buffer = buffer.Slice(readBytesCount);
8354
}
84-
85-
buffer = new ArraySegment<byte>(buffer.Array, bufferSize, buffer.Array.Length - bufferSize);
86-
result = default(ValueTask<ArraySegment<byte>>);
87-
return false;
8855
}
8956

90-
ArraySegment<byte> m_remainingData;
9157
readonly byte[] m_buffer;
58+
ArraySegment<byte> m_remainingData;
9259
}
9360
}

0 commit comments

Comments
 (0)