Reduce HTTP/2 allocations (#6119)

- Remove per request allocations on the thread pool by implementing IThreadPoolWorkItem on Http2Stream
- Made generic version of Http2Stream to store the IHttpApplication instead of using a tuple
- Removed passing of IHttpApplication<TContext> everywhere
This commit is contained in:
David Fowler 2019-01-04 08:33:57 -08:00 committed by GitHub
parent 51047ef9d6
commit c61639b4a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 52 additions and 17 deletions

View File

@ -415,7 +415,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
case Http2FrameType.WINDOW_UPDATE: case Http2FrameType.WINDOW_UPDATE:
return ProcessWindowUpdateFrameAsync(); return ProcessWindowUpdateFrameAsync();
case Http2FrameType.CONTINUATION: case Http2FrameType.CONTINUATION:
return ProcessContinuationFrameAsync(application, payload); return ProcessContinuationFrameAsync(payload);
default: default:
return ProcessUnknownFrameAsync(); return ProcessUnknownFrameAsync();
} }
@ -558,7 +558,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
} }
// Start a new stream // Start a new stream
_currentHeadersStream = new Http2Stream(new Http2StreamContext _currentHeadersStream = new Http2Stream<TContext>(application, new Http2StreamContext
{ {
ConnectionId = ConnectionId, ConnectionId = ConnectionId,
StreamId = _incomingFrame.StreamId, StreamId = _incomingFrame.StreamId,
@ -580,7 +580,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_headerFlags = _incomingFrame.HeadersFlags; _headerFlags = _incomingFrame.HeadersFlags;
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding
return DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, headersPayload); return DecodeHeadersAsync(_incomingFrame.HeadersEndHeaders, headersPayload);
} }
} }
@ -822,7 +822,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task ProcessContinuationFrameAsync<TContext>(IHttpApplication<TContext> application, ReadOnlySequence<byte> payload) private Task ProcessContinuationFrameAsync(ReadOnlySequence<byte> payload)
{ {
if (_currentHeadersStream == null) if (_currentHeadersStream == null)
{ {
@ -847,7 +847,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
TimeoutControl.CancelTimeout(); TimeoutControl.CancelTimeout();
} }
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, payload); return DecodeHeadersAsync(_incomingFrame.ContinuationEndHeaders, payload);
} }
} }
@ -861,7 +861,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, ReadOnlySequence<byte> payload) private Task DecodeHeadersAsync(bool endHeaders, ReadOnlySequence<byte> payload)
{ {
try try
{ {
@ -870,7 +870,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if (endHeaders) if (endHeaders)
{ {
StartStream(application); StartStream();
ResetRequestHeaderParsingState(); ResetRequestHeaderParsingState();
} }
} }
@ -896,7 +896,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask; return Task.CompletedTask;
} }
private void StartStream<TContext>(IHttpApplication<TContext> application) private void StartStream()
{ {
if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields) if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
{ {
@ -923,12 +923,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_activeStreamCount++; _activeStreamCount++;
_streams[_incomingFrame.StreamId] = _currentHeadersStream; _streams[_incomingFrame.StreamId] = _currentHeadersStream;
// Must not allow app code to block the connection handling loop. // Must not allow app code to block the connection handling loop.
ThreadPool.UnsafeQueueUserWorkItem(state => ThreadPool.UnsafeQueueUserWorkItem(_currentHeadersStream, preferLocal: false);
{
var (app, currentStream) = (Tuple<IHttpApplication<TContext>, Http2Stream>)state;
_ = currentStream.ProcessRequestsAsync(app);
},
new Tuple<IHttpApplication<TContext>, Http2Stream>(application, _currentHeadersStream));
} }
private void ResetRequestHeaderParsingState() private void ResetRequestHeaderParsingState()

View File

@ -6,6 +6,7 @@ using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
@ -17,7 +18,7 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
public partial class Http2Stream : HttpProtocol public abstract partial class Http2Stream : HttpProtocol, IThreadPoolWorkItem
{ {
private readonly Http2StreamContext _context; private readonly Http2StreamContext _context;
private readonly Http2OutputProducer _http2Output; private readonly Http2OutputProducer _http2Output;
@ -499,6 +500,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
} }
} }
/// <summary>
/// Used to kick off the request processing loop by derived classes.
/// </summary>
public abstract void Execute();
[Flags] [Flags]
private enum StreamCompletionFlags private enum StreamCompletionFlags
{ {

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Hosting.Server;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2Stream<TContext> : Http2Stream
{
private readonly IHttpApplication<TContext> _application;
public Http2Stream(IHttpApplication<TContext> application, Http2StreamContext context) : base(context)
{
_application = application;
}
public override void Execute()
{
// REVIEW: Should we store this in a field for easy debugging?
_ = ProcessRequestsAsync(_application);
}
}
}

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_http1Connection.Reset(); _http1Connection.Reset();
_collection = _http1Connection; _collection = _http1Connection;
var http2Stream = new Http2Stream(context); var http2Stream = new TestHttp2Stream(context);
http2Stream.Reset(); http2Stream.Reset();
_http2Collection = http2Stream; _http2Collection = http2Stream;
} }
@ -220,5 +220,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
} }
private Http1Connection CreateHttp1Connection() => new TestHttp1Connection(_httpConnectionContext); private Http1Connection CreateHttp1Connection() => new TestHttp1Connection(_httpConnectionContext);
private class TestHttp2Stream : Http2Stream
{
public TestHttp2Stream(Http2StreamContext context) : base(context)
{
}
public override void Execute()
{
}
}
} }
} }