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,8 +220,22 @@ 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)
{ {
Application?.Output.CancelPendingFlush();
if (TransportType == HttpTransportType.WebSockets)
{
// The websocket transport will close the application output automatically when reading is cancelled
Cancellation?.Cancel();
}
else
{
// The other transports don't close their own output, so we can do it here safely
Application?.Output.Complete();
}
}
// Wait for either to finish // Wait for either to finish
var result = await Task.WhenAny(applicationTask, transportTask); var result = await Task.WhenAny(applicationTask, transportTask);
@ -266,33 +282,6 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
Transport?.Input.Complete(); Transport?.Input.Complete();
} }
} }
}
else
{
Log.ShuttingDownTransportAndApplication(_logger, TransportType);
// 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);
try
{
Log.WaitingForTransportAndApplication(_logger, TransportType);
// A poorly written application *could* in theory get stuck forever and it'll show up as a memory leak
await Task.WhenAll(applicationTask, transportTask);
}
finally
{
Log.TransportAndApplicationComplete(_logger, TransportType);
// Close the reading side after both sides run
Application?.Input.Complete();
Transport?.Input.Complete();
}
}
// Notify all waiters that we're done disposing // Notify all waiters that we're done disposing
_disposeTcs.TrySetResult(null); _disposeTcs.TrySetResult(null);

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,13 +156,13 @@ 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)
@ -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);