From df2019695c0934b32236695831e1f3b58df98f66 Mon Sep 17 00:00:00 2001 From: Geoff Kizer Date: Mon, 14 Aug 2017 01:27:14 -0700 Subject: [PATCH] avoid async overhead in ReadNextLineAsync when possible --- .../Http/Managed/ChunkedEncodingReadStream.cs | 11 ++- .../System/Net/Http/Managed/HttpConnection.cs | 90 ++++++++++--------- 2 files changed, 60 insertions(+), 41 deletions(-) diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/ChunkedEncodingReadStream.cs b/src/System.Net.Http/src/System/Net/Http/Managed/ChunkedEncodingReadStream.cs index 161282c94662..4f78b98fe48a 100644 --- a/src/System.Net.Http/src/System/Net/Http/Managed/ChunkedEncodingReadStream.cs +++ b/src/System.Net.Http/src/System/Net/Http/Managed/ChunkedEncodingReadStream.cs @@ -26,7 +26,16 @@ private async Task TryGetNextChunk(CancellationToken cancellationToken) Debug.Assert(_chunkBytesRemaining == 0); // Start of chunk, read chunk size. - ulong chunkSize = ParseHexSize(await _connection.ReadNextLineAsync(cancellationToken).ConfigureAwait(false)); + ArraySegment line; + while (!_connection.TryReadNextLine(out line)) + { + if (!await _connection.FillAsync(cancellationToken).ConfigureAwait(false)) + { + throw new IOException(SR.net_http_invalid_response); + } + } + + ulong chunkSize = ParseHexSize(line); _chunkBytesRemaining = chunkSize; if (chunkSize > 0) diff --git a/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnection.cs b/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnection.cs index 3e2f7b0be3c3..7252743a639c 100644 --- a/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnection.cs +++ b/src/System.Net.Http/src/System/Net/Http/Managed/HttpConnection.cs @@ -275,14 +275,33 @@ await WriteStringAsync( // Parse the response status line and headers var response = new HttpResponseMessage() { RequestMessage = request, Content = new HttpConnectionContent(CancellationToken.None) }; - ParseStatusLine(await ReadNextLineAsync(cancellationToken).ConfigureAwait(false), response); + + ArraySegment line; + while (!TryReadNextLine(out line)) + { + if (!await FillAsync(cancellationToken).ConfigureAwait(false)) + { + throw new IOException(SR.net_http_invalid_response); + } + } + + ParseStatusLine(line, response); + while (true) { - ArraySegment line = await ReadNextLineAsync(cancellationToken).ConfigureAwait(false); + while (!TryReadNextLine(out line)) + { + if (!await FillAsync(cancellationToken).ConfigureAwait(false)) + { + throw new IOException(SR.net_http_invalid_response); + } + } + if (line[0] == '\r') { break; } + ParseHeaderNameValue(line, response); } @@ -639,44 +658,33 @@ private Task WriteToStreamAsync(byte[] buffer, int offset, int count, Cancellati return _stream.WriteAsync(buffer, offset, count, cancellationToken); } - private async ValueTask> ReadNextLineAsync(CancellationToken cancellationToken) + private bool TryReadNextLine(out ArraySegment line) { - int searchOffset = 0; - while (true) + int remaining = _readLength - _readOffset; + int crPos = Array.IndexOf(_readBuffer, (byte)'\r', _readOffset, remaining); + if (crPos < 0) { - int remaining = _readLength - _readOffset; - int startIndex = _readOffset + searchOffset; - int length = _readLength - startIndex; - int crPos = Array.IndexOf(_readBuffer, (byte)'\r', startIndex, length); - if (crPos < 0) - { - // Couldn't find a \r. Read more. - searchOffset = length; - await FillAsync(cancellationToken); - } - else if (crPos + 1 >= _readLength) - { - // We found a \r, but we don't have enough data buffered to read the \n. - searchOffset = length - 1; - await FillAsync(cancellationToken).ConfigureAwait(false); - } - else if (_readBuffer[crPos + 1] == '\n') - { - // We found a \r\n. Return the data up to and including it. - int lineLength = crPos - _readOffset + 2; - var result = new ArraySegment(_readBuffer, _readOffset, lineLength); - _readOffset += lineLength; - return result; - } - else - { - ThrowInvalidHttpResponse(); - } - - if (remaining == _readLength - _readOffset) - { - throw new IOException(SR.net_http_invalid_response); - } + // Couldn't find a \r. + line = default(ArraySegment); + return false; + } + else if (crPos + 1 >= _readLength) + { + // We found a \r, but we don't have enough data buffered to read the \n. + line = default(ArraySegment); + return false; + } + else if (_readBuffer[crPos + 1] == '\n') + { + // We found a \r\n. Return the data up to and including it. + int lineLength = crPos - _readOffset + 2; + line = new ArraySegment(_readBuffer, _readOffset, lineLength); + _readOffset += lineLength; + return true; + } + else + { + throw new HttpRequestException(SR.net_http_invalid_response); } } @@ -714,7 +722,8 @@ private async Task ReadCrLfAsync(CancellationToken cancellationToken) throw new IOException(SR.net_http_invalid_response); } - private Task FillAsync(CancellationToken cancellationToken) + // Returns false on EOF. + private Task FillAsync(CancellationToken cancellationToken) { int remaining = _readLength - _readOffset; Debug.Assert(remaining >= 0); @@ -771,7 +780,7 @@ private Task FillAsync(CancellationToken cancellationToken) int bytesRead = t.GetAwaiter().GetResult(); if (NetEventSource.IsEnabled) Trace($"Received {bytesRead} bytes."); _readLength += bytesRead; - return Task.CompletedTask; + return Task.FromResult(bytesRead > 0); } else { @@ -783,6 +792,7 @@ private Task FillAsync(CancellationToken cancellationToken) int bytesRead = completed.GetAwaiter().GetResult(); if (NetEventSource.IsEnabled) innerConnection.Trace($"Received {bytesRead} bytes."); innerConnection._readLength += bytesRead; + return (bytesRead > 0); }, this, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } }