#2035 Do not await OnCompleted handlers before sending the Response (#2324)

This commit is contained in:
Justin Wyer 2018-02-26 03:10:04 +02:00 committed by Stephen Halter
parent 06945ba81e
commit 83bf2375b3
2 changed files with 109 additions and 15 deletions

View File

@ -554,11 +554,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
PauseStreams(); PauseStreams();
if (_onCompleted != null)
{
await FireOnCompleted();
}
if (badRequestException == null) if (badRequestException == null)
{ {
// If _requestAbort is set, the connection has already been closed. // If _requestAbort is set, the connection has already been closed.
@ -601,6 +596,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
} }
if (_onCompleted != null)
{
await FireOnCompleted();
}
if (badRequestException != null) if (badRequestException != null)
{ {
// Handle BadHttpRequestException thrown during app execution or remaining message body consumption. // Handle BadHttpRequestException thrown during app execution or remaining message body consumption.
@ -739,10 +739,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
else
{ return FireOnCompletedAwaited(onCompleted);
return FireOnCompletedAwaited(onCompleted);
}
} }
private async Task FireOnCompletedAwaited(Stack<KeyValuePair<Func<object, Task>, object>> onCompleted) private async Task FireOnCompletedAwaited(Stack<KeyValuePair<Func<object, Task>, object>> onCompleted)
@ -755,7 +753,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
catch (Exception ex) catch (Exception ex)
{ {
ReportApplicationError(ex); Log.ApplicationError(ConnectionId, TraceIdentifier, ex);
} }
} }
} }

View File

@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task OnCompleteCalledEvenWhenOnStartingNotCalled() public async Task OnCompleteCalledEvenWhenOnStartingNotCalled()
{ {
var onStartingCalled = false; var onStartingCalled = false;
var onCompletedCalled = false; var onCompletedTcs = new TaskCompletionSource<object>();
var hostBuilder = TransportSelector.GetWebHostBuilder() var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel() .UseKestrel()
@ -156,7 +156,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
app.Run(context => app.Run(context =>
{ {
context.Response.OnStarting(() => Task.Run(() => onStartingCalled = true)); context.Response.OnStarting(() => Task.Run(() => onStartingCalled = true));
context.Response.OnCompleted(() => Task.Run(() => onCompletedCalled = true)); context.Response.OnCompleted(() => Task.Run(() =>
{
onCompletedTcs.SetResult(null);
}));
// Prevent OnStarting call (see HttpProtocol.ProcessRequestsAsync()). // Prevent OnStarting call (see HttpProtocol.ProcessRequestsAsync()).
throw new Exception(); throw new Exception();
@ -173,7 +176,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
Assert.False(onStartingCalled); Assert.False(onStartingCalled);
Assert.True(onCompletedCalled); await onCompletedTcs.Task.TimeoutAfter(TestConstants.DefaultTimeout);
} }
} }
} }
@ -294,6 +297,99 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
sendMalformedRequest: true); sendMalformedRequest: true);
} }
[Fact]
public async Task OnCompletedExceptionShouldNotPreventAResponse()
{
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel()
.UseUrls("http://127.0.0.1:0/")
.Configure(app =>
{
app.Run(async context =>
{
context.Response.OnCompleted(_ => throw new Exception(), null);
await context.Response.WriteAsync("hello, world");
});
});
using (var host = hostBuilder.Build())
{
host.Start();
using (var client = new HttpClient())
{
var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
}
[Fact]
public async Task OnCompletedShouldNotBlockAResponse()
{
var delay = Task.Delay(TestConstants.DefaultTimeout);
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel()
.UseUrls("http://127.0.0.1:0/")
.Configure(app =>
{
app.Run(async context =>
{
context.Response.OnCompleted(async () =>
{
await delay;
});
await context.Response.WriteAsync("hello, world");
});
});
using (var host = hostBuilder.Build())
{
host.Start();
using (var client = new HttpClient())
{
var response = await client.GetAsync($"http://127.0.0.1:{host.GetPort()}/");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.False(delay.IsCompleted);
}
}
}
[Fact]
public async Task InvalidChunkedEncodingInRequestShouldNotBlockOnCompleted()
{
var onCompletedTcs = new TaskCompletionSource<object>();
using (var server = new TestServer(httpContext =>
{
httpContext.Response.OnCompleted(() => Task.Run(() =>
{
onCompletedTcs.SetResult(null);
}));
return Task.CompletedTask;
}))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"Transfer-Encoding: chunked",
"",
"gg");
await connection.ReceiveForcedEnd(
"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: 0",
"",
"");
}
}
await onCompletedTcs.Task.TimeoutAfter(TestConstants.DefaultTimeout);
}
private static async Task ResponseStatusCodeSetBeforeHttpContextDispose( private static async Task ResponseStatusCodeSetBeforeHttpContextDispose(
RequestDelegate handler, RequestDelegate handler,
HttpStatusCode? expectedClientStatusCode, HttpStatusCode? expectedClientStatusCode,
@ -1988,7 +2084,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Theory] [Theory]
[MemberData(nameof(ConnectionAdapterData))] [MemberData(nameof(ConnectionAdapterData))]
public async Task ThrowingInOnCompletedIsLoggedAndClosesConnection(ListenOptions listenOptions) public async Task ThrowingInOnCompletedIsLogged(ListenOptions listenOptions)
{ {
var testContext = new TestServiceContext(); var testContext = new TestServiceContext();
@ -2024,7 +2120,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
"Host:", "Host:",
"", "",
""); "");
await connection.ReceiveForcedEnd( await connection.ReceiveEnd(
"HTTP/1.1 200 OK", "HTTP/1.1 200 OK",
$"Date: {testContext.DateHeaderValue}", $"Date: {testContext.DateHeaderValue}",
"Content-Length: 11", "Content-Length: 11",