Read server response in Http2ConnectionBenchmark (#19694)
This commit is contained in:
parent
cd6e6ae0bc
commit
92e98b7ede
|
|
@ -3,9 +3,11 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net.Http.HPack;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -25,20 +27,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
public class Http2ConnectionBenchmark
|
||||
{
|
||||
private MemoryPool<byte> _memoryPool;
|
||||
private Pipe _pipe;
|
||||
private HttpRequestHeaders _httpRequestHeaders;
|
||||
private Http2Connection _connection;
|
||||
private Http2HeadersEnumerator _requestHeadersEnumerator;
|
||||
private int _currentStreamId;
|
||||
private byte[] _headersBuffer;
|
||||
private DuplexPipe.DuplexPipePair _connectionPair;
|
||||
private Http2Frame _httpFrame;
|
||||
private string _responseData;
|
||||
private int _dataWritten;
|
||||
|
||||
[Params(0, 10, 1024 * 1024)]
|
||||
public int ResponseDataLength { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_memoryPool = SlabMemoryPoolFactory.Create();
|
||||
_httpFrame = new Http2Frame();
|
||||
_responseData = new string('!', ResponseDataLength);
|
||||
|
||||
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
|
||||
_pipe = new Pipe(options);
|
||||
|
||||
_connectionPair = DuplexPipe.CreateConnectionPair(options, options);
|
||||
|
||||
_httpRequestHeaders = new HttpRequestHeaders();
|
||||
_httpRequestHeaders.Append(HeaderNames.Method, new StringValues("GET"));
|
||||
|
|
@ -55,15 +66,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
Log = new KestrelTrace(NullLogger.Instance),
|
||||
SystemClock = new MockSystemClock()
|
||||
};
|
||||
serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = int.MaxValue;
|
||||
serviceContext.DateHeaderValueManager.OnHeartbeat(default);
|
||||
|
||||
_connection = new Http2Connection(new HttpConnectionContext
|
||||
{
|
||||
MemoryPool = _memoryPool,
|
||||
ConnectionId = "TestConnectionId",
|
||||
Protocols = Core.HttpProtocols.Http2,
|
||||
Transport = new MockDuplexPipe(_pipe.Reader, new NullPipeWriter()),
|
||||
Protocols = HttpProtocols.Http2,
|
||||
Transport = _connectionPair.Transport,
|
||||
ServiceContext = serviceContext,
|
||||
ConnectionFeatures = new FeatureCollection(),
|
||||
TimeoutControl = new MockTimeoutControl(),
|
||||
|
|
@ -73,11 +83,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
|
||||
_currentStreamId = 1;
|
||||
|
||||
_ = _connection.ProcessRequestsAsync(new DummyApplication());
|
||||
_ = _connection.ProcessRequestsAsync(new DummyApplication(c => ResponseDataLength == 0 ? Task.CompletedTask : c.Response.WriteAsync(_responseData), new MockHttpContextFactory()));
|
||||
|
||||
_pipe.Writer.Write(Http2Connection.ClientPreface);
|
||||
_pipe.Writer.WriteSettings(new Http2PeerSettings());
|
||||
_pipe.Writer.FlushAsync().GetAwaiter().GetResult();
|
||||
_connectionPair.Application.Output.Write(Http2Connection.ClientPreface);
|
||||
_connectionPair.Application.Output.WriteSettings(new Http2PeerSettings
|
||||
{
|
||||
InitialWindowSize = 2147483647
|
||||
});
|
||||
_connectionPair.Application.Output.FlushAsync().GetAwaiter().GetResult();
|
||||
|
||||
// Read past connection setup frames
|
||||
ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame).GetAwaiter().GetResult();
|
||||
Debug.Assert(_httpFrame.Type == Http2FrameType.SETTINGS);
|
||||
ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame).GetAwaiter().GetResult();
|
||||
Debug.Assert(_httpFrame.Type == Http2FrameType.WINDOW_UPDATE);
|
||||
ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame).GetAwaiter().GetResult();
|
||||
Debug.Assert(_httpFrame.Type == Http2FrameType.SETTINGS);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
|
|
@ -85,15 +106,77 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
{
|
||||
_requestHeadersEnumerator.Initialize(_httpRequestHeaders);
|
||||
_requestHeadersEnumerator.MoveNext();
|
||||
_pipe.Writer.WriteStartStream(streamId: _currentStreamId, _requestHeadersEnumerator, _headersBuffer, endStream: true);
|
||||
_connectionPair.Application.Output.WriteStartStream(streamId: _currentStreamId, _requestHeadersEnumerator, _headersBuffer, endStream: true, frame: _httpFrame);
|
||||
await _connectionPair.Application.Output.FlushAsync();
|
||||
|
||||
while (true)
|
||||
{
|
||||
await ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame);
|
||||
|
||||
if (_httpFrame.StreamId != _currentStreamId && _httpFrame.StreamId != 0)
|
||||
{
|
||||
throw new Exception($"Unexpected stream ID: {_httpFrame.StreamId}");
|
||||
}
|
||||
|
||||
if (_httpFrame.Type == Http2FrameType.DATA)
|
||||
{
|
||||
_dataWritten += _httpFrame.DataPayloadLength;
|
||||
}
|
||||
|
||||
if (_dataWritten > 1024 * 32)
|
||||
{
|
||||
_connectionPair.Application.Output.WriteWindowUpdateAsync(streamId: 0, _dataWritten, _httpFrame);
|
||||
await _connectionPair.Application.Output.FlushAsync();
|
||||
|
||||
_dataWritten = 0;
|
||||
}
|
||||
|
||||
if ((_httpFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_currentStreamId += 2;
|
||||
await _pipe.Writer.FlushAsync();
|
||||
}
|
||||
|
||||
internal async ValueTask ReceiveFrameAsync(PipeReader pipeReader, Http2Frame frame, uint maxFrameSize = Http2PeerSettings.DefaultMaxFrameSize)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var result = await pipeReader.ReadAsync();
|
||||
var buffer = result.Buffer;
|
||||
var consumed = buffer.Start;
|
||||
var examined = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (Http2FrameReader.TryReadFrame(ref buffer, frame, maxFrameSize, out var framePayload))
|
||||
{
|
||||
consumed = examined = framePayload.End;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
examined = buffer.End;
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
throw new IOException("The reader completed without returning a frame.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
pipeReader.AdvanceTo(consumed, examined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Dispose()
|
||||
{
|
||||
_pipe.Writer.Complete();
|
||||
_connectionPair.Application.Output.Complete();
|
||||
_memoryPool?.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||
{
|
||||
public class MockHttpContextFactory : IHttpContextFactory
|
||||
{
|
||||
private readonly object _lock = new object();
|
||||
private readonly Queue<DefaultHttpContext> _cache = new Queue<DefaultHttpContext>();
|
||||
|
||||
public HttpContext Create(IFeatureCollection featureCollection)
|
||||
{
|
||||
DefaultHttpContext httpContext;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_cache.TryDequeue(out httpContext))
|
||||
{
|
||||
httpContext = new DefaultHttpContext();
|
||||
}
|
||||
}
|
||||
|
||||
httpContext.Initialize(featureCollection);
|
||||
return httpContext;
|
||||
}
|
||||
|
||||
public void Dispose(HttpContext httpContext)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var defaultHttpContext = (DefaultHttpContext)httpContext;
|
||||
|
||||
defaultHttpContext.Uninitialize();
|
||||
_cache.Enqueue(defaultHttpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net.Http.HPack;
|
||||
|
|
@ -24,9 +25,9 @@ namespace Microsoft.AspNetCore.Testing
|
|||
writer.Write(payload);
|
||||
}
|
||||
|
||||
public static void WriteStartStream(this PipeWriter writer, int streamId, Http2HeadersEnumerator headers, byte[] headerEncodingBuffer, bool endStream)
|
||||
public static void WriteStartStream(this PipeWriter writer, int streamId, Http2HeadersEnumerator headers, byte[] headerEncodingBuffer, bool endStream, Http2Frame frame = null)
|
||||
{
|
||||
var frame = new Http2Frame();
|
||||
frame ??= new Http2Frame();
|
||||
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
|
||||
|
||||
var buffer = headerEncodingBuffer.AsSpan();
|
||||
|
|
@ -63,9 +64,9 @@ namespace Microsoft.AspNetCore.Testing
|
|||
}
|
||||
}
|
||||
|
||||
public static void WriteStartStream(this PipeWriter writer, int streamId, Span<byte> headerData, bool endStream)
|
||||
public static void WriteStartStream(this PipeWriter writer, int streamId, Span<byte> headerData, bool endStream, Http2Frame frame = null)
|
||||
{
|
||||
var frame = new Http2Frame();
|
||||
frame ??= new Http2Frame();
|
||||
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
|
||||
frame.PayloadLength = headerData.Length;
|
||||
frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
|
||||
|
|
@ -79,10 +80,9 @@ namespace Microsoft.AspNetCore.Testing
|
|||
writer.Write(headerData);
|
||||
}
|
||||
|
||||
public static void WriteData(this PipeWriter writer, int streamId, Memory<byte> data, bool endStream)
|
||||
public static void WriteData(this PipeWriter writer, int streamId, Memory<byte> data, bool endStream, Http2Frame frame = null)
|
||||
{
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame ??= new Http2Frame();
|
||||
frame.PrepareData(streamId);
|
||||
frame.PayloadLength = data.Length;
|
||||
frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE;
|
||||
|
|
@ -90,5 +90,14 @@ namespace Microsoft.AspNetCore.Testing
|
|||
Http2FrameWriter.WriteHeader(frame, writer);
|
||||
writer.Write(data.Span);
|
||||
}
|
||||
|
||||
public static void WriteWindowUpdateAsync(this PipeWriter writer, int streamId, int sizeIncrement, Http2Frame frame = null)
|
||||
{
|
||||
frame ??= new Http2Frame();
|
||||
frame.PrepareWindowUpdate(streamId, sizeIncrement);
|
||||
Http2FrameWriter.WriteHeader(frame, writer);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(writer.GetSpan(4), (uint)sizeIncrement);
|
||||
writer.Advance(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Buffers;
|
|||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
|
|
@ -1051,12 +1052,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
protected Task SendWindowUpdateAsync(int streamId, int sizeIncrement)
|
||||
{
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareWindowUpdate(streamId, sizeIncrement);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
var buffer = outputWriter.GetSpan(4);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)sizeIncrement);
|
||||
outputWriter.Advance(4);
|
||||
outputWriter.WriteWindowUpdateAsync(streamId, sizeIncrement);
|
||||
return FlushAsync(outputWriter);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
|
|||
Loading…
Reference in New Issue