Skip to content

Commit 40a2d4a

Browse files
committed
Reduce HTTP/2 allocations
- 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
1 parent 51047ef commit 40a2d4a

File tree

4 files changed

+52
-17
lines changed

4 files changed

+52
-17
lines changed

src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ private Task ProcessFrameAsync<TContext>(IHttpApplication<TContext> application,
415415
case Http2FrameType.WINDOW_UPDATE:
416416
return ProcessWindowUpdateFrameAsync();
417417
case Http2FrameType.CONTINUATION:
418-
return ProcessContinuationFrameAsync(application, payload);
418+
return ProcessContinuationFrameAsync(payload);
419419
default:
420420
return ProcessUnknownFrameAsync();
421421
}
@@ -558,7 +558,7 @@ private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> appli
558558
}
559559

560560
// Start a new stream
561-
_currentHeadersStream = new Http2Stream(new Http2StreamContext
561+
_currentHeadersStream = new Http2Stream<TContext>(application, new Http2StreamContext
562562
{
563563
ConnectionId = ConnectionId,
564564
StreamId = _incomingFrame.StreamId,
@@ -580,7 +580,7 @@ private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> appli
580580
_headerFlags = _incomingFrame.HeadersFlags;
581581

582582
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding
583-
return DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, headersPayload);
583+
return DecodeHeadersAsync(_incomingFrame.HeadersEndHeaders, headersPayload);
584584
}
585585
}
586586

@@ -822,7 +822,7 @@ private Task ProcessWindowUpdateFrameAsync()
822822
return Task.CompletedTask;
823823
}
824824

825-
private Task ProcessContinuationFrameAsync<TContext>(IHttpApplication<TContext> application, ReadOnlySequence<byte> payload)
825+
private Task ProcessContinuationFrameAsync(ReadOnlySequence<byte> payload)
826826
{
827827
if (_currentHeadersStream == null)
828828
{
@@ -847,7 +847,7 @@ private Task ProcessContinuationFrameAsync<TContext>(IHttpApplication<TContext>
847847
TimeoutControl.CancelTimeout();
848848
}
849849

850-
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, payload);
850+
return DecodeHeadersAsync(_incomingFrame.ContinuationEndHeaders, payload);
851851
}
852852
}
853853

@@ -861,7 +861,7 @@ private Task ProcessUnknownFrameAsync()
861861
return Task.CompletedTask;
862862
}
863863

864-
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, ReadOnlySequence<byte> payload)
864+
private Task DecodeHeadersAsync(bool endHeaders, ReadOnlySequence<byte> payload)
865865
{
866866
try
867867
{
@@ -870,7 +870,7 @@ private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application
870870

871871
if (endHeaders)
872872
{
873-
StartStream(application);
873+
StartStream();
874874
ResetRequestHeaderParsingState();
875875
}
876876
}
@@ -896,7 +896,7 @@ private Task DecodeTrailersAsync(bool endHeaders, ReadOnlySequence<byte> payload
896896
return Task.CompletedTask;
897897
}
898898

899-
private void StartStream<TContext>(IHttpApplication<TContext> application)
899+
private void StartStream()
900900
{
901901
if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
902902
{
@@ -923,12 +923,7 @@ private void StartStream<TContext>(IHttpApplication<TContext> application)
923923
_activeStreamCount++;
924924
_streams[_incomingFrame.StreamId] = _currentHeadersStream;
925925
// Must not allow app code to block the connection handling loop.
926-
ThreadPool.UnsafeQueueUserWorkItem(state =>
927-
{
928-
var (app, currentStream) = (Tuple<IHttpApplication<TContext>, Http2Stream>)state;
929-
_ = currentStream.ProcessRequestsAsync(app);
930-
},
931-
new Tuple<IHttpApplication<TContext>, Http2Stream>(application, _currentHeadersStream));
926+
ThreadPool.UnsafeQueueUserWorkItem(_currentHeadersStream, preferLocal: false);
932927
}
933928

934929
private void ResetRequestHeaderParsingState()

src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Diagnostics;
77
using System.IO;
88
using System.IO.Pipelines;
9+
using System.Threading;
910
using System.Threading.Tasks;
1011
using Microsoft.AspNetCore.Connections;
1112
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
@@ -17,7 +18,7 @@
1718

1819
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
1920
{
20-
public partial class Http2Stream : HttpProtocol
21+
public abstract partial class Http2Stream : HttpProtocol, IThreadPoolWorkItem
2122
{
2223
private readonly Http2StreamContext _context;
2324
private readonly Http2OutputProducer _http2Output;
@@ -499,6 +500,11 @@ private Pipe CreateRequestBodyPipe(uint windowSize)
499500
}
500501
}
501502

503+
/// <summary>
504+
/// Used to kick off the request processing loop by derived classes.
505+
/// </summary>
506+
public abstract void Execute();
507+
502508
[Flags]
503509
private enum StreamCompletionFlags
504510
{
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using Microsoft.AspNetCore.Hosting.Server;
5+
6+
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
7+
{
8+
public class Http2Stream<TContext> : Http2Stream
9+
{
10+
private readonly IHttpApplication<TContext> _application;
11+
12+
public Http2Stream(IHttpApplication<TContext> application, Http2StreamContext context) : base(context)
13+
{
14+
_application = application;
15+
}
16+
17+
public override void Execute()
18+
{
19+
// REVIEW: Should we store this in a field for easy debugging?
20+
_ = ProcessRequestsAsync(_application);
21+
}
22+
}
23+
}

src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -40,7 +40,7 @@ public HttpProtocolFeatureCollectionTests()
4040
_http1Connection.Reset();
4141
_collection = _http1Connection;
4242

43-
var http2Stream = new Http2Stream(context);
43+
var http2Stream = new TestHttp2Stream(context);
4444
http2Stream.Reset();
4545
_http2Collection = http2Stream;
4646
}
@@ -220,5 +220,16 @@ private int SetFeaturesToNonDefault()
220220
}
221221

222222
private Http1Connection CreateHttp1Connection() => new TestHttp1Connection(_httpConnectionContext);
223+
224+
private class TestHttp2Stream : Http2Stream
225+
{
226+
public TestHttp2Stream(Http2StreamContext context) : base(context)
227+
{
228+
}
229+
230+
public override void Execute()
231+
{
232+
}
233+
}
223234
}
224235
}

0 commit comments

Comments
 (0)