From 3709eda2701d53623732c7c7ccebf84a0ab4f37d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 8 Jul 2020 13:41:23 +1200 Subject: [PATCH] Fix HTTP/2 stream output flow control abort error (#23727) * Fix stream output flow control abort error * Clean up * Clean up * Add timeouts --- .../FlowControl/StreamOutputFlowControl.cs | 2 +- .../Http2/Http2ConnectionTests.cs | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamOutputFlowControl.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamOutputFlowControl.cs index 792a1ca3a7..f688e5669e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamOutputFlowControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/FlowControl/StreamOutputFlowControl.cs @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl if (_currentConnectionLevelAwaitable != null && _currentConnectionLevelAwaitable.Version == _currentConnectionLevelAwaitableVersion) { - _currentConnectionLevelAwaitable.SetResult(null); + _currentConnectionLevelAwaitable.TrySetResult(null); } } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index a1cf418fe3..a969bfe609 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -1594,6 +1594,68 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await WaitForAllStreamsAsync(); } + [Fact] + public async Task OutputFlowControl_ConnectionAndRequestAborted_NoException() + { + // Ensure the stream window size is bigger than the connection window size + _clientSettings.InitialWindowSize = _clientSettings.InitialWindowSize * 2; + + var connectionAbortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var requestAbortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + await InitializeConnectionAsync(async context => + { + // Exceed connection window size + await context.Response.WriteAsync(new string('!', 65536)); + + await connectionAbortedTcs.Task; + + try + { + context.Abort(); + requestAbortedTcs.SetResult(); + } + catch (Exception ex) + { + requestAbortedTcs.SetException(ex); + } + }).DefaultTimeout(); + + await StartStreamAsync(1, _browserRequestHeaders, endStream: true).DefaultTimeout(); + + await ExpectAsync(Http2FrameType.HEADERS, + withLength: 32, + withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS), + withStreamId: 1).DefaultTimeout(); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)(Http2DataFrameFlags.NONE), + withStreamId: 1).DefaultTimeout(); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)(Http2DataFrameFlags.NONE), + withStreamId: 1).DefaultTimeout(); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 16384, + withFlags: (byte)(Http2DataFrameFlags.NONE), + withStreamId: 1).DefaultTimeout(); + + await ExpectAsync(Http2FrameType.DATA, + withLength: 16383, + withFlags: (byte)(Http2DataFrameFlags.NONE), + withStreamId: 1).DefaultTimeout(); + + _connection.HandleReadDataRateTimeout(); + + connectionAbortedTcs.SetResult(); + + // Task completing successfully means HttpContext.Abort didn't throw + await requestAbortedTcs.Task.DefaultTimeout(); + } + [Fact] public async Task DATA_Sent_DespiteStreamOutputFlowControl_IfEmptyAndEndsStream() {