Skip to content

Commit 42e17be

Browse files
committed
Fix StackOverflowException. Fixes #239
1 parent b26876f commit 42e17be

File tree

1 file changed

+54
-22
lines changed

1 file changed

+54
-22
lines changed

src/MySqlConnector/Protocol/Serialization/BufferedByteReader.cs

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ public BufferedByteReader()
1212

1313
public ValueTask<ArraySegment<byte>> ReadBytesAsync(IByteHandler byteHandler, int count, IOBehavior ioBehavior)
1414
{
15+
// check if read can be satisfied from the buffer
1516
if (m_remainingData.Count >= count)
1617
{
1718
var readBytes = m_remainingData.Slice(0, count);
1819
m_remainingData = m_remainingData.Slice(count);
1920
return new ValueTask<ArraySegment<byte>>(readBytes);
2021
}
2122

23+
// get a buffer big enough to hold all the data, and move any buffered data to the beginning
2224
var buffer = count > m_buffer.Length ? new byte[count] : m_buffer;
2325
if (m_remainingData.Count > 0)
2426
{
@@ -29,29 +31,59 @@ public ValueTask<ArraySegment<byte>> ReadBytesAsync(IByteHandler byteHandler, in
2931
return ReadBytesAsync(byteHandler, new ArraySegment<byte>(buffer, m_remainingData.Count, buffer.Length - m_remainingData.Count), count, ioBehavior);
3032
}
3133

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

5789
ArraySegment<byte> m_remainingData;

0 commit comments

Comments
 (0)