Handle client and server aborts differently from eachhother (#2612)
* Trace when app aborts connection * Improve exception messages * Always abort connection with ConnectionAbortedException * Add ConnectionContext.Abort(Exception)
This commit is contained in:
parent
aa9b9ca724
commit
0aff4a0440
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||
|
|
@ -204,6 +205,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AbortCore(ConnectionAbortedException abortReason)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from https://github.com/aspnet/benchmarks/blob/dev/src/Benchmarks/Middleware/PlaintextMiddleware.cs
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
public void RequestBodyDrainTimedOut(string connectionId, string traceIdentifier) { }
|
||||
public void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate) { }
|
||||
public void ResponseMininumDataRateNotSatisfied(string connectionId, string traceIdentifier) { }
|
||||
public void ApplicationAbortedConnection(string connectionId, string traceIdentifier) { }
|
||||
public void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex) { }
|
||||
public void Http2StreamError(string connectionId, Http2StreamErrorException ex) { }
|
||||
public void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex) { }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipelines;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Connections
|
||||
|
|
@ -13,5 +17,13 @@ namespace Microsoft.AspNetCore.Connections
|
|||
public abstract IDictionary<object, object> Items { get; set; }
|
||||
|
||||
public abstract IDuplexPipe Transport { get; set; }
|
||||
|
||||
public virtual void Abort(ConnectionAbortedException abortReason)
|
||||
{
|
||||
// We expect this to be overridden, but this helps maintain back compat
|
||||
// with implementations of ConnectionContext that predate the addition of
|
||||
// ConnectioContext.Abort()
|
||||
Features.Get<IConnectionLifetimeFeature>()?.Abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,9 @@ namespace Microsoft.AspNetCore.Connections
|
|||
|
||||
public CancellationToken ConnectionClosed { get; set; }
|
||||
|
||||
public virtual void Abort()
|
||||
public void Abort() => Abort(abortReason: null);
|
||||
|
||||
public override void Abort(ConnectionAbortedException abortReason)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(cts => ((CancellationTokenSource)cts).Cancel(), _connectionClosedTokenSource);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
public interface IConnectionLifetimeFeature
|
||||
{
|
||||
CancellationToken ConnectionClosed { get; set; }
|
||||
|
||||
void Abort();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ using System.IO;
|
|||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
|
||||
{
|
||||
|
|
@ -15,23 +17,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
|
|||
private static readonly int MinAllocBufferSize = KestrelMemoryPool.MinimumSegmentSize / 2;
|
||||
|
||||
private readonly IDuplexPipe _transport;
|
||||
private readonly IDuplexPipe _application;
|
||||
|
||||
public AdaptedPipeline(IDuplexPipe transport,
|
||||
IDuplexPipe application,
|
||||
Pipe inputPipe,
|
||||
Pipe outputPipe)
|
||||
Pipe outputPipe,
|
||||
IKestrelTrace log)
|
||||
{
|
||||
_transport = transport;
|
||||
_application = application;
|
||||
Input = inputPipe;
|
||||
Output = outputPipe;
|
||||
Log = log;
|
||||
}
|
||||
|
||||
public Pipe Input { get; }
|
||||
|
||||
public Pipe Output { get; }
|
||||
|
||||
public IKestrelTrace Log { get; }
|
||||
|
||||
PipeReader IDuplexPipe.Input => Input.Reader;
|
||||
|
||||
PipeWriter IDuplexPipe.Output => Output.Writer;
|
||||
|
|
@ -47,8 +50,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
|
|||
|
||||
private async Task WriteOutputAsync(Stream stream)
|
||||
{
|
||||
Exception error = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (stream == null)
|
||||
|
|
@ -63,13 +64,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
|
|||
|
||||
try
|
||||
{
|
||||
if (result.IsCanceled)
|
||||
{
|
||||
// Forward the cancellation to the transport pipe
|
||||
_application.Input.CancelPendingRead();
|
||||
break;
|
||||
}
|
||||
|
||||
if (buffer.IsEmpty)
|
||||
{
|
||||
if (result.IsCompleted)
|
||||
|
|
@ -108,7 +102,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
Log.LogError(0, ex, $"{nameof(AdaptedPipeline)}.{nameof(WriteOutputAsync)}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -506,4 +506,16 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
|||
<data name="BadRequest_RequestBodyTimeout" xml:space="preserve">
|
||||
<value>Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.</value>
|
||||
</data>
|
||||
<data name="ConnectionAbortedByApplication" xml:space="preserve">
|
||||
<value>The connection was aborted by the application.</value>
|
||||
</data>
|
||||
<data name="ConnectionAbortedDuringServerShutdown" xml:space="preserve">
|
||||
<value>The connection was aborted because the server is shutting down and request processing didn't complete within the time specified by HostOptions.ShutdownTimeout.</value>
|
||||
</data>
|
||||
<data name="ConnectionTimedBecauseResponseMininumDataRateNotSatisfied" xml:space="preserve">
|
||||
<value>The connection was timed out by the server because the response was not read by the client at the specified minimum data rate.</value>
|
||||
</data>
|
||||
<data name="ConnectionTimedOutByServer" xml:space="preserve">
|
||||
<value>The connection was timed out by the server.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -45,12 +45,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_requestHeadersTimeoutTicks = ServerOptions.Limits.RequestHeadersTimeout.Ticks;
|
||||
|
||||
Output = new Http1OutputProducer(
|
||||
_context.Application.Input,
|
||||
_context.Transport.Output,
|
||||
_context.ConnectionId,
|
||||
_context.ConnectionContext,
|
||||
_context.ServiceContext.Log,
|
||||
_context.TimeoutControl,
|
||||
_context.ConnectionFeatures.Get<IConnectionLifetimeFeature>(),
|
||||
_context.ConnectionFeatures.Get<IBytesWrittenFeature>());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
public string ConnectionId { get; set; }
|
||||
public ServiceContext ServiceContext { get; set; }
|
||||
public ConnectionContext ConnectionContext { get; set; }
|
||||
public IFeatureCollection ConnectionFeatures { get; set; }
|
||||
public MemoryPool<byte> MemoryPool { get; set; }
|
||||
public IPEndPoint RemoteEndPoint { get; set; }
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||
|
|
@ -22,9 +23,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
private static readonly ReadOnlyMemory<byte> _endChunkedResponseBytes = new ReadOnlyMemory<byte>(Encoding.ASCII.GetBytes("0\r\n\r\n"));
|
||||
|
||||
private readonly string _connectionId;
|
||||
private readonly ConnectionContext _connectionContext;
|
||||
private readonly ITimeoutControl _timeoutControl;
|
||||
private readonly IKestrelTrace _log;
|
||||
private readonly IConnectionLifetimeFeature _lifetimeFeature;
|
||||
private readonly IBytesWrittenFeature _transportBytesWrittenFeature;
|
||||
|
||||
// This locks access to to all of the below fields
|
||||
|
|
@ -36,7 +37,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
private long _totalBytesCommitted;
|
||||
|
||||
private readonly PipeWriter _pipeWriter;
|
||||
private readonly PipeReader _outputPipeReader;
|
||||
|
||||
// https://github.com/dotnet/corefxlab/issues/1334
|
||||
// Pipelines don't support multiple awaiters on flush
|
||||
|
|
@ -48,21 +48,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
private ValueTask<FlushResult> _flushTask;
|
||||
|
||||
public Http1OutputProducer(
|
||||
PipeReader outputPipeReader,
|
||||
PipeWriter pipeWriter,
|
||||
string connectionId,
|
||||
ConnectionContext connectionContext,
|
||||
IKestrelTrace log,
|
||||
ITimeoutControl timeoutControl,
|
||||
IConnectionLifetimeFeature lifetimeFeature,
|
||||
IBytesWrittenFeature transportBytesWrittenFeature)
|
||||
{
|
||||
_outputPipeReader = outputPipeReader;
|
||||
_pipeWriter = pipeWriter;
|
||||
_connectionId = connectionId;
|
||||
_connectionContext = connectionContext;
|
||||
_timeoutControl = timeoutControl;
|
||||
_log = log;
|
||||
_flushCompleted = OnFlushCompleted;
|
||||
_lifetimeFeature = lifetimeFeature;
|
||||
_transportBytesWrittenFeature = transportBytesWrittenFeature;
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +167,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
public void Abort(Exception error)
|
||||
public void Abort(ConnectionAbortedException error)
|
||||
{
|
||||
// Abort can be called after Dispose if there's a flush timeout.
|
||||
// It's important to still call _lifetimeFeature.Abort() in this case.
|
||||
|
|
@ -181,17 +179,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
return;
|
||||
}
|
||||
|
||||
_aborted = true;
|
||||
_connectionContext.Abort(error);
|
||||
|
||||
if (!_completed)
|
||||
{
|
||||
_log.ConnectionDisconnect(_connectionId);
|
||||
_completed = true;
|
||||
|
||||
_outputPipeReader.CancelPendingRead();
|
||||
_pipeWriter.Complete(error);
|
||||
_pipeWriter.Complete();
|
||||
}
|
||||
|
||||
_aborted = true;
|
||||
_lifetimeFeature.Abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -227,7 +227,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
void IHttpRequestLifetimeFeature.Abort()
|
||||
{
|
||||
Abort(new ConnectionAbortedException());
|
||||
Log.ApplicationAbortedConnection(ConnectionId, TraceIdentifier);
|
||||
Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,11 +39,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
protected Streams _streams;
|
||||
|
||||
protected Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
|
||||
protected Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;
|
||||
private Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
|
||||
private Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;
|
||||
|
||||
protected volatile int _requestAborted;
|
||||
protected CancellationTokenSource _abortedCts;
|
||||
private int _requestAborted;
|
||||
private volatile int _ioCompleted;
|
||||
private CancellationTokenSource _abortedCts;
|
||||
private CancellationToken? _manuallySetRequestAbortToken;
|
||||
|
||||
protected RequestProcessingStatus _requestProcessingStatus;
|
||||
|
|
@ -51,15 +52,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
protected bool _upgradeAvailable;
|
||||
private bool _canHaveBody;
|
||||
private bool _autoChunk;
|
||||
protected Exception _applicationException;
|
||||
private Exception _applicationException;
|
||||
private BadHttpRequestException _requestRejectedException;
|
||||
|
||||
protected HttpVersion _httpVersion;
|
||||
|
||||
private string _requestId;
|
||||
protected int _requestHeadersParsed;
|
||||
private int _requestHeadersParsed;
|
||||
|
||||
protected long _responseBytesWritten;
|
||||
private long _responseBytesWritten;
|
||||
|
||||
private readonly IHttpProtocolContext _context;
|
||||
|
||||
|
|
@ -247,7 +248,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var cts = _abortedCts;
|
||||
return
|
||||
cts != null ? cts.Token :
|
||||
(_requestAborted == 1) ? new CancellationToken(true) :
|
||||
(_ioCompleted == 1) ? new CancellationToken(true) :
|
||||
RequestAbortedSource.Token;
|
||||
}
|
||||
set
|
||||
|
|
@ -272,7 +273,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource())
|
||||
?? new CancellationTokenSource();
|
||||
|
||||
if (_requestAborted == 1)
|
||||
if (_ioCompleted == 1)
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
|
|
@ -410,32 +411,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately kill the connection and poison the request and response streams with an error if there is one.
|
||||
/// </summary>
|
||||
public void Abort(Exception error)
|
||||
public void OnInputOrOutputCompleted()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _requestAborted, 1) != 0)
|
||||
if (Interlocked.Exchange(ref _ioCompleted, 1) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_keepAlive = false;
|
||||
|
||||
// If Abort() isn't called with an exception, there was a FIN. In this case, even though the connection is
|
||||
// still closed immediately, we allow the app to drain the data in the request buffer. If the request data
|
||||
// was truncated, MessageBody will complete the RequestBodyPipe with an error.
|
||||
if (error != null)
|
||||
{
|
||||
_streams?.Abort(error);
|
||||
}
|
||||
|
||||
Output.Abort(error);
|
||||
Output.Dispose();
|
||||
|
||||
// Potentially calling user code. CancelRequestAbortedToken logs any exceptions.
|
||||
ServiceContext.Scheduler.Schedule(state => ((HttpProtocol)state).CancelRequestAbortedToken(), this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately kill the connection and poison the request and response streams with an error if there is one.
|
||||
/// </summary>
|
||||
public void Abort(ConnectionAbortedException abortReason)
|
||||
{
|
||||
if (Interlocked.Exchange(ref _requestAborted, 1) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_streams?.Abort(abortReason);
|
||||
|
||||
// Abort output prior to calling OnIOCompleted() to give the transport the chance to
|
||||
// complete the input with the correct error and message.
|
||||
Output.Abort(abortReason);
|
||||
|
||||
OnInputOrOutputCompleted();
|
||||
}
|
||||
|
||||
public void OnHeader(Span<byte> name, Span<byte> value)
|
||||
{
|
||||
_requestHeadersParsed++;
|
||||
|
|
@ -543,7 +552,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// Run the application code for this request
|
||||
await application.ProcessRequestAsync(httpContext);
|
||||
|
||||
if (_requestAborted == 0)
|
||||
if (_ioCompleted == 0)
|
||||
{
|
||||
VerifyResponseContentLength();
|
||||
}
|
||||
|
|
@ -579,8 +588,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
// 4XX responses are written by TryProduceInvalidRequestResponse during connection tear down.
|
||||
if (_requestRejectedException == null)
|
||||
{
|
||||
// If _requestAbort is set, the connection has already been closed.
|
||||
if (_requestAborted == 0)
|
||||
if (_ioCompleted == 0)
|
||||
{
|
||||
// Call ProduceEnd() before consuming the rest of the request body to prevent
|
||||
// delaying clients waiting for the chunk terminator:
|
||||
|
|
@ -612,7 +620,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
application.DisposeContext(httpContext, _applicationException);
|
||||
|
||||
// Even for non-keep-alive requests, try to consume the entire body to avoid RSTs.
|
||||
if (_requestAborted == 0 && _requestRejectedException == null && !messageBody.IsEmpty)
|
||||
if (_ioCompleted == 0 && _requestRejectedException == null && !messageBody.IsEmpty)
|
||||
{
|
||||
await messageBody.ConsumeAsync();
|
||||
}
|
||||
|
|
@ -1010,8 +1018,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
protected Task TryProduceInvalidRequestResponse()
|
||||
{
|
||||
// If _requestAborted is set, the connection has already been closed.
|
||||
if (_requestRejectedException != null && _requestAborted == 0)
|
||||
// If _ioCompleted is set, the connection has already been closed.
|
||||
if (_requestRejectedException != null && _ioCompleted == 0)
|
||||
{
|
||||
return ProduceEnd();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ using System;
|
|||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||
{
|
||||
public interface IHttpOutputProducer : IDisposable
|
||||
{
|
||||
void Abort(Exception error);
|
||||
void Abort(ConnectionAbortedException abortReason);
|
||||
Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state);
|
||||
Task FlushAsync(CancellationToken cancellationToken);
|
||||
Task Write100ContinueAsync(CancellationToken cancellationToken);
|
||||
|
|
|
|||
|
|
@ -89,7 +89,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures;
|
||||
|
||||
public void Abort(Exception ex)
|
||||
public void OnInputOrOutputCompleted()
|
||||
{
|
||||
_stopping = true;
|
||||
_frameWriter.Abort(ex: null);
|
||||
}
|
||||
|
||||
public void Abort(ConnectionAbortedException ex)
|
||||
{
|
||||
_stopping = true;
|
||||
_frameWriter.Abort(ex);
|
||||
|
|
@ -202,7 +208,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
{
|
||||
foreach (var stream in _streams.Values)
|
||||
{
|
||||
stream.Abort(error);
|
||||
stream.Http2Abort(error);
|
||||
}
|
||||
|
||||
await _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, errorCode);
|
||||
|
|
@ -464,7 +470,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
|
||||
{
|
||||
stream.Abort(error: null);
|
||||
stream.Abort(abortReason: null);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
{
|
||||
}
|
||||
|
||||
public void Abort(Exception error)
|
||||
public void Abort(ConnectionAbortedException error)
|
||||
{
|
||||
// TODO: RST_STREAM?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,5 +142,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
RequestBodyPipe.Writer.Complete(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The HTTP/2 tests expect the request and response streams to be aborted with
|
||||
// non-ConnectionAbortedExceptions. The abortReasons can include things like
|
||||
// Http2ConnectionErrorException which don't derive from IOException or
|
||||
// OperationCanceledException. This is probably not a good idea.
|
||||
public void Http2Abort(Exception abortReason)
|
||||
{
|
||||
_streams?.Abort(abortReason);
|
||||
|
||||
OnInputOrOutputCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ using System.IO.Pipelines;
|
|||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
||||
|
|
@ -123,9 +125,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
if (_context.ConnectionAdapters.Count > 0)
|
||||
{
|
||||
adaptedPipeline = new AdaptedPipeline(_adaptedTransport,
|
||||
application,
|
||||
new Pipe(AdaptedInputPipeOptions),
|
||||
new Pipe(AdaptedOutputPipeOptions));
|
||||
new Pipe(AdaptedOutputPipeOptions),
|
||||
Log);
|
||||
|
||||
_adaptedTransport = adaptedPipeline;
|
||||
}
|
||||
|
|
@ -222,6 +224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
LocalEndPoint = LocalEndPoint,
|
||||
RemoteEndPoint = RemoteEndPoint,
|
||||
ServiceContext = _context.ServiceContext,
|
||||
ConnectionContext = _context.ConnectionContext,
|
||||
TimeoutControl = this,
|
||||
Transport = transport,
|
||||
Application = application
|
||||
|
|
@ -255,15 +258,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
switch (_protocolSelectionState)
|
||||
{
|
||||
case ProtocolSelectionState.Initializing:
|
||||
CloseUninitializedConnection();
|
||||
_protocolSelectionState = ProtocolSelectionState.Stopped;
|
||||
CloseUninitializedConnection(abortReason: null);
|
||||
_protocolSelectionState = ProtocolSelectionState.Aborted;
|
||||
break;
|
||||
case ProtocolSelectionState.Selected:
|
||||
_requestProcessor.StopProcessingNextRequest();
|
||||
_protocolSelectionState = ProtocolSelectionState.Stopping;
|
||||
break;
|
||||
case ProtocolSelectionState.Stopping:
|
||||
case ProtocolSelectionState.Stopped:
|
||||
case ProtocolSelectionState.Aborted:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -271,28 +272,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
return _lifetimeTask;
|
||||
}
|
||||
|
||||
public void Abort(Exception ex)
|
||||
public void OnInputOrOutputCompleted()
|
||||
{
|
||||
lock (_protocolSelectionLock)
|
||||
{
|
||||
switch (_protocolSelectionState)
|
||||
{
|
||||
case ProtocolSelectionState.Initializing:
|
||||
CloseUninitializedConnection();
|
||||
CloseUninitializedConnection(abortReason: null);
|
||||
_protocolSelectionState = ProtocolSelectionState.Aborted;
|
||||
break;
|
||||
case ProtocolSelectionState.Selected:
|
||||
case ProtocolSelectionState.Stopping:
|
||||
_requestProcessor.Abort(ex);
|
||||
_requestProcessor.OnInputOrOutputCompleted();
|
||||
break;
|
||||
case ProtocolSelectionState.Stopped:
|
||||
case ProtocolSelectionState.Aborted:
|
||||
break;
|
||||
}
|
||||
|
||||
_protocolSelectionState = ProtocolSelectionState.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
public Task AbortAsync(Exception ex)
|
||||
public void Abort(ConnectionAbortedException ex)
|
||||
{
|
||||
lock (_protocolSelectionLock)
|
||||
{
|
||||
switch (_protocolSelectionState)
|
||||
{
|
||||
case ProtocolSelectionState.Initializing:
|
||||
CloseUninitializedConnection(ex);
|
||||
break;
|
||||
case ProtocolSelectionState.Selected:
|
||||
_requestProcessor.Abort(ex);
|
||||
break;
|
||||
case ProtocolSelectionState.Aborted:
|
||||
break;
|
||||
}
|
||||
|
||||
_protocolSelectionState = ProtocolSelectionState.Aborted;
|
||||
}
|
||||
}
|
||||
|
||||
public Task AbortAsync(ConnectionAbortedException ex)
|
||||
{
|
||||
Abort(ex);
|
||||
|
||||
|
|
@ -373,7 +393,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
|
||||
public void Tick(DateTimeOffset now)
|
||||
{
|
||||
if (_protocolSelectionState == ProtocolSelectionState.Stopped)
|
||||
if (_protocolSelectionState == ProtocolSelectionState.Aborted)
|
||||
{
|
||||
// It's safe to check for timeouts on a dead connection,
|
||||
// but try not to in order to avoid extraneous logs.
|
||||
|
|
@ -419,7 +439,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
break;
|
||||
case TimeoutAction.AbortConnection:
|
||||
// This is actually supported with HTTP/2!
|
||||
Abort(new TimeoutException());
|
||||
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedOutByServer));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -482,7 +502,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
RequestTimedOut = true;
|
||||
Log.ResponseMininumDataRateNotSatisfied(_http1Connection.ConnectionIdFeature, _http1Connection.TraceIdentifier);
|
||||
Abort(new TimeoutException());
|
||||
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -622,13 +642,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
ResetTimeout(timeSpan.Ticks, TimeoutAction.AbortConnection);
|
||||
}
|
||||
|
||||
private void CloseUninitializedConnection()
|
||||
private void CloseUninitializedConnection(ConnectionAbortedException abortReason)
|
||||
{
|
||||
Debug.Assert(_adaptedTransport != null);
|
||||
|
||||
// CancelPendingRead signals the transport directly to close the connection
|
||||
// without any potential interference from connection adapters.
|
||||
_context.Application.Input.CancelPendingRead();
|
||||
_context.ConnectionContext.Abort(abortReason);
|
||||
|
||||
_adaptedTransport.Input.Complete();
|
||||
_adaptedTransport.Output.Complete();
|
||||
|
|
@ -638,8 +656,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
Initializing,
|
||||
Selected,
|
||||
Stopping,
|
||||
Stopped
|
||||
Aborted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
|
@ -75,11 +77,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
var processingTask = connection.StartRequestProcessing(_application);
|
||||
|
||||
connectionContext.Transport.Input.OnWriterCompleted(
|
||||
(error, state) => ((HttpConnection)state).Abort(error),
|
||||
(_, state) => ((HttpConnection)state).OnInputOrOutputCompleted(),
|
||||
connection);
|
||||
|
||||
connectionContext.Transport.Output.OnReaderCompleted(
|
||||
(error, state) => ((HttpConnection)state).Abort(error),
|
||||
(_, state) => ((HttpConnection)state).OnInputOrOutputCompleted(),
|
||||
connection);
|
||||
|
||||
await AsTask(lifetimeFeature.ConnectionClosed);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||
|
|
@ -11,6 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application);
|
||||
void StopProcessingNextRequest();
|
||||
void Abort(Exception ex);
|
||||
void OnInputOrOutputCompleted();
|
||||
void Abort(ConnectionAbortedException ex);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
public static async Task<bool> AbortAllConnectionsAsync(this HttpConnectionManager connectionManager)
|
||||
{
|
||||
var abortTasks = new List<Task>();
|
||||
var canceledException = new ConnectionAbortedException();
|
||||
var canceledException = new ConnectionAbortedException(CoreStrings.ConnectionAbortedDuringServerShutdown);
|
||||
|
||||
connectionManager.Walk(connection =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
|
||||
void ResponseMininumDataRateNotSatisfied(string connectionId, string traceIdentifier);
|
||||
|
||||
void ApplicationAbortedConnection(string connectionId, string traceIdentifier);
|
||||
|
||||
void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex);
|
||||
|
||||
void Http2StreamError(string connectionId, Http2StreamErrorException ex);
|
||||
|
|
|
|||
|
|
@ -65,12 +65,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
private static readonly Action<ILogger, string, string, double, Exception> _requestBodyMinimumDataRateNotSatisfied =
|
||||
LoggerMessage.Define<string, string, double>(LogLevel.Information, new EventId(27, nameof(RequestBodyMininumDataRateNotSatisfied)), @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": the request timed out because it was not sent by the client at a minimum of {Rate} bytes/second.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, Exception> _requestBodyNotEntirelyRead =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Information, new EventId(32, nameof(RequestBodyNotEntirelyRead)), @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": the application completed without reading the entire request body.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, Exception> _requestBodyDrainTimedOut =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Information, new EventId(33, nameof(RequestBodyDrainTimedOut)), @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": automatic draining of the request body timed out after taking over 5 seconds.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, Exception> _responseMinimumDataRateNotSatisfied =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Information, new EventId(28, nameof(ResponseMininumDataRateNotSatisfied)), @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": the connection was closed because the response was not read by the client at the specified minimum data rate.");
|
||||
|
||||
|
|
@ -83,6 +77,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
private static readonly Action<ILogger, string, int, Exception> _hpackDecodingError =
|
||||
LoggerMessage.Define<string, int>(LogLevel.Information, new EventId(31, nameof(HPackDecodingError)), @"Connection id ""{ConnectionId}"": HPACK decoding error while decoding headers for stream ID {StreamId}.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, Exception> _requestBodyNotEntirelyRead =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Information, new EventId(32, nameof(RequestBodyNotEntirelyRead)), @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": the application completed without reading the entire request body.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, Exception> _requestBodyDrainTimedOut =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Information, new EventId(33, nameof(RequestBodyDrainTimedOut)), @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": automatic draining of the request body timed out after taking over 5 seconds.");
|
||||
|
||||
private static readonly Action<ILogger, string, string, Exception> _applicationAbortedConnection =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Information, new EventId(34, nameof(RequestBodyDrainTimedOut)), @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": the application aborted the connection.");
|
||||
|
||||
protected readonly ILogger _logger;
|
||||
|
||||
public KestrelTrace(ILogger logger)
|
||||
|
|
@ -195,6 +198,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
_responseMinimumDataRateNotSatisfied(_logger, connectionId, traceIdentifier, null);
|
||||
}
|
||||
|
||||
public virtual void ApplicationAbortedConnection(string connectionId, string traceIdentifier)
|
||||
{
|
||||
_applicationAbortedConnection(_logger, connectionId, traceIdentifier, null);
|
||||
}
|
||||
|
||||
public virtual void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex)
|
||||
{
|
||||
_http2ConnectionError(_logger, connectionId, ex);
|
||||
|
|
|
|||
|
|
@ -1820,6 +1820,62 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
internal static string FormatBadRequest_RequestBodyTimeout()
|
||||
=> GetString("BadRequest_RequestBodyTimeout");
|
||||
|
||||
/// <summary>
|
||||
/// The connection was aborted by the application.
|
||||
/// </summary>
|
||||
internal static string ConnectionAbortedByApplication
|
||||
{
|
||||
get => GetString("ConnectionAbortedByApplication");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The connection was aborted by the application.
|
||||
/// </summary>
|
||||
internal static string FormatConnectionAbortedByApplication()
|
||||
=> GetString("ConnectionAbortedByApplication");
|
||||
|
||||
/// <summary>
|
||||
/// The connection was aborted because the server is shutting down and request processing didn't complete within the time specified by HostOptions.ShutdownTimeout.
|
||||
/// </summary>
|
||||
internal static string ConnectionAbortedDuringServerShutdown
|
||||
{
|
||||
get => GetString("ConnectionAbortedDuringServerShutdown");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The connection was aborted because the server is shutting down and request processing didn't complete within the time specified by HostOptions.ShutdownTimeout.
|
||||
/// </summary>
|
||||
internal static string FormatConnectionAbortedDuringServerShutdown()
|
||||
=> GetString("ConnectionAbortedDuringServerShutdown");
|
||||
|
||||
/// <summary>
|
||||
/// The connection was timed out by the server because the response was not read by the client at the specified minimum data rate.
|
||||
/// </summary>
|
||||
internal static string ConnectionTimedBecauseResponseMininumDataRateNotSatisfied
|
||||
{
|
||||
get => GetString("ConnectionTimedBecauseResponseMininumDataRateNotSatisfied");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The connection was timed out by the server because the response was not read by the client at the specified minimum data rate.
|
||||
/// </summary>
|
||||
internal static string FormatConnectionTimedBecauseResponseMininumDataRateNotSatisfied()
|
||||
=> GetString("ConnectionTimedBecauseResponseMininumDataRateNotSatisfied");
|
||||
|
||||
/// <summary>
|
||||
/// The connection was timed out by the server.
|
||||
/// </summary>
|
||||
internal static string ConnectionTimedOutByServer
|
||||
{
|
||||
get => GetString("ConnectionTimedOutByServer");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The connection was timed out by the server.
|
||||
/// </summary>
|
||||
internal static string FormatConnectionTimedOutByServer()
|
||||
=> GetString("ConnectionTimedOutByServer");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
|
|
@ -84,7 +85,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
|
|||
set => ConnectionClosed = value;
|
||||
}
|
||||
|
||||
void IConnectionLifetimeFeature.Abort() => Abort();
|
||||
void IConnectionLifetimeFeature.Abort() => Abort(abortReason: null);
|
||||
|
||||
long IBytesWrittenFeature.TotalBytesWritten => TotalBytesWritten;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,21 +11,13 @@ using Microsoft.AspNetCore.Http.Features;
|
|||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
|
||||
{
|
||||
public partial class TransportConnection : ConnectionContext
|
||||
public abstract partial class TransportConnection : ConnectionContext
|
||||
{
|
||||
private IDictionary<object, object> _items;
|
||||
|
||||
public TransportConnection()
|
||||
{
|
||||
_currentIConnectionIdFeature = this;
|
||||
_currentIConnectionTransportFeature = this;
|
||||
_currentIHttpConnectionFeature = this;
|
||||
_currentIConnectionItemsFeature = this;
|
||||
_currentIApplicationTransportFeature = this;
|
||||
_currentIMemoryPoolFeature = this;
|
||||
_currentITransportSchedulerFeature = this;
|
||||
_currentIConnectionLifetimeFeature = this;
|
||||
_currentIBytesWrittenFeature = this;
|
||||
FastReset();
|
||||
}
|
||||
|
||||
public IPAddress RemoteAddress { get; set; }
|
||||
|
|
@ -63,8 +55,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
|
|||
|
||||
public CancellationToken ConnectionClosed { get; set; }
|
||||
|
||||
public virtual void Abort()
|
||||
// DO NOT remove this override to ConnectionContext.Abort(). Doing so would cause
|
||||
// any TransportConnection that does not override Abort() or calls base.Abort()
|
||||
// to stack overflow when IConnectionLifetimeFeature.Abort() is called.
|
||||
public override void Abort(ConnectionAbortedException abortReason)
|
||||
{
|
||||
AbortCore(abortReason);
|
||||
}
|
||||
|
||||
protected abstract void AbortCore(ConnectionAbortedException abortReason);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
private readonly UvStreamHandle _socket;
|
||||
private readonly CancellationTokenSource _connectionClosedTokenSource = new CancellationTokenSource();
|
||||
|
||||
private volatile ConnectionAbortedException _abortReason;
|
||||
|
||||
private MemoryHandle _bufferHandle;
|
||||
|
||||
public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread thread, IPEndPoint remoteEndPoint, IPEndPoint localEndPoint)
|
||||
|
|
@ -93,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
finally
|
||||
{
|
||||
// Now, complete the input so that no more reads can happen
|
||||
Input.Complete(inputError ?? new ConnectionAbortedException());
|
||||
Input.Complete(inputError ?? _abortReason ?? new ConnectionAbortedException());
|
||||
Output.Complete(outputError);
|
||||
|
||||
// Make sure it isn't possible for a paused read to resume reading after calling uv_close
|
||||
|
|
@ -114,8 +116,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
}
|
||||
}
|
||||
|
||||
public override void Abort()
|
||||
protected override void AbortCore(ConnectionAbortedException abortReason)
|
||||
{
|
||||
_abortReason = abortReason;
|
||||
Output.CancelPendingRead();
|
||||
|
||||
// This cancels any pending I/O.
|
||||
Thread.Post(s => s.Dispose(), _socket);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,17 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
|
||||
while (true)
|
||||
{
|
||||
ReadResult result;
|
||||
|
||||
try
|
||||
{
|
||||
result = await _pipe.ReadAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Handled in LibuvConnection.Abort()
|
||||
return;
|
||||
}
|
||||
var result = await _pipe.ReadAsync();
|
||||
|
||||
var buffer = result.Buffer;
|
||||
var consumed = buffer.End;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
|||
|
||||
private readonly object _shutdownLock = new object();
|
||||
private volatile bool _aborted;
|
||||
private volatile ConnectionAbortedException _abortReason;
|
||||
private long _totalBytesWritten;
|
||||
|
||||
internal SocketConnection(Socket socket, MemoryPool<byte> memoryPool, PipeScheduler scheduler, ISocketsTrace trace)
|
||||
|
|
@ -91,8 +92,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
|||
}
|
||||
}
|
||||
|
||||
public override void Abort()
|
||||
protected override void AbortCore(ConnectionAbortedException abortReason)
|
||||
{
|
||||
_abortReason = abortReason;
|
||||
Output.CancelPendingRead();
|
||||
|
||||
// Try to gracefully close the socket to match libuv behavior.
|
||||
Shutdown();
|
||||
}
|
||||
|
|
@ -125,7 +129,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
|||
if (!_aborted)
|
||||
{
|
||||
// Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix.
|
||||
error = new ConnectionAbortedException();
|
||||
_trace.ConnectionError(ConnectionId, error);
|
||||
}
|
||||
}
|
||||
|
|
@ -133,7 +136,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
|||
{
|
||||
if (!_aborted)
|
||||
{
|
||||
error = new ConnectionAbortedException();
|
||||
_trace.ConnectionError(ConnectionId, error);
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +153,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
|||
{
|
||||
if (_aborted)
|
||||
{
|
||||
error = error ?? new ConnectionAbortedException();
|
||||
error = error ?? _abortReason ?? new ConnectionAbortedException();
|
||||
}
|
||||
|
||||
Input.Complete(error);
|
||||
|
|
@ -221,10 +223,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
|||
catch (IOException ex)
|
||||
{
|
||||
error = ex;
|
||||
_trace.ConnectionError(ConnectionId, error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = new IOException(ex.Message, ex);
|
||||
_trace.ConnectionError(ConnectionId, error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -239,8 +243,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
// Wait for data to write from the pipe producer
|
||||
var result = await Output.ReadAsync();
|
||||
|
||||
var buffer = result.Buffer;
|
||||
|
||||
if (result.IsCanceled)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||
|
|
@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
var tcs = new TaskCompletionSource<object>();
|
||||
var dispatcher = new ConnectionDispatcher(serviceContext, _ => tcs.Task);
|
||||
|
||||
var connection = new TransportConnection();
|
||||
var connection = Mock.Of<TransportConnection>();
|
||||
|
||||
dispatcher.OnConnection(connection);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
|
@ -58,6 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_http1ConnectionContext = new Http1ConnectionContext
|
||||
{
|
||||
ServiceContext = _serviceContext,
|
||||
ConnectionContext = Mock.Of<ConnectionContext>(),
|
||||
ConnectionFeatures = connectionFeatures,
|
||||
MemoryPool = _pipelineFactory,
|
||||
TimeoutControl = _timeoutControl.Object,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
||||
|
|
@ -38,6 +39,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_httpConnectionContext = new HttpConnectionContext
|
||||
{
|
||||
ConnectionId = "0123456789",
|
||||
ConnectionContext = Mock.Of<ConnectionContext>(),
|
||||
ConnectionAdapters = new List<IConnectionAdapter>(),
|
||||
ConnectionFeatures = connectionFeatures,
|
||||
MemoryPool = _memoryPool,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
|
|
@ -61,39 +62,38 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Fact]
|
||||
public void AbortsTransportEvenAfterDispose()
|
||||
{
|
||||
var mockLifetimeFeature = new Mock<IConnectionLifetimeFeature>();
|
||||
var mockConnectionContext = new Mock<ConnectionContext>();
|
||||
|
||||
var outputProducer = CreateOutputProducer(lifetimeFeature: mockLifetimeFeature.Object);
|
||||
var outputProducer = CreateOutputProducer(connectionContext: mockConnectionContext.Object);
|
||||
|
||||
outputProducer.Dispose();
|
||||
|
||||
mockLifetimeFeature.Verify(f => f.Abort(), Times.Never());
|
||||
mockConnectionContext.Verify(f => f.Abort(It.IsAny<ConnectionAbortedException>()), Times.Never());
|
||||
|
||||
outputProducer.Abort(null);
|
||||
|
||||
mockLifetimeFeature.Verify(f => f.Abort(), Times.Once());
|
||||
mockConnectionContext.Verify(f => f.Abort(null), Times.Once());
|
||||
|
||||
outputProducer.Abort(null);
|
||||
|
||||
mockLifetimeFeature.Verify(f => f.Abort(), Times.Once());
|
||||
mockConnectionContext.Verify(f => f.Abort(null), Times.Once());
|
||||
}
|
||||
|
||||
private Http1OutputProducer CreateOutputProducer(
|
||||
PipeOptions pipeOptions = null,
|
||||
IConnectionLifetimeFeature lifetimeFeature = null)
|
||||
ConnectionContext connectionContext = null)
|
||||
{
|
||||
pipeOptions = pipeOptions ?? new PipeOptions();
|
||||
lifetimeFeature = lifetimeFeature ?? Mock.Of<IConnectionLifetimeFeature>();
|
||||
connectionContext = connectionContext ?? Mock.Of<ConnectionContext>();
|
||||
|
||||
var pipe = new Pipe(pipeOptions);
|
||||
var serviceContext = new TestServiceContext();
|
||||
var socketOutput = new Http1OutputProducer(
|
||||
pipe.Reader,
|
||||
pipe.Writer,
|
||||
"0",
|
||||
connectionContext,
|
||||
serviceContext.Log,
|
||||
Mock.Of<ITimeoutControl>(),
|
||||
lifetimeFeature,
|
||||
Mock.Of<IBytesWrittenFeature>());
|
||||
|
||||
return socketOutput;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Buffers;
|
|||
using System.IO.Pipelines;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
|
|
@ -35,6 +36,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Http1ConnectionContext = new Http1ConnectionContext
|
||||
{
|
||||
ServiceContext = new TestServiceContext(),
|
||||
ConnectionContext = Mock.Of<ConnectionContext>(),
|
||||
ConnectionFeatures = connectionFeatures,
|
||||
Application = Application,
|
||||
Transport = Transport,
|
||||
|
|
|
|||
|
|
@ -1131,6 +1131,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
[MemberData(nameof(ConnectionAdapterData))]
|
||||
public async Task RequestsCanBeAbortedMidRead(ListenOptions listenOptions)
|
||||
{
|
||||
const int applicationAbortedConnectionId = 34;
|
||||
|
||||
var testContext = new TestServiceContext(LoggerFactory);
|
||||
|
||||
var readTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
|
@ -1205,6 +1207,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
// The cancellation token for only the last request should be triggered.
|
||||
var abortedRequestId = await registrationTcs.Task;
|
||||
Assert.Equal(2, abortedRequestId);
|
||||
|
||||
Assert.Single(TestSink.Writes.Where(w => w.LoggerName == "Microsoft.AspNetCore.Server.Kestrel" &&
|
||||
w.EventId == applicationAbortedConnectionId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Collections.Concurrent;
|
|||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
|
|
@ -738,6 +739,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
|
|||
var http1Connection = new Http1Connection(new Http1ConnectionContext
|
||||
{
|
||||
ServiceContext = serviceContext,
|
||||
ConnectionContext = Mock.Of<ConnectionContext>(),
|
||||
ConnectionFeatures = connectionFeatures,
|
||||
MemoryPool = _memoryPool,
|
||||
TimeoutControl = Mock.Of<ITimeoutControl>(),
|
||||
|
|
@ -764,12 +766,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
|
|||
// Without ConfigureAwait(false), xunit will dispatch.
|
||||
await consumer.WriteOutputAsync().ConfigureAwait(false);
|
||||
|
||||
http1Connection.Abort(error: null);
|
||||
http1Connection.Abort(abortReason: null);
|
||||
outputReader.Complete();
|
||||
}
|
||||
catch (UvException ex)
|
||||
{
|
||||
http1Connection.Abort(ex);
|
||||
http1Connection.Abort(new ConnectionAbortedException(ex.Message, ex));
|
||||
outputReader.Complete(ex);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,6 +166,12 @@ namespace Microsoft.AspNetCore.Testing
|
|||
_trace2.ResponseMininumDataRateNotSatisfied(connectionId, traceIdentifier);
|
||||
}
|
||||
|
||||
public void ApplicationAbortedConnection(string connectionId, string traceIdentifier)
|
||||
{
|
||||
_trace1.ApplicationAbortedConnection(connectionId, traceIdentifier);
|
||||
_trace2.ApplicationAbortedConnection(connectionId, traceIdentifier);
|
||||
}
|
||||
|
||||
public void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex)
|
||||
{
|
||||
_trace1.Http2ConnectionError(connectionId, ex);
|
||||
|
|
|
|||
Loading…
Reference in New Issue