From c41b4496810bddcc3c7fde1e4a90d5bde9e32982 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 3 Dec 2015 01:36:22 +0000 Subject: [PATCH] Lighter & less async sMachines Selected excerpts from "Value tasks + Less Async" #432 --- .../Http/Frame.cs | 73 +++++++++++++++++-- .../Http/FrameOfT.cs | 33 +++++---- 2 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index befc6ba294..008e1a905e 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -44,9 +44,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http protected readonly FrameRequestHeaders _requestHeaders = new FrameRequestHeaders(); private readonly FrameResponseHeaders _responseHeaders = new FrameResponseHeaders(); - private List, object>> _onStarting; + protected List, object>> _onStarting; - private List, object>> _onCompleted; + protected List, object>> _onCompleted; private bool _requestProcessingStarted; private Task _requestProcessingTask; @@ -144,8 +144,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { // If a request abort token was previously explicitly set, return it. if (_manuallySetRequestAbortToken.HasValue) + { return _manuallySetRequestAbortToken.Value; - + } // Otherwise, get the abort CTS. If we have one, which would mean that someone previously // asked for the RequestAborted token, simply return its token. If we don't, // check to see whether we've already aborted, in which case just return an @@ -416,7 +417,28 @@ namespace Microsoft.AspNet.Server.Kestrel.Http } } - public async Task WriteAsync(ArraySegment data, CancellationToken cancellationToken) + public Task WriteAsync(ArraySegment data, CancellationToken cancellationToken) + { + if (!_responseStarted) + { + return WriteAsyncAwaited(data, cancellationToken); + } + + if (_autoChunk) + { + if (data.Count == 0) + { + return TaskUtilities.CompletedTask; + } + return WriteChunkedAsync(data, cancellationToken); + } + else + { + return SocketOutput.WriteAsync(data, immediate: true, cancellationToken: cancellationToken); + } + } + + public async Task WriteAsyncAwaited(ArraySegment data, CancellationToken cancellationToken) { await ProduceStartAndFireOnStarting(immediate: false); @@ -501,10 +523,27 @@ namespace Microsoft.AspNet.Server.Kestrel.Http } } - public async Task ProduceStartAndFireOnStarting(bool immediate = true) + public Task ProduceStartAndFireOnStarting(bool immediate = true) { - if (_responseStarted) return; + if (_responseStarted) return TaskUtilities.CompletedTask; + if (_onStarting != null) + { + return FireOnStartingProduceStart(immediate: immediate); + } + + if (_applicationException != null) + { + throw new ObjectDisposedException( + "The response has been aborted due to an unhandled application exception.", + _applicationException); + } + + return ProduceStart(immediate, appCompleted: false); + } + + private async Task FireOnStartingProduceStart(bool immediate) + { await FireOnStarting(); if (_applicationException != null) @@ -527,7 +566,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http return CreateResponseHeader(statusBytes, appCompleted, immediate); } - protected async Task ProduceEnd() + protected Task ProduceEnd() { if (_applicationException != null) { @@ -535,7 +574,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { // We can no longer respond with a 500, so we simply close the connection. _requestProcessingStopping = true; - return; + return TaskUtilities.CompletedTask; } else { @@ -547,8 +586,26 @@ namespace Microsoft.AspNet.Server.Kestrel.Http } } + + if (!_responseStarted) + { + return ProduceEndAwaited(); + } + + WriteSuffix(); + + return TaskUtilities.CompletedTask; + } + + private async Task ProduceEndAwaited() + { await ProduceStart(immediate: true, appCompleted: true); + WriteSuffix(); + } + + private void WriteSuffix() + { // _autoChunk should be checked after we are sure ProduceStart() has been called // since ProduceStart() may set _autoChunk to true. if (_autoChunk) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/FrameOfT.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameOfT.cs index 941c75fe8e..4410cdc8cd 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/FrameOfT.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameOfT.cs @@ -40,28 +40,27 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { try { - var terminated = false; - while (!terminated && !_requestProcessingStopping) + while (!_requestProcessingStopping) { - while (!terminated && !_requestProcessingStopping && !TakeStartLine(SocketInput)) + while (!_requestProcessingStopping && !TakeStartLine(SocketInput)) { - terminated = SocketInput.RemoteIntakeFin; - if (!terminated) + if (SocketInput.RemoteIntakeFin) { - await SocketInput; + return; } + await SocketInput; } - while (!terminated && !_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders)) + while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders)) { - terminated = SocketInput.RemoteIntakeFin; - if (!terminated) + if (SocketInput.RemoteIntakeFin) { - await SocketInput; + return; } + await SocketInput; } - if (!terminated && !_requestProcessingStopping) + if (!_requestProcessingStopping) { var messageBody = MessageBody.For(HttpVersion, _requestHeaders, this); _keepAlive = messageBody.RequestKeepAlive; @@ -89,12 +88,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http // already failed. If an OnStarting callback throws we can go through // our normal error handling in ProduceEnd. // https://github.com/aspnet/KestrelHttpServer/issues/43 - if (!_responseStarted && _applicationException == null) + if (!_responseStarted && _applicationException == null && _onStarting != null) { await FireOnStarting(); } - await FireOnCompleted(); + if (_onCompleted != null) + { + await FireOnCompleted(); + } _application.DisposeContext(context, _applicationException); @@ -114,7 +116,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Http _responseBody.StopAcceptingWrites(); } - terminated = !_keepAlive; + if (!_keepAlive) + { + return; + } } Reset();