From 9072e0ba26838bdedf4a9ec17ad5a809f37c8b26 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Wed, 3 May 2017 19:38:34 -0700 Subject: [PATCH] Added a ConnectionAbortedException to Transport.Abstractions (#1806) * Added a ConnectionAbortedException to Transport.Abstractions - To avoid hard coding TaskCanceledException in each transport - This PR tries to keep compatibility by converting the ConnectionAbortedException to a TaskCanceledException on exceptions in FrameRequestStream. The downside is that this conversion causes an async state machine to be created per call to ReadAsync. CopyToAsync isn't that bad because it's a single long running task. --- .../Internal/Http/FrameRequestStream.cs | 42 +++++++++++++++---- ...rameConnectionManagerShutdownExtensions.cs | 3 +- .../Exceptions/ConnectionAbortedException.cs | 21 ++++++++++ .../Internal/LibuvConnection.cs | 2 +- .../SocketConnection.cs | 4 +- 5 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Exceptions/ConnectionAbortedException.cs diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameRequestStream.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameRequestStream.cs index f7a92e37a0..4fa42fef79 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameRequestStream.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Http/FrameRequestStream.cs @@ -3,9 +3,11 @@ using System; using System.IO; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http @@ -105,11 +107,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { var task = ValidateState(cancellationToken); - if (task == null) + if (task != null) { - return _body.ReadAsync(new ArraySegment(buffer, offset, count), cancellationToken); + return task; + } + + return ReadAsyncInternal(buffer, offset, count, cancellationToken); + } + + private async Task ReadAsyncInternal(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + try + { + return await _body.ReadAsync(new ArraySegment(buffer, offset, count), cancellationToken); + } + catch (ConnectionAbortedException ex) + { + throw new TaskCanceledException("The request was aborted", ex); } - return task; } public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -124,11 +139,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } var task = ValidateState(cancellationToken); - if (task == null) + if (task != null) { - return _body.CopyToAsync(destination, cancellationToken); + return task; + } + + return CopyToAsyncInternal(destination, cancellationToken); + } + + private async Task CopyToAsyncInternal(Stream destination, CancellationToken cancellationToken) + { + try + { + await _body.CopyToAsync(destination, cancellationToken); + } + catch (ConnectionAbortedException ex) + { + throw new TaskCanceledException("The request was aborted", ex); } - return task; } public void StartAcceptingReads(MessageBody body) @@ -165,7 +193,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public void Abort(Exception error = null) { // We don't want to throw an ODE until the app func actually completes. - // If the request is aborted, we throw an TaskCanceledException instead, + // If the request is aborted, we throw a TaskCanceledException instead, // unless error is not null, in which case we throw it. if (_state != FrameStreamState.Closed) { diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs index 88aae5e6c7..9f43f2e2ad 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/FrameConnectionManagerShutdownExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { @@ -25,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure public static async Task AbortAllConnectionsAsync(this FrameConnectionManager connectionManager) { var abortTasks = new List(); - var canceledException = new TaskCanceledException(CoreStrings.RequestProcessingAborted); + var canceledException = new ConnectionAbortedException(); connectionManager.Walk(connection => { diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Exceptions/ConnectionAbortedException.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Exceptions/ConnectionAbortedException.cs new file mode 100644 index 0000000000..0975038e3d --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Exceptions/ConnectionAbortedException.cs @@ -0,0 +1,21 @@ +using System; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions +{ + public class ConnectionAbortedException : OperationCanceledException + { + public ConnectionAbortedException() : + this("The connection was aborted") + { + + } + + public ConnectionAbortedException(string message) : base(message) + { + } + + public ConnectionAbortedException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConnection.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConnection.cs index d9dac68dd3..d9b17193ee 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConnection.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConnection.cs @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal Input.CancelPendingFlush(); // Now, complete the input so that no more reads can happen - Input.Complete(new TaskCanceledException("The request was aborted")); + Input.Complete(new ConnectionAbortedException()); // We're done with the socket now _socket.Dispose(); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketConnection.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketConnection.cs index 025c4188f8..6d5c7e7cf6 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketConnection.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets/SocketConnection.cs @@ -115,11 +115,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets } catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted) { - error = new TaskCanceledException("The request was aborted"); + error = new ConnectionAbortedException(); } catch (ObjectDisposedException) { - error = new TaskCanceledException("The request was aborted"); + error = new ConnectionAbortedException(); } catch (IOException ex) {