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:
commit
556a234c45
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue