Merge pull request #2755 from dotnet-maestro-bot/merge/release/2.2-to-master

[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
Andrew Stanton-Nurse 2018-08-08 08:17:40 -07:00 committed by GitHub
commit 556a234c45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 62 deletions

View File

@ -182,8 +182,6 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
{ {
Task disposeTask; Task disposeTask;
Cancellation?.Dispose();
await StateLock.WaitAsync(); await StateLock.WaitAsync();
try try
{ {
@ -206,6 +204,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
finally finally
{ {
StateLock.Release(); StateLock.Release();
Cancellation?.Dispose();
Cancellation = null;
} }
await disposeTask; await disposeTask;
@ -218,78 +220,65 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
// Closing gracefully means we're only going to close the finished sides of the pipe // Closing gracefully means we're only going to close the finished sides of the pipe
// If the application finishes, that means it's done with the transport pipe // If the application finishes, that means it's done with the transport pipe
// If the transport finishes, that means it's done with the application pipe // If the transport finishes, that means it's done with the application pipe
if (closeGracefully) if (!closeGracefully)
{ {
// Wait for either to finish Application?.Output.CancelPendingFlush();
var result = await Task.WhenAny(applicationTask, transportTask);
// If the application is complete, complete the transport pipe (it's the pipe to the transport) if (TransportType == HttpTransportType.WebSockets)
if (result == applicationTask)
{ {
Transport?.Output.Complete(applicationTask.Exception?.InnerException); // The websocket transport will close the application output automatically when reading is cancelled
Transport?.Input.Complete(); Cancellation?.Cancel();
try
{
Log.WaitingForTransport(_logger, TransportType);
// Transports are written by us and are well behaved, wait for them to drain
await transportTask;
}
finally
{
Log.TransportComplete(_logger, TransportType);
// Now complete the application
Application?.Output.Complete();
Application?.Input.Complete();
}
} }
else else
{ {
// If the transport is complete, complete the application pipes // The other transports don't close their own output, so we can do it here safely
Application?.Output.Complete(transportTask.Exception?.InnerException); Application?.Output.Complete();
}
}
// Wait for either to finish
var result = await Task.WhenAny(applicationTask, transportTask);
// If the application is complete, complete the transport pipe (it's the pipe to the transport)
if (result == applicationTask)
{
Transport?.Output.Complete(applicationTask.Exception?.InnerException);
Transport?.Input.Complete();
try
{
Log.WaitingForTransport(_logger, TransportType);
// Transports are written by us and are well behaved, wait for them to drain
await transportTask;
}
finally
{
Log.TransportComplete(_logger, TransportType);
// Now complete the application
Application?.Output.Complete();
Application?.Input.Complete(); Application?.Input.Complete();
try
{
// A poorly written application *could* in theory get stuck forever and it'll show up as a memory leak
Log.WaitingForApplication(_logger);
await applicationTask;
}
finally
{
Log.ApplicationComplete(_logger);
Transport?.Output.Complete();
Transport?.Input.Complete();
}
} }
} }
else else
{ {
Log.ShuttingDownTransportAndApplication(_logger, TransportType); // If the transport is complete, complete the application pipes
// Cancel any pending flushes from back pressure
Application?.Output.CancelPendingFlush();
// Shutdown both sides and wait for nothing
Transport?.Output.Complete(applicationTask.Exception?.InnerException);
Application?.Output.Complete(transportTask.Exception?.InnerException); Application?.Output.Complete(transportTask.Exception?.InnerException);
Application?.Input.Complete();
try try
{ {
Log.WaitingForTransportAndApplication(_logger, TransportType);
// A poorly written application *could* in theory get stuck forever and it'll show up as a memory leak // A poorly written application *could* in theory get stuck forever and it'll show up as a memory leak
await Task.WhenAll(applicationTask, transportTask); Log.WaitingForApplication(_logger);
await applicationTask;
} }
finally finally
{ {
Log.TransportAndApplicationComplete(_logger, TransportType); Log.ApplicationComplete(_logger);
// Close the reading side after both sides run Transport?.Output.Complete();
Application?.Input.Complete();
Transport?.Input.Complete(); Transport?.Input.Complete();
} }
} }

View File

@ -166,6 +166,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
Log.EstablishedConnection(_logger); Log.EstablishedConnection(_logger);
// Allow the reads to be cancelled
connection.Cancellation = new CancellationTokenSource();
var ws = new WebSocketsTransport(options.WebSockets, connection.Application, connection, _loggerFactory); var ws = new WebSocketsTransport(options.WebSockets, connection.Application, connection, _loggerFactory);
await DoPersistentConnection(connectionDelegate, ws, context, connection); await DoPersistentConnection(connectionDelegate, ws, context, connection);

View File

@ -138,13 +138,15 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports
private async Task StartReceiving(WebSocket socket) private async Task StartReceiving(WebSocket socket)
{ {
var token = _connection.Cancellation?.Token ?? default;
try try
{ {
while (true) while (!token.IsCancellationRequested)
{ {
#if NETCOREAPP2_2 #if NETCOREAPP2_2
// Do a 0 byte read so that idle connections don't allocate a buffer when waiting for a read // Do a 0 byte read so that idle connections don't allocate a buffer when waiting for a read
var result = await socket.ReceiveAsync(Memory<byte>.Empty, CancellationToken.None); var result = await socket.ReceiveAsync(Memory<byte>.Empty, token);
if (result.MessageType == WebSocketMessageType.Close) if (result.MessageType == WebSocketMessageType.Close)
{ {
@ -154,15 +156,15 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports
var memory = _application.Output.GetMemory(); var memory = _application.Output.GetMemory();
#if NETCOREAPP2_2 #if NETCOREAPP2_2
var receiveResult = await socket.ReceiveAsync(memory, CancellationToken.None); var receiveResult = await socket.ReceiveAsync(memory, token);
#else #else
var isArray = MemoryMarshal.TryGetArray<byte>(memory, out var arraySegment); var isArray = MemoryMarshal.TryGetArray<byte>(memory, out var arraySegment);
Debug.Assert(isArray); Debug.Assert(isArray);
// Exceptions are handled above where the send and receive tasks are being run. // Exceptions are handled above where the send and receive tasks are being run.
var receiveResult = await socket.ReceiveAsync(arraySegment, CancellationToken.None); var receiveResult = await socket.ReceiveAsync(arraySegment, token);
#endif #endif
// Need to check again for NetCoreApp2.2 because a close can happen between a 0-byte read and the actual read // Need to check again for NetCoreApp2.2 because a close can happen between a 0-byte read and the actual read
if (receiveResult.MessageType == WebSocketMessageType.Close) if (receiveResult.MessageType == WebSocketMessageType.Close)
{ {
return; return;
@ -193,7 +195,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal.Transports
} }
catch (Exception ex) catch (Exception ex)
{ {
if (!_aborted) if (!_aborted && !token.IsCancellationRequested)
{ {
_application.Output.Complete(ex); _application.Output.Complete(ex);

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Buffers;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -96,9 +97,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
try try
{ {
await connection.DisposeAsync(closeGracefully); await connection.DisposeAsync(closeGracefully).OrTimeout();
} }
catch catch (Exception ex) when (!(ex is TimeoutException))
{ {
// Ignore the exception that bubbles out of the failing task // Ignore the exception that bubbles out of the failing task
} }
@ -166,6 +167,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
try try
{ {
Assert.True(result.IsCompleted); Assert.True(result.IsCompleted);
// We should be able to write
await connection.Transport.Output.WriteAsync(new byte[] { 1 });
} }
finally finally
{ {
@ -176,6 +180,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
connection.TransportTask = Task.Run(async () => connection.TransportTask = Task.Run(async () =>
{ {
var result = await connection.Application.Input.ReadAsync(); var result = await connection.Application.Input.ReadAsync();
Assert.Equal(new byte[] { 1 }, result.Buffer.ToArray());
connection.Application.Input.AdvanceTo(result.Buffer.End);
result = await connection.Application.Input.ReadAsync();
try try
{ {
Assert.True(result.IsCompleted); Assert.True(result.IsCompleted);