Wait for frame loop completion to dispose connection stream (#1156).

This commit is contained in:
Cesar Blum Silveira 2016-10-10 10:16:03 -07:00
parent 663b825d97
commit 3177ba0aae
4 changed files with 94 additions and 6 deletions

View File

@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public Task StopAsync() public Task StopAsync()
{ {
_frame.Stop(); _frame.StopAsync();
_frame.SocketInput.CompleteAwaiting(); _frame.SocketInput.CompleteAwaiting();
return _socketClosedTcs.Task; return _socketClosedTcs.Task;
@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{ {
if (_filteredStreamAdapter != null) if (_filteredStreamAdapter != null)
{ {
_readInputTask.ContinueWith((task, state) => Task.WhenAll(_readInputTask, _frame.StopAsync()).ContinueWith((task, state) =>
{ {
var connection = (Connection)state; var connection = (Connection)state;
connection._filterContext.Connection.Dispose(); connection._filterContext.Connection.Dispose();

View File

@ -365,7 +365,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
this, this,
default(CancellationToken), default(CancellationToken),
TaskCreationOptions.DenyChildAttach, TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default); TaskScheduler.Default).Unwrap();
} }
/// <summary> /// <summary>
@ -374,7 +374,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
/// Stop will be called on all active connections, and Task.WaitAll() will be called on every /// Stop will be called on all active connections, and Task.WaitAll() will be called on every
/// return value. /// return value.
/// </summary> /// </summary>
public Task Stop() public Task StopAsync()
{ {
if (!_requestProcessingStopping) if (!_requestProcessingStopping)
{ {

View File

@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{ {
try try
{ {
await httpContext.Response.WriteAsync($"hello, world\r\r", ct); await httpContext.Response.WriteAsync($"hello, world", ct);
await Task.Delay(1000, ct); await Task.Delay(1000, ct);
} }
catch (TaskCanceledException) catch (TaskCanceledException)
@ -136,6 +136,54 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
Assert.False(loggerFactory.ErrorLogger.ObjectDisposedExceptionLogged); Assert.False(loggerFactory.ErrorLogger.ObjectDisposedExceptionLogged);
} }
[Fact]
public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnectionIsAborted()
{
var tcs = new TaskCompletionSource<object>();
var x509Certificate2 = new X509Certificate2(@"TestResources/testCert.pfx", "testPassword");
var loggerFactory = new HandshakeErrorLoggerFactory();
var hostBuilder = new WebHostBuilder()
.UseKestrel(options =>
{
options.UseHttps(@"TestResources/testCert.pfx", "testPassword");
})
.UseUrls("https://127.0.0.1:0/")
.UseLoggerFactory(loggerFactory)
.Configure(app => app.Run(async httpContext =>
{
httpContext.Abort();
try
{
await httpContext.Response.WriteAsync($"hello, world");
tcs.SetResult(null);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}));
using (var host = hostBuilder.Build())
{
host.Start();
using (var socket = await HttpClientSlim.GetSocket(new Uri($"https://127.0.0.1:{host.GetPort()}/")))
using (var stream = new NetworkStream(socket, ownsSocket: false))
using (var sslStream = new SslStream(stream, true, (sender, certificate, chain, errors) => true))
{
await sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null,
enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12,
checkCertificateRevocation: false);
var request = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n");
await sslStream.WriteAsync(request, 0, request.Length);
await sslStream.ReadAsync(new byte[32], 0, 32);
}
}
await tcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
}
private class HandshakeErrorLoggerFactory : ILoggerFactory private class HandshakeErrorLoggerFactory : ILoggerFactory
{ {
public HttpsConnectionFilterLogger FilterLogger { get; } = new HttpsConnectionFilterLogger(); public HttpsConnectionFilterLogger FilterLogger { get; } = new HttpsConnectionFilterLogger();

View File

@ -6,6 +6,8 @@ using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Internal; using Microsoft.AspNetCore.Server.Kestrel.Internal;
@ -1290,7 +1292,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var expectedKeepAliveTimeout = (long)serviceContext.ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds; var expectedKeepAliveTimeout = (long)serviceContext.ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection)); connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
frame.Stop(); frame.StopAsync();
socketInput.IncomingFin(); socketInput.IncomingFin();
requestProcessingTask.Wait(); requestProcessingTask.Wait();
@ -1464,5 +1466,43 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
// Assert // Assert
Assert.Throws<InvalidOperationException>(() => frame.Flush()); Assert.Throws<InvalidOperationException>(() => frame.Flush());
} }
[Fact]
public async Task RequestProcessingTaskIsUnwrapped()
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
using (var pool = new MemoryPool())
using (var socketInput = new SocketInput(pool, ltp))
{
var serviceContext = new ServiceContext
{
DateHeaderValueManager = new DateHeaderValueManager(),
ServerOptions = new KestrelServerOptions(),
Log = trace
};
var listenerContext = new ListenerContext(serviceContext)
{
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
};
var connectionContext = new ConnectionContext(listenerContext)
{
ConnectionControl = Mock.Of<IConnectionControl>(),
SocketInput = socketInput
};
var frame = new Frame<HttpContext>(application: null, context: connectionContext);
frame.Start();
var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n\r\n");
socketInput.IncomingData(data, 0, data.Length);
var requestProcessingTask = frame.StopAsync();
Assert.IsNotType(typeof(Task<Task>), requestProcessingTask);
await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
socketInput.IncomingFin();
}
}
} }
} }