diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 2dded1c2d180..d80df01e4409 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -415,7 +415,7 @@ private Task ProcessFrameAsync(IHttpApplication application, case Http2FrameType.WINDOW_UPDATE: return ProcessWindowUpdateFrameAsync(); case Http2FrameType.CONTINUATION: - return ProcessContinuationFrameAsync(application, payload); + return ProcessContinuationFrameAsync(payload); default: return ProcessUnknownFrameAsync(); } @@ -558,7 +558,7 @@ private Task ProcessHeadersFrameAsync(IHttpApplication appli } // Start a new stream - _currentHeadersStream = new Http2Stream(new Http2StreamContext + _currentHeadersStream = new Http2Stream(application, new Http2StreamContext { ConnectionId = ConnectionId, StreamId = _incomingFrame.StreamId, @@ -580,7 +580,7 @@ private Task ProcessHeadersFrameAsync(IHttpApplication appli _headerFlags = _incomingFrame.HeadersFlags; var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding - return DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, headersPayload); + return DecodeHeadersAsync(_incomingFrame.HeadersEndHeaders, headersPayload); } } @@ -822,7 +822,7 @@ private Task ProcessWindowUpdateFrameAsync() return Task.CompletedTask; } - private Task ProcessContinuationFrameAsync(IHttpApplication application, ReadOnlySequence payload) + private Task ProcessContinuationFrameAsync(ReadOnlySequence payload) { if (_currentHeadersStream == null) { @@ -847,7 +847,7 @@ private Task ProcessContinuationFrameAsync(IHttpApplication TimeoutControl.CancelTimeout(); } - return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, payload); + return DecodeHeadersAsync(_incomingFrame.ContinuationEndHeaders, payload); } } @@ -861,7 +861,7 @@ private Task ProcessUnknownFrameAsync() return Task.CompletedTask; } - private Task DecodeHeadersAsync(IHttpApplication application, bool endHeaders, ReadOnlySequence payload) + private Task DecodeHeadersAsync(bool endHeaders, ReadOnlySequence payload) { try { @@ -870,7 +870,7 @@ private Task DecodeHeadersAsync(IHttpApplication application if (endHeaders) { - StartStream(application); + StartStream(); ResetRequestHeaderParsingState(); } } @@ -896,7 +896,7 @@ private Task DecodeTrailersAsync(bool endHeaders, ReadOnlySequence payload return Task.CompletedTask; } - private void StartStream(IHttpApplication application) + private void StartStream() { if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields) { @@ -923,12 +923,7 @@ private void StartStream(IHttpApplication application) _activeStreamCount++; _streams[_incomingFrame.StreamId] = _currentHeadersStream; // Must not allow app code to block the connection handling loop. - ThreadPool.UnsafeQueueUserWorkItem(state => - { - var (app, currentStream) = (Tuple, Http2Stream>)state; - _ = currentStream.ProcessRequestsAsync(app); - }, - new Tuple, Http2Stream>(application, _currentHeadersStream)); + ThreadPool.UnsafeQueueUserWorkItem(_currentHeadersStream, preferLocal: false); } private void ResetRequestHeaderParsingState() diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs index c51dfcf84cd7..661c2ae06895 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.IO.Pipelines; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; @@ -17,7 +18,7 @@ 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 Http2OutputProducer _http2Output; @@ -499,6 +500,11 @@ private Pipe CreateRequestBodyPipe(uint windowSize) } } + /// + /// Used to kick off the request processing loop by derived classes. + /// + public abstract void Execute(); + [Flags] private enum StreamCompletionFlags { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamOfT.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamOfT.cs new file mode 100644 index 000000000000..d026b3a28884 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2StreamOfT.cs @@ -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 : Http2Stream + { + private readonly IHttpApplication _application; + + public Http2Stream(IHttpApplication application, Http2StreamContext context) : base(context) + { + _application = application; + } + + public override void Execute() + { + // REVIEW: Should we store this in a field for easy debugging? + _ = ProcessRequestsAsync(_application); + } + } +} diff --git a/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs b/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs index 001d3952b39d..011fa59fe1a6 100644 --- a/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpProtocolFeatureCollectionTests.cs @@ -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; @@ -40,7 +40,7 @@ public HttpProtocolFeatureCollectionTests() _http1Connection.Reset(); _collection = _http1Connection; - var http2Stream = new Http2Stream(context); + var http2Stream = new TestHttp2Stream(context); http2Stream.Reset(); _http2Collection = http2Stream; } @@ -220,5 +220,16 @@ private int SetFeaturesToNonDefault() } private Http1Connection CreateHttp1Connection() => new TestHttp1Connection(_httpConnectionContext); + + private class TestHttp2Stream : Http2Stream + { + public TestHttp2Stream(Http2StreamContext context) : base(context) + { + } + + public override void Execute() + { + } + } } }