Skip to content

Commit bc6fb44

Browse files
authored
Start pooling Http2Streams (#18601)
1 parent 602f467 commit bc6fb44

File tree

11 files changed

+229
-45
lines changed

11 files changed

+229
-45
lines changed

src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ internal partial class Http1Connection : HttpProtocol, IRequestProcessor
3737
private int _remainingRequestHeadersBytesAllowed;
3838

3939
public Http1Connection(HttpConnectionContext context)
40-
: base(context)
4140
{
41+
Initialize(context);
42+
4243
_context = context;
4344
_parser = ServiceContext.HttpParser;
4445
_keepAliveTicks = ServerOptions.Limits.KeepAliveTimeout.Ticks;

src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ internal abstract partial class HttpProtocol : IHttpResponseControl
3737
private Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;
3838

3939
private readonly object _abortLock = new object();
40-
private volatile bool _connectionAborted;
40+
protected volatile bool _connectionAborted;
4141
private bool _preventRequestAbortedCancellation;
4242
private CancellationTokenSource _abortedCts;
4343
private CancellationToken? _manuallySetRequestAbortToken;
@@ -66,7 +66,7 @@ internal abstract partial class HttpProtocol : IHttpResponseControl
6666

6767
private long _responseBytesWritten;
6868

69-
private readonly HttpConnectionContext _context;
69+
private HttpConnectionContext _context;
7070
private RouteValueDictionary _routeValues;
7171
private Endpoint _endpoint;
7272

@@ -75,12 +75,16 @@ internal abstract partial class HttpProtocol : IHttpResponseControl
7575
private Stream _requestStreamInternal;
7676
private Stream _responseStreamInternal;
7777

78-
public HttpProtocol(HttpConnectionContext context)
78+
public void Initialize(HttpConnectionContext context)
7979
{
8080
_context = context;
8181

8282
ServerOptions = ServiceContext.ServerOptions;
83-
HttpRequestHeaders = new HttpRequestHeaders(reuseHeaderValues: !ServerOptions.DisableStringReuse);
83+
84+
Reset();
85+
86+
HttpRequestHeaders.ReuseHeaderValues = !ServerOptions.DisableStringReuse;
87+
8488
HttpResponseControl = this;
8589
}
8690

@@ -97,7 +101,7 @@ public HttpProtocol(HttpConnectionContext context)
97101
protected IKestrelTrace Log => ServiceContext.Log;
98102
private DateHeaderValueManager DateHeaderValueManager => ServiceContext.DateHeaderValueManager;
99103
// Hold direct reference to ServerOptions since this is used very often in the request processing path
100-
protected KestrelServerOptions ServerOptions { get; }
104+
protected KestrelServerOptions ServerOptions { get; set; }
101105
protected string ConnectionId => _context.ConnectionId;
102106

103107
public string ConnectionIdFeature { get; set; }
@@ -306,7 +310,7 @@ public CancellationToken RequestAborted
306310

307311
public bool HasResponseCompleted => _requestProcessingStatus == RequestProcessingStatus.ResponseCompleted;
308312

309-
protected HttpRequestHeaders HttpRequestHeaders { get; }
313+
protected HttpRequestHeaders HttpRequestHeaders { get; } = new HttpRequestHeaders();
310314

311315
protected HttpResponseHeaders HttpResponseHeaders { get; } = new HttpResponseHeaders();
312316

@@ -361,7 +365,6 @@ public void Reset()
361365
var remoteEndPoint = RemoteEndPoint;
362366
RemoteIpAddress = remoteEndPoint?.Address;
363367
RemotePort = remoteEndPoint?.Port ?? 0;
364-
365368
var localEndPoint = LocalEndPoint;
366369
LocalIpAddress = localEndPoint?.Address;
367370
LocalPort = localEndPoint?.Port ?? 0;
@@ -373,6 +376,7 @@ public void Reset()
373376
RequestHeaders = HttpRequestHeaders;
374377
ResponseHeaders = HttpResponseHeaders;
375378
RequestTrailers.Clear();
379+
ResponseTrailers?.Reset();
376380
RequestTrailersAvailable = false;
377381

378382
_isLeasedMemoryInvalid = true;

src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestHeaders.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
1414
{
1515
internal sealed partial class HttpRequestHeaders : HttpHeaders
1616
{
17-
private readonly bool _reuseHeaderValues;
1817
private long _previousBits = 0;
1918

2019
public HttpRequestHeaders(bool reuseHeaderValues = true)
2120
{
22-
_reuseHeaderValues = reuseHeaderValues;
21+
ReuseHeaderValues = reuseHeaderValues;
2322
}
2423

24+
public bool ReuseHeaderValues { get; set; }
25+
2526
public void OnHeadersComplete()
2627
{
2728
var bitsToClear = _previousBits & ~_bits;
@@ -40,7 +41,7 @@ public void OnHeadersComplete()
4041

4142
protected override void ClearFast()
4243
{
43-
if (!_reuseHeaderValues)
44+
if (!ReuseHeaderValues)
4445
{
4546
// If we aren't reusing headers clear them all
4647
Clear(_bits);

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

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
using System.Diagnostics;
99
using System.IO;
1010
using System.IO.Pipelines;
11-
using System.Net.Http;
1211
using System.Net.Http.HPack;
1312
using System.Runtime.CompilerServices;
1413
using System.Security.Authentication;
15-
using System.Text;
1614
using System.Threading;
1715
using System.Threading.Tasks;
1816
using Microsoft.AspNetCore.Connections;
@@ -23,7 +21,6 @@
2321
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl;
2422
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
2523
using Microsoft.Extensions.Logging;
26-
using Microsoft.Net.Http.Headers;
2724

2825
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
2926
{
@@ -67,6 +64,11 @@ internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeade
6764
private int _gracefulCloseInitiator;
6865
private int _isClosed;
6966

67+
private Http2StreamStack _streamPool;
68+
69+
internal const int InitialStreamPoolSize = 5;
70+
internal const int MaxStreamPoolSize = 40;
71+
7072
public Http2Connection(HttpConnectionContext context)
7173
{
7274
var httpLimits = context.ServiceContext.ServerOptions.Limits;
@@ -106,6 +108,10 @@ public Http2Connection(HttpConnectionContext context)
106108
_serverSettings.HeaderTableSize = (uint)http2Limits.HeaderTableSize;
107109
_serverSettings.MaxHeaderListSize = (uint)httpLimits.MaxRequestHeadersTotalSize;
108110
_serverSettings.InitialWindowSize = (uint)http2Limits.InitialStreamWindowSize;
111+
112+
// Start pool off at a smaller size if the max number of streams is less than the InitialStreamPoolSize
113+
_streamPool = new Http2StreamStack(Math.Min(InitialStreamPoolSize, http2Limits.MaxStreamsPerConnection));
114+
109115
_inputTask = ReadInputAsync();
110116
}
111117

@@ -554,32 +560,57 @@ private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> appli
554560
}
555561

556562
// Start a new stream
557-
_currentHeadersStream = new Http2Stream<TContext>(application, new Http2StreamContext
558-
{
559-
ConnectionId = ConnectionId,
560-
StreamId = _incomingFrame.StreamId,
561-
ServiceContext = _context.ServiceContext,
562-
ConnectionFeatures = _context.ConnectionFeatures,
563-
MemoryPool = _context.MemoryPool,
564-
LocalEndPoint = _context.LocalEndPoint,
565-
RemoteEndPoint = _context.RemoteEndPoint,
566-
StreamLifetimeHandler = this,
567-
ClientPeerSettings = _clientSettings,
568-
ServerPeerSettings = _serverSettings,
569-
FrameWriter = _frameWriter,
570-
ConnectionInputFlowControl = _inputFlowControl,
571-
ConnectionOutputFlowControl = _outputFlowControl,
572-
TimeoutControl = TimeoutControl,
573-
});
574-
575-
_currentHeadersStream.Reset();
563+
_currentHeadersStream = GetStream(application);
564+
576565
_headerFlags = _incomingFrame.HeadersFlags;
577566

578567
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding
579568
return DecodeHeadersAsync(_incomingFrame.HeadersEndHeaders, headersPayload);
580569
}
581570
}
582571

572+
private Http2Stream GetStream<TContext>(IHttpApplication<TContext> application)
573+
{
574+
if (_streamPool.TryPop(out var stream))
575+
{
576+
stream.InitializeWithExistingContext(_incomingFrame.StreamId);
577+
return stream;
578+
}
579+
580+
return new Http2Stream<TContext>(
581+
application,
582+
CreateHttp2StreamContext());
583+
}
584+
585+
private Http2StreamContext CreateHttp2StreamContext()
586+
{
587+
return new Http2StreamContext
588+
{
589+
ConnectionId = ConnectionId,
590+
StreamId = _incomingFrame.StreamId,
591+
ServiceContext = _context.ServiceContext,
592+
ConnectionFeatures = _context.ConnectionFeatures,
593+
MemoryPool = _context.MemoryPool,
594+
LocalEndPoint = _context.LocalEndPoint,
595+
RemoteEndPoint = _context.RemoteEndPoint,
596+
StreamLifetimeHandler = this,
597+
ClientPeerSettings = _clientSettings,
598+
ServerPeerSettings = _serverSettings,
599+
FrameWriter = _frameWriter,
600+
ConnectionInputFlowControl = _inputFlowControl,
601+
ConnectionOutputFlowControl = _outputFlowControl,
602+
TimeoutControl = TimeoutControl,
603+
};
604+
}
605+
606+
private void ReturnStream(Http2Stream stream)
607+
{
608+
if (_streamPool.Count < MaxStreamPoolSize)
609+
{
610+
_streamPool.Push(stream);
611+
}
612+
}
613+
583614
private Task ProcessPriorityFrameAsync()
584615
{
585616
if (_currentHeadersStream != null)
@@ -1028,6 +1059,7 @@ private void UpdateCompletedStreams()
10281059
}
10291060

10301061
_streams.Remove(stream.StreamId);
1062+
ReturnStream(stream);
10311063
}
10321064
else
10331065
{
@@ -1054,6 +1086,7 @@ private void MakeSpaceInDrainQueue()
10541086
}
10551087

10561088
_streams.Remove(stream.StreamId);
1089+
ReturnStream(stream);
10571090
}
10581091
}
10591092

@@ -1400,6 +1433,7 @@ private enum PseudoHeaderFields
14001433
Unknown = 0x40000000
14011434
}
14021435

1436+
14031437
private static class GracefulCloseInitiator
14041438
{
14051439
public const int None = 0;

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

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
1919
{
2020
internal abstract partial class Http2Stream : HttpProtocol, IThreadPoolWorkItem
2121
{
22-
private readonly Http2StreamContext _context;
23-
private readonly Http2OutputProducer _http2Output;
24-
private readonly StreamInputFlowControl _inputFlowControl;
25-
private readonly StreamOutputFlowControl _outputFlowControl;
22+
private Http2StreamContext _context;
23+
private Http2OutputProducer _http2Output;
24+
private StreamInputFlowControl _inputFlowControl;
25+
private StreamOutputFlowControl _outputFlowControl;
2626

2727
private bool _decrementCalled;
28-
public Pipe RequestBodyPipe { get; }
28+
29+
public Pipe RequestBodyPipe { get; set; }
2930

3031
internal long DrainExpirationTicks { get; set; }
3132

3233
private StreamCompletionFlags _completionState;
3334
private readonly object _completionLock = new object();
3435

35-
public Http2Stream(Http2StreamContext context)
36-
: base(context)
36+
public void Initialize(Http2StreamContext context)
3737
{
38+
base.Initialize(context);
39+
40+
_decrementCalled = false;
41+
_completionState = StreamCompletionFlags.None;
42+
InputRemaining = null;
43+
RequestBodyStarted = false;
44+
DrainExpirationTicks = 0;
45+
3846
_context = context;
3947

4048
_inputFlowControl = new StreamInputFlowControl(
@@ -60,6 +68,12 @@ public Http2Stream(Http2StreamContext context)
6068
Output = _http2Output;
6169
}
6270

71+
public void InitializeWithExistingContext(int streamId)
72+
{
73+
_context.StreamId = streamId;
74+
Initialize(_context);
75+
}
76+
6377
public int StreamId => _context.StreamId;
6478

6579
public long? InputRemaining { get; internal set; }
@@ -82,6 +96,9 @@ public bool ReceivedEmptyRequestBody
8296

8397
protected override void OnReset()
8498
{
99+
_keepAlive = true;
100+
_connectionAborted = false;
101+
85102
ResetHttp2Features();
86103
}
87104

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ internal sealed class Http2Stream<TContext> : Http2Stream, IHostContextContainer
1111
{
1212
private readonly IHttpApplication<TContext> _application;
1313

14-
public Http2Stream(IHttpApplication<TContext> application, Http2StreamContext context) : base(context)
14+
public Http2Stream(IHttpApplication<TContext> application, Http2StreamContext context)
1515
{
16+
Initialize(context);
1617
_application = application;
1718
}
1819

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
7+
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
8+
{
9+
// See https://github.com/dotnet/runtime/blob/master/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegmentStack.cs
10+
internal struct Http2StreamStack
11+
{
12+
private Http2StreamAsValueType[] _array;
13+
private int _size;
14+
15+
public Http2StreamStack(int size)
16+
{
17+
_array = new Http2StreamAsValueType[size];
18+
_size = 0;
19+
}
20+
21+
public int Count => _size;
22+
23+
public bool TryPop(out Http2Stream result)
24+
{
25+
int size = _size - 1;
26+
Http2StreamAsValueType[] array = _array;
27+
28+
if ((uint)size >= (uint)array.Length)
29+
{
30+
result = default;
31+
return false;
32+
}
33+
34+
_size = size;
35+
result = array[size];
36+
array[size] = default;
37+
return true;
38+
}
39+
40+
// Pushes an item to the top of the stack.
41+
public void Push(Http2Stream item)
42+
{
43+
int size = _size;
44+
Http2StreamAsValueType[] array = _array;
45+
46+
if ((uint)size < (uint)array.Length)
47+
{
48+
array[size] = item;
49+
_size = size + 1;
50+
}
51+
else
52+
{
53+
PushWithResize(item);
54+
}
55+
}
56+
57+
// Non-inline from Stack.Push to improve its code quality as uncommon path
58+
[MethodImpl(MethodImplOptions.NoInlining)]
59+
private void PushWithResize(Http2Stream item)
60+
{
61+
Array.Resize(ref _array, 2 * _array.Length);
62+
_array[_size] = item;
63+
_size++;
64+
}
65+
66+
private readonly struct Http2StreamAsValueType
67+
{
68+
private readonly Http2Stream _value;
69+
private Http2StreamAsValueType(Http2Stream value) => _value = value;
70+
public static implicit operator Http2StreamAsValueType(Http2Stream s) => new Http2StreamAsValueType(s);
71+
public static implicit operator Http2Stream(Http2StreamAsValueType s) => s._value;
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)