Prevent app code from blocking the connection loop #2657
This commit is contained in:
parent
2b269e0433
commit
324565772c
|
|
@ -7,6 +7,7 @@ using System.Collections.Concurrent;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
|
|
@ -703,7 +704,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
|
|
||||||
_streams[_incomingFrame.StreamId] = _currentHeadersStream;
|
_streams[_incomingFrame.StreamId] = _currentHeadersStream;
|
||||||
_ = _currentHeadersStream.ProcessRequestsAsync(application);
|
// Must not allow app code to block the connection handling loop.
|
||||||
|
ThreadPool.UnsafeQueueUserWorkItem(state =>
|
||||||
|
{
|
||||||
|
var (app, currentStream) = (Tuple<IHttpApplication<TContext>, Http2Stream>)state;
|
||||||
|
_ = currentStream.ProcessRequestsAsync(app);
|
||||||
|
},
|
||||||
|
new Tuple<IHttpApplication<TContext>, Http2Stream>(application, _currentHeadersStream));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetRequestHeaderParsingState()
|
private void ResetRequestHeaderParsingState()
|
||||||
|
|
|
||||||
|
|
@ -473,6 +473,72 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Assert.Equal(stream3DataFrame2.DataPayload, _worldBytes);
|
Assert.Equal(stream3DataFrame2.DataPayload, _worldBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DATA_Received_Multiplexed_AppMustNotBlockOtherFrames()
|
||||||
|
{
|
||||||
|
var stream1Read = new ManualResetEvent(false);
|
||||||
|
var stream1ReadFinished = new ManualResetEvent(false);
|
||||||
|
var stream3Read = new ManualResetEvent(false);
|
||||||
|
var stream3ReadFinished = new ManualResetEvent(false);
|
||||||
|
await InitializeConnectionAsync(async context =>
|
||||||
|
{
|
||||||
|
var data = new byte[10];
|
||||||
|
var read = await context.Request.Body.ReadAsync(new byte[10], 0, 10);
|
||||||
|
if (context.Features.Get<IHttp2StreamIdFeature>().StreamId == 1)
|
||||||
|
{
|
||||||
|
stream1Read.Set();
|
||||||
|
Assert.True(stream1ReadFinished.WaitOne(TimeSpan.FromSeconds(10)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream3Read.Set();
|
||||||
|
Assert.True(stream3ReadFinished.WaitOne(TimeSpan.FromSeconds(10)));
|
||||||
|
}
|
||||||
|
await context.Response.Body.WriteAsync(data, 0, read);
|
||||||
|
});
|
||||||
|
|
||||||
|
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
|
||||||
|
await StartStreamAsync(3, _browserRequestHeaders, endStream: false);
|
||||||
|
|
||||||
|
await SendDataAsync(1, _helloBytes, endStream: false);
|
||||||
|
Assert.True(stream1Read.WaitOne(TimeSpan.FromSeconds(10)));
|
||||||
|
|
||||||
|
await SendDataAsync(3, _helloBytes, endStream: false);
|
||||||
|
Assert.True(stream3Read.WaitOne(TimeSpan.FromSeconds(10)));
|
||||||
|
|
||||||
|
stream3ReadFinished.Set();
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 37,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 3);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 5,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.NONE,
|
||||||
|
withStreamId: 3);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 3);
|
||||||
|
|
||||||
|
stream1ReadFinished.Set();
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 37,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 1);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 5,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.NONE,
|
||||||
|
withStreamId: 1);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(0)]
|
[InlineData(0)]
|
||||||
[InlineData(1)]
|
[InlineData(1)]
|
||||||
|
|
@ -643,7 +709,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
ignoreNonGoAwayFrames: false,
|
ignoreNonGoAwayFrames: false,
|
||||||
expectedLastStreamId: 1,
|
expectedLastStreamId: 1,
|
||||||
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
|
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
|
||||||
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1));
|
expectedErrorMessage: null);
|
||||||
|
|
||||||
|
// There's a race where either of these messages could be logged, depending on if the stream cleanup has finished yet.
|
||||||
|
var closedMessage = CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1);
|
||||||
|
var halfClosedMessage = CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1);
|
||||||
|
|
||||||
|
var message = Assert.Single(_logger.Messages, m => m.Exception is Http2ConnectionErrorException);
|
||||||
|
Assert.True(message.Exception.Message.IndexOf(closedMessage) >= 0
|
||||||
|
|| message.Exception.Message.IndexOf(halfClosedMessage) >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -846,6 +920,60 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HEADERS_Received_AppCannotBlockOtherFrames()
|
||||||
|
{
|
||||||
|
var firstRequestReceived = new ManualResetEvent(false);
|
||||||
|
var finishFirstRequest = new ManualResetEvent(false);
|
||||||
|
var secondRequestReceived = new ManualResetEvent(false);
|
||||||
|
var finishSecondRequest = new ManualResetEvent(false);
|
||||||
|
await InitializeConnectionAsync(context =>
|
||||||
|
{
|
||||||
|
if (!firstRequestReceived.WaitOne(0))
|
||||||
|
{
|
||||||
|
firstRequestReceived.Set();
|
||||||
|
Assert.True(finishFirstRequest.WaitOne(TimeSpan.FromSeconds(10)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
secondRequestReceived.Set();
|
||||||
|
Assert.True(finishSecondRequest.WaitOne(TimeSpan.FromSeconds(10)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
|
||||||
|
Assert.True(firstRequestReceived.WaitOne(TimeSpan.FromSeconds(10)));
|
||||||
|
|
||||||
|
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
|
||||||
|
Assert.True(secondRequestReceived.WaitOne(TimeSpan.FromSeconds(10)));
|
||||||
|
|
||||||
|
finishSecondRequest.Set();
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 55,
|
||||||
|
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
|
||||||
|
withStreamId: 3);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 3);
|
||||||
|
|
||||||
|
finishFirstRequest.Set();
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 55,
|
||||||
|
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
|
||||||
|
withStreamId: 1);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HEADERS_Received_StreamIdZero_ConnectionError()
|
public async Task HEADERS_Received_StreamIdZero_ConnectionError()
|
||||||
{
|
{
|
||||||
|
|
@ -897,7 +1025,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
ignoreNonGoAwayFrames: false,
|
ignoreNonGoAwayFrames: false,
|
||||||
expectedLastStreamId: 1,
|
expectedLastStreamId: 1,
|
||||||
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
|
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
|
||||||
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1));
|
expectedErrorMessage: null);
|
||||||
|
|
||||||
|
// There's a race where either of these messages could be logged, depending on if the stream cleanup has finished yet.
|
||||||
|
var closedMessage = CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1);
|
||||||
|
var halfClosedMessage = CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1);
|
||||||
|
|
||||||
|
var message = Assert.Single(_logger.Messages, m => m.Exception is Http2ConnectionErrorException);
|
||||||
|
Assert.True(message.Exception.Message.IndexOf(closedMessage) >= 0
|
||||||
|
|| message.Exception.Message.IndexOf(halfClosedMessage) >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue