diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index e445bc8155..a738cb0c84 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -53,8 +53,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http private Task _requestProcessingTask; private volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx private volatile bool _requestAborted; - private CancellationTokenSource _disconnectCts = new CancellationTokenSource(); - private CancellationTokenSource _requestAbortCts; + private CancellationTokenSource _abortedCts; + private CancellationToken? _manuallySetRequestAbortToken; private FrameRequestStream _requestBody; private FrameResponseStream _responseBody; @@ -135,8 +135,47 @@ namespace Microsoft.AspNet.Server.Kestrel.Http public Stream DuplexStream { get; set; } - public CancellationToken RequestAborted { get; set; } + public CancellationToken RequestAborted + { + get + { + // 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 + // already canceled token. Finally, force a source into existence if we still + // don't have one, and return its token. + var cts = _abortedCts; + return + cts != null ? cts.Token : + _requestAborted ? new CancellationToken(true) : + RequestAbortedSource.Token; + } + set + { + // Set an abort token, overriding one we create internally. This setter and associated + // field exist purely to support IHttpRequestLifetimeFeature.set_RequestAborted. + _manuallySetRequestAbortToken = value; + } + } + + private CancellationTokenSource RequestAbortedSource + { + get + { + // Get the abort token, lazily-initializing it if necessary. + // Make sure it's canceled if an abort request already came in. + var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource()); + if (_requestAborted) + { + cts.Cancel(); + } + return cts; + } + } public bool HasResponseStarted { get { return _responseStarted; } @@ -188,7 +227,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http _prepareRequest?.Invoke(this); - _requestAbortCts?.Dispose(); + _manuallySetRequestAbortToken = null; + _abortedCts = null; } public void ResetResponseHeaders() @@ -244,13 +284,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { ConnectionControl.End(ProduceEndType.SocketDisconnect); SocketInput.AbortAwaiting(); - - _disconnectCts.Cancel(); + RequestAbortedSource.Cancel(); } catch (Exception ex) { Log.LogError("Abort", ex); } + finally + { + _abortedCts = null; + } } /// @@ -294,8 +337,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http ResponseBody = _responseBody; DuplexStream = new FrameDuplexStream(RequestBody, ResponseBody); - _requestAbortCts = CancellationTokenSource.CreateLinkedTokenSource(_disconnectCts.Token); - RequestAborted = _requestAbortCts.Token; + _abortedCts = null; + _manuallySetRequestAbortToken = null; var httpContext = HttpContextFactory.Create(this); try @@ -351,7 +394,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http { try { - _disconnectCts.Dispose(); + _abortedCts = null; // If _requestAborted is set, the connection has already been closed. if (!_requestAborted)