Merge branch 'release/2.2'

This commit is contained in:
Stephen Halter 2018-10-08 17:17:12 -07:00
commit 64769f2277
24 changed files with 460 additions and 262 deletions

View File

@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance namespace Microsoft.AspNetCore.Server.Kestrel.Performance
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(), ConnectionFeatures = new FeatureCollection(),
MemoryPool = memoryPool, MemoryPool = memoryPool,
TimeoutControl = new MockTimeoutControl(), TimeoutControl = new TimeoutControl(timeoutHandler: null),
Transport = pair.Transport Transport = pair.Transport
}); });

View File

@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance namespace Microsoft.AspNetCore.Server.Kestrel.Performance
@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(), ConnectionFeatures = new FeatureCollection(),
MemoryPool = memoryPool, MemoryPool = memoryPool,
TimeoutControl = new MockTimeoutControl(), TimeoutControl = new TimeoutControl(timeoutHandler: null),
Transport = pair.Transport Transport = pair.Transport
}); });

View File

@ -1,51 +0,0 @@
// 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 Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks
{
public class MockTimeoutControl : ITimeoutControl
{
public void CancelTimeout()
{
}
public void ResetTimeout(long ticks, TimeoutReason timeoutReason)
{
}
public void SetTimeout(long ticks, TimeoutReason timeoutReason)
{
}
public void StartTimingReads(MinDataRate minRate)
{
}
public void StopTimingReads()
{
}
public void PauseTimingReads()
{
}
public void ResumeTimingReads()
{
}
public void BytesRead(long count)
{
}
public void StartTimingWrite(MinDataRate rate, long size)
{
}
public void StopTimingWrite()
{
}
}
}

View File

@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance namespace Microsoft.AspNetCore.Server.Kestrel.Performance
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
ConnectionFeatures = new FeatureCollection(), ConnectionFeatures = new FeatureCollection(),
MemoryPool = _memoryPool, MemoryPool = _memoryPool,
Transport = pair.Transport, Transport = pair.Transport,
TimeoutControl = new MockTimeoutControl() TimeoutControl = new TimeoutControl(timeoutHandler: null)
}); });
http1Connection.Reset(); http1Connection.Reset();

View File

@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(), ConnectionFeatures = new FeatureCollection(),
MemoryPool = _memoryPool, MemoryPool = _memoryPool,
TimeoutControl = new MockTimeoutControl(), TimeoutControl = new TimeoutControl(timeoutHandler: null),
Transport = _pair.Transport Transport = _pair.Transport
}); });

View File

@ -122,6 +122,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Input.CancelPendingRead(); Input.CancelPendingRead();
} }
public void HandleRequestHeadersTimeout()
=> SendTimeoutResponse();
public void ParseRequest(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined) public void ParseRequest(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
{ {
consumed = buffer.Start; consumed = buffer.Start;

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Security.Authentication; using System.Security.Authentication;
@ -105,7 +106,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public PipeReader Input => _context.Transport.Input; public PipeReader Input => _context.Transport.Input;
public IKestrelTrace Log => _context.ServiceContext.Log; public IKestrelTrace Log => _context.ServiceContext.Log;
public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures; public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures;
public KestrelServerOptions ServerOptions => _context.ServiceContext.ServerOptions; public ITimeoutControl TimeoutControl => _context.TimeoutControl;
public KestrelServerLimits Limits => _context.ServiceContext.ServerOptions.Limits;
internal Http2PeerSettings ServerSettings => _serverSettings; internal Http2PeerSettings ServerSettings => _serverSettings;
@ -139,6 +141,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public void StopProcessingNextRequest() public void StopProcessingNextRequest()
=> StopProcessingNextRequest(true); => StopProcessingNextRequest(true);
public void HandleRequestHeadersTimeout()
{
Log.ConnectionBadRequest(ConnectionId, BadHttpRequestException.GetException(RequestRejectionReason.RequestHeadersTimeout));
Abort(new ConnectionAbortedException(CoreStrings.BadRequest_RequestHeadersTimeout));
}
public void StopProcessingNextRequest(bool sendGracefulGoAway = false) public void StopProcessingNextRequest(bool sendGracefulGoAway = false)
{ {
lock (_stateLock) lock (_stateLock)
@ -174,6 +182,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
try try
{ {
ValidateTlsRequirements(); ValidateTlsRequirements();
TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive);
if (!await TryReadPrefaceAsync()) if (!await TryReadPrefaceAsync())
{ {
@ -298,7 +307,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
await _streamsCompleted.Task; await _streamsCompleted.Task;
_context.TimeoutControl.StartDrainTimeout(ServerOptions.Limits.MinResponseDataRate, ServerOptions.Limits.MaxResponseBufferSize); TimeoutControl.StartDrainTimeout(Limits.MinResponseDataRate, Limits.MaxResponseBufferSize);
_frameWriter.Complete(); _frameWriter.Complete();
} }
@ -551,30 +560,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
} }
else else
{ {
// Start a new stream // Cancel keep-alive timeout and start header timeout if necessary. The keep-alive timeout can be
_currentHeadersStream = new Http2Stream(new Http2StreamContext // started on another thread so the lock is necessary.
lock (_stateLock)
{ {
ConnectionId = ConnectionId, if (TimeoutControl.TimerReason != TimeoutReason.None)
StreamId = _incomingFrame.StreamId, {
ServiceContext = _context.ServiceContext, Debug.Assert(TimeoutControl.TimerReason == TimeoutReason.KeepAlive, "Non keep-alive timeout set at start of stream.");
ConnectionFeatures = _context.ConnectionFeatures, TimeoutControl.CancelTimeout();
MemoryPool = _context.MemoryPool, }
LocalEndPoint = _context.LocalEndPoint,
RemoteEndPoint = _context.RemoteEndPoint,
StreamLifetimeHandler = this,
ClientPeerSettings = _clientSettings,
ServerPeerSettings = _serverSettings,
FrameWriter = _frameWriter,
ConnectionInputFlowControl = _inputFlowControl,
ConnectionOutputFlowControl = _outputFlowControl,
TimeoutControl = _context.TimeoutControl,
});
_currentHeadersStream.Reset(); if (!_incomingFrame.HeadersEndHeaders)
_headerFlags = _incomingFrame.HeadersFlags; {
TimeoutControl.SetTimeout(Limits.RequestHeadersTimeout.Ticks, TimeoutReason.RequestHeaders);
}
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding // Start a new stream
return DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, headersPayload); _currentHeadersStream = new Http2Stream(new Http2StreamContext
{
ConnectionId = ConnectionId,
StreamId = _incomingFrame.StreamId,
ServiceContext = _context.ServiceContext,
ConnectionFeatures = _context.ConnectionFeatures,
MemoryPool = _context.MemoryPool,
LocalEndPoint = _context.LocalEndPoint,
RemoteEndPoint = _context.RemoteEndPoint,
StreamLifetimeHandler = this,
ClientPeerSettings = _clientSettings,
ServerPeerSettings = _serverSettings,
FrameWriter = _frameWriter,
ConnectionInputFlowControl = _inputFlowControl,
ConnectionOutputFlowControl = _outputFlowControl,
TimeoutControl = TimeoutControl,
});
_currentHeadersStream.Reset();
_headerFlags = _incomingFrame.HeadersFlags;
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding
return DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, headersPayload);
}
} }
} }
@ -835,7 +860,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
} }
else else
{ {
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, payload); lock (_stateLock)
{
Debug.Assert(TimeoutControl.TimerReason == TimeoutReason.RequestHeaders, "Received continuation frame without request header timeout being set.");
if (_incomingFrame.HeadersEndHeaders)
{
TimeoutControl.CancelTimeout();
}
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, payload);
}
} }
} }
@ -849,24 +884,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask; return Task.CompletedTask;
} }
// This is always called with the _stateLock acquired.
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, ReadOnlySequence<byte> payload) private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, ReadOnlySequence<byte> payload)
{ {
try try
{ {
lock (_stateLock) _highestOpenedStreamId = _currentHeadersStream.StreamId;
_hpackDecoder.Decode(payload, endHeaders, handler: this);
if (endHeaders)
{ {
_highestOpenedStreamId = _currentHeadersStream.StreamId; if (_state != Http2ConnectionState.Closed)
_hpackDecoder.Decode(payload, endHeaders, handler: this);
if (endHeaders)
{ {
if (_state != Http2ConnectionState.Closed) StartStream(application);
{
StartStream(application);
}
ResetRequestHeaderParsingState();
} }
ResetRequestHeaderParsingState();
} }
} }
catch (Http2StreamErrorException) catch (Http2StreamErrorException)
@ -979,7 +1012,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
Input.CancelPendingRead(); Input.CancelPendingRead();
} }
if (_state != Http2ConnectionState.Open) if (_state == Http2ConnectionState.Open)
{
// If we're awaiting headers, either a new stream will be started, or there will be a connection
// error possibly due to a request header timeout, so no need to start a keep-alive timeout.
if (TimeoutControl.TimerReason != TimeoutReason.RequestHeaders)
{
TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive);
}
}
else
{ {
// Complete the task waiting on all streams to finish // Complete the task waiting on all streams to finish
_streamsCompleted.TrySetResult(null); _streamsCompleted.TrySetResult(null);
@ -1164,6 +1206,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
} }
else if (state == Http2ConnectionState.Closed) else if (state == Http2ConnectionState.Closed)
{ {
// This cancels keep-alive and request header timeouts, but not the response drain timeout.
TimeoutControl.CancelTimeout();
Log.Http2ConnectionClosed(_context.ConnectionId, _highestOpenedStreamId); Log.Http2ConnectionClosed(_context.ConnectionId, _highestOpenedStreamId);
} }
} }

View File

@ -47,10 +47,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_timeoutControl = new TimeoutControl(this); _timeoutControl = new TimeoutControl(this);
} }
// For testing
internal HttpProtocol Http1Connection => _http1Connection;
internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton;
public string ConnectionId => _context.ConnectionId; public string ConnectionId => _context.ConnectionId;
public IPEndPoint LocalEndPoint => _context.LocalEndPoint; public IPEndPoint LocalEndPoint => _context.LocalEndPoint;
public IPEndPoint RemoteEndPoint => _context.RemoteEndPoint; public IPEndPoint RemoteEndPoint => _context.RemoteEndPoint;
@ -196,9 +192,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
} }
// For testing only // For testing only
internal void Initialize(IDuplexPipe transport) internal void Initialize(IRequestProcessor requestProcessor)
{ {
_requestProcessor = _http1Connection = new Http1Connection(CreateDerivedContext(transport)); _requestProcessor = requestProcessor;
_http1Connection = requestProcessor as Http1Connection;
_protocolSelectionState = ProtocolSelectionState.Selected; _protocolSelectionState = ProtocolSelectionState.Selected;
} }
@ -379,17 +376,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
switch (reason) switch (reason)
{ {
case TimeoutReason.KeepAlive: case TimeoutReason.KeepAlive:
_http1Connection.StopProcessingNextRequest(); _requestProcessor.StopProcessingNextRequest();
break; break;
case TimeoutReason.RequestHeaders: case TimeoutReason.RequestHeaders:
_http1Connection.SendTimeoutResponse(); _requestProcessor.HandleRequestHeadersTimeout();
break; break;
case TimeoutReason.ReadDataRate: case TimeoutReason.ReadDataRate:
Log.RequestBodyMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection.TraceIdentifier, _http1Connection.MinRequestBodyDataRate.BytesPerSecond); Log.RequestBodyMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection.TraceIdentifier, _http1Connection.MinRequestBodyDataRate.BytesPerSecond);
_http1Connection.SendTimeoutResponse(); _http1Connection.SendTimeoutResponse();
break; break;
case TimeoutReason.WriteDataRate: case TimeoutReason.WriteDataRate:
Log.ResponseMinimumDataRateNotSatisfied(_http1Connection.ConnectionIdFeature, _http1Connection.TraceIdentifier); Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, _http1Connection?.TraceIdentifier);
Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)); Abort(new ConnectionAbortedException(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied));
break; break;
case TimeoutReason.RequestBodyDrain: case TimeoutReason.RequestBodyDrain:

View File

@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{ {
Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application); Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application);
void StopProcessingNextRequest(); void StopProcessingNextRequest();
void HandleRequestHeadersTimeout();
void OnInputOrOutputCompleted(); void OnInputOrOutputCompleted();
void Abort(ConnectionAbortedException ex); void Abort(ConnectionAbortedException ex);
} }

View File

@ -5,6 +5,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{ {
public interface ITimeoutControl public interface ITimeoutControl
{ {
TimeoutReason TimerReason { get; }
void SetTimeout(long ticks, TimeoutReason timeoutReason); void SetTimeout(long ticks, TimeoutReason timeoutReason);
void ResetTimeout(long ticks, TimeoutReason timeoutReason); void ResetTimeout(long ticks, TimeoutReason timeoutReason);
void CancelTimeout(); void CancelTimeout();

View File

@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
private long _lastTimestamp; private long _lastTimestamp;
private long _timeoutTimestamp = long.MaxValue; private long _timeoutTimestamp = long.MaxValue;
private TimeoutReason _timeoutReason;
private readonly object _readTimingLock = new object(); private readonly object _readTimingLock = new object();
private MinDataRate _minReadRate; private MinDataRate _minReadRate;
@ -32,6 +31,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
_timeoutHandler = timeoutHandler; _timeoutHandler = timeoutHandler;
} }
public TimeoutReason TimerReason { get; private set; }
internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton; internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton;
public void Initialize(DateTimeOffset now) public void Initialize(DateTimeOffset now)
@ -56,9 +57,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{ {
if (timestamp > Interlocked.Read(ref _timeoutTimestamp)) if (timestamp > Interlocked.Read(ref _timeoutTimestamp))
{ {
var timeoutReason = TimerReason;
CancelTimeout(); CancelTimeout();
_timeoutHandler.OnTimeout(_timeoutReason); _timeoutHandler.OnTimeout(timeoutReason);
} }
} }
} }
@ -117,7 +120,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
public void SetTimeout(long ticks, TimeoutReason timeoutReason) public void SetTimeout(long ticks, TimeoutReason timeoutReason)
{ {
Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported"); Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported.");
AssignTimeout(ticks, timeoutReason); AssignTimeout(ticks, timeoutReason);
} }
@ -130,11 +133,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
public void CancelTimeout() public void CancelTimeout()
{ {
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue); Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
TimerReason = TimeoutReason.None;
} }
private void AssignTimeout(long ticks, TimeoutReason timeoutReason) private void AssignTimeout(long ticks, TimeoutReason timeoutReason)
{ {
_timeoutReason = timeoutReason; TimerReason = timeoutReason;
// Add Heartbeat.Interval since this can be called right before the next heartbeat. // Add Heartbeat.Interval since this can be called right before the next heartbeat.
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + ticks + Heartbeat.Interval.Ticks); Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + ticks + Heartbeat.Interval.Ticks);

View File

@ -5,6 +5,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
{ {
public enum TimeoutReason public enum TimeoutReason
{ {
None,
KeepAlive, KeepAlive,
RequestHeaders, RequestHeaders,
ReadDataRate, ReadDataRate,

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
void ConnectionReadFin(string connectionId); void ConnectionReadFin(string connectionId);
void ConnectionWriteFin(string connectionId); void ConnectionWriteFin(string connectionId, string reason);
void ConnectionWrite(string connectionId, int count); void ConnectionWrite(string connectionId, int count);
@ -25,7 +25,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
void ConnectionPause(string connectionId); void ConnectionPause(string connectionId);
void ConnectionResume(string connectionId); void ConnectionResume(string connectionId);
void ConnectionAborted(string connectionId, string message);
} }
} }

View File

@ -95,8 +95,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
} }
finally finally
{ {
inputError = inputError ?? _abortReason ?? new ConnectionAbortedException("The libuv transport's send loop completed gracefully.");
// Now, complete the input so that no more reads can happen // Now, complete the input so that no more reads can happen
Input.Complete(inputError ?? _abortReason ?? new ConnectionAbortedException("The libuv transport's send loop completed gracefully.")); Input.Complete(inputError);
Output.Complete(outputError); Output.Complete(outputError);
// Make sure it isn't possible for a paused read to resume reading after calling uv_close // Make sure it isn't possible for a paused read to resume reading after calling uv_close
@ -104,7 +106,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
Input.CancelPendingFlush(); Input.CancelPendingFlush();
// Send a FIN // Send a FIN
Log.ConnectionWriteFin(ConnectionId); Log.ConnectionWriteFin(ConnectionId, inputError.Message);
// We're done with the socket now // We're done with the socket now
_socket.Dispose(); _socket.Dispose();
@ -119,8 +121,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
public override void Abort(ConnectionAbortedException abortReason) public override void Abort(ConnectionAbortedException abortReason)
{ {
Log.ConnectionAborted(ConnectionId, abortReason?.Message);
_abortReason = abortReason; _abortReason = abortReason;
// Cancel WriteOutputAsync loop after setting _abortReason. // Cancel WriteOutputAsync loop after setting _abortReason.

View File

@ -19,8 +19,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
private static readonly Action<ILogger, string, Exception> _connectionReadFin = private static readonly Action<ILogger, string, Exception> _connectionReadFin =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, nameof(ConnectionReadFin)), @"Connection id ""{ConnectionId}"" received FIN."); LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, nameof(ConnectionReadFin)), @"Connection id ""{ConnectionId}"" received FIN.");
private static readonly Action<ILogger, string, Exception> _connectionWriteFin = private static readonly Action<ILogger, string, string, Exception> _connectionWriteFin =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN."); LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN because: ""{Reason}""");
// ConnectionWrite: Reserved: 11 // ConnectionWrite: Reserved: 11
@ -32,9 +32,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
private static readonly Action<ILogger, string, Exception> _connectionReset = private static readonly Action<ILogger, string, Exception> _connectionReset =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(19, nameof(ConnectionReset)), @"Connection id ""{ConnectionId}"" reset."); LoggerMessage.Define<string>(LogLevel.Debug, new EventId(19, nameof(ConnectionReset)), @"Connection id ""{ConnectionId}"" reset.");
private static readonly Action<ILogger, string, string, Exception> _connectionAborted =
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(20, nameof(ConnectionAborted)), @"Connection id ""{ConnectionId}"" closing because: ""{Message}""");
private readonly ILogger _logger; private readonly ILogger _logger;
public LibuvTrace(ILogger logger) public LibuvTrace(ILogger logger)
@ -53,9 +50,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
_connectionReadFin(_logger, connectionId, null); _connectionReadFin(_logger, connectionId, null);
} }
public void ConnectionWriteFin(string connectionId) public void ConnectionWriteFin(string connectionId, string reason)
{ {
_connectionWriteFin(_logger, connectionId, null); _connectionWriteFin(_logger, connectionId, reason, null);
} }
public void ConnectionWrite(string connectionId, int count) public void ConnectionWrite(string connectionId, int count)
@ -90,11 +87,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
_connectionResume(_logger, connectionId, null); _connectionResume(_logger, connectionId, null);
} }
public void ConnectionAborted(string connectionId, string message)
{
_connectionAborted(_logger, connectionId, message, null);
}
public IDisposable BeginScope<TState>(TState state) => _logger.BeginScope(state); public IDisposable BeginScope<TState>(TState state) => _logger.BeginScope(state);
public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel); public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
{ {
void ConnectionReadFin(string connectionId); void ConnectionReadFin(string connectionId);
void ConnectionWriteFin(string connectionId); void ConnectionWriteFin(string connectionId, string reason);
void ConnectionError(string connectionId, Exception ex); void ConnectionError(string connectionId, Exception ex);
@ -19,7 +19,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
void ConnectionPause(string connectionId); void ConnectionPause(string connectionId);
void ConnectionResume(string connectionId); void ConnectionResume(string connectionId);
void ConnectionAborted(string connectionId, string message);
} }
} }

View File

@ -92,8 +92,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
public override void Abort(ConnectionAbortedException abortReason) public override void Abort(ConnectionAbortedException abortReason)
{ {
_trace.ConnectionAborted(ConnectionId, abortReason?.Message);
// Try to gracefully close the socket to match libuv behavior. // Try to gracefully close the socket to match libuv behavior.
Shutdown(abortReason); Shutdown(abortReason);
@ -290,7 +288,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
// to half close the connection which is currently unsupported. // to half close the connection which is currently unsupported.
_shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Socket transport's send loop completed gracefully."); _shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Socket transport's send loop completed gracefully.");
_trace.ConnectionWriteFin(ConnectionId); _trace.ConnectionWriteFin(ConnectionId, _shutdownReason.Message);
try try
{ {

View File

@ -19,8 +19,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
private static readonly Action<ILogger, string, Exception> _connectionReadFin = private static readonly Action<ILogger, string, Exception> _connectionReadFin =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, nameof(ConnectionReadFin)), @"Connection id ""{ConnectionId}"" received FIN."); LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, nameof(ConnectionReadFin)), @"Connection id ""{ConnectionId}"" received FIN.");
private static readonly Action<ILogger, string, Exception> _connectionWriteFin = private static readonly Action<ILogger, string, string, Exception> _connectionWriteFin =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN."); LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN because: ""{Reason}""");
// ConnectionWrite: Reserved: 11 // ConnectionWrite: Reserved: 11
@ -32,9 +32,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
private static readonly Action<ILogger, string, Exception> _connectionReset = private static readonly Action<ILogger, string, Exception> _connectionReset =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(19, nameof(ConnectionReset)), @"Connection id ""{ConnectionId}"" reset."); LoggerMessage.Define<string>(LogLevel.Debug, new EventId(19, nameof(ConnectionReset)), @"Connection id ""{ConnectionId}"" reset.");
private static readonly Action<ILogger, string, string, Exception> _connectionAborted =
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(20, nameof(ConnectionAborted)), @"Connection id ""{ConnectionId}"" closing because: ""{Message}""");
private readonly ILogger _logger; private readonly ILogger _logger;
public SocketsTrace(ILogger logger) public SocketsTrace(ILogger logger)
@ -53,9 +50,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
_connectionReadFin(_logger, connectionId, null); _connectionReadFin(_logger, connectionId, null);
} }
public void ConnectionWriteFin(string connectionId) public void ConnectionWriteFin(string connectionId, string reason)
{ {
_connectionWriteFin(_logger, connectionId, null); _connectionWriteFin(_logger, connectionId, reason, null);
} }
public void ConnectionWrite(string connectionId, int count) public void ConnectionWrite(string connectionId, int count)
@ -90,11 +87,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal
_connectionResume(_logger, connectionId, null); _connectionResume(_logger, connectionId, null);
} }
public void ConnectionAborted(string connectionId, string message)
{
_connectionAborted(_logger, connectionId, message, null);
}
public IDisposable BeginScope<TState>(TState state) => _logger.BeginScope(state); public IDisposable BeginScope<TState>(TState state) => _logger.BeginScope(state);
public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel); public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);

View File

@ -5,6 +5,7 @@ using System.IO.Pipelines;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Moq; using Moq;
@ -29,10 +30,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var httpConnection = new HttpConnection(httpConnectionContext); var httpConnection = new HttpConnection(httpConnectionContext);
var aborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); var aborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var http1Connection = new Http1Connection(httpConnectionContext);
httpConnection.Initialize(httpConnectionContext.Transport); httpConnection.Initialize(http1Connection);
httpConnection.Http1Connection.Reset(); http1Connection.Reset();
httpConnection.Http1Connection.RequestAborted.Register(() => http1Connection.RequestAborted.Register(() =>
{ {
aborted.SetResult(null); aborted.SetResult(null);
}); });

View File

@ -13,11 +13,9 @@ using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
@ -2971,34 +2969,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1)); expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1));
} }
[Fact]
public async Task GOAWAY_Received_ConnectionClosedWhenResponseNotDrainedAtMinimumDataRate()
{
var mockSystemClock = new MockSystemClock();
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
var timeoutControl = _connectionContext.TimeoutControl;
_timeoutControl.Initialize(mockSystemClock.UtcNow);
await InitializeConnectionAsync(_noopApplication);
await SendGoAwayAsync();
await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
mockSystemClock.UtcNow +=
Heartbeat.Interval +
TimeSpan.FromSeconds(limits.MaxResponseBufferSize.Value * 2 / limits.MinResponseDataRate.BytesPerSecond);
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once);
}
[Fact] [Fact]
public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError() public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError()
{ {

View File

@ -56,8 +56,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
protected readonly HPackDecoder _hpackDecoder; protected readonly HPackDecoder _hpackDecoder;
private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize]; private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize];
protected readonly Mock<ITimeoutHandler> _mockTimeoutHandler = new Mock<ITimeoutHandler>();
protected readonly TimeoutControl _timeoutControl; protected readonly TimeoutControl _timeoutControl;
protected readonly Mock<ConnectionContext> _mockConnectionContext = new Mock<ConnectionContext>();
protected readonly Mock<ITimeoutHandler> _mockTimeoutHandler = new Mock<ITimeoutHandler>();
protected readonly Mock<MockTimeoutControlBase> _mockTimeoutControl;
protected readonly ConcurrentDictionary<int, TaskCompletionSource<object>> _runningStreams = new ConcurrentDictionary<int, TaskCompletionSource<object>>(); protected readonly ConcurrentDictionary<int, TaskCompletionSource<object>> _runningStreams = new ConcurrentDictionary<int, TaskCompletionSource<object>>();
protected readonly Dictionary<string, string> _receivedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); protected readonly Dictionary<string, string> _receivedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@ -105,7 +107,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions); _pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
_hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize); _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize);
_timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object); _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object);
_mockTimeoutControl = new Mock<MockTimeoutControlBase>(_timeoutControl) { CallBase = true };
_noopApplication = context => Task.CompletedTask; _noopApplication = context => Task.CompletedTask;
@ -289,15 +293,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_connectionContext = new HttpConnectionContext _connectionContext = new HttpConnectionContext
{ {
ConnectionContext = Mock.Of<ConnectionContext>(), ConnectionContext = _mockConnectionContext.Object,
ConnectionFeatures = new FeatureCollection(), ConnectionFeatures = new FeatureCollection(),
ServiceContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object), ServiceContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object),
MemoryPool = _memoryPool, MemoryPool = _memoryPool,
Transport = _pair.Transport, Transport = _pair.Transport,
TimeoutControl = _timeoutControl TimeoutControl = _mockTimeoutControl.Object
}; };
_connection = new Http2Connection(_connectionContext); _connection = new Http2Connection(_connectionContext);
var httpConnection = new HttpConnection(_connectionContext);
httpConnection.Initialize(_connection);
_mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny<TimeoutReason>()))
.Callback<TimeoutReason>(r => httpConnection.OnTimeout(r));
} }
public override void Dispose() public override void Dispose()
@ -1065,5 +1074,69 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
public ReadOnlySequence<byte> PayloadSequence => new ReadOnlySequence<byte>(Payload); public ReadOnlySequence<byte> PayloadSequence => new ReadOnlySequence<byte>(Payload);
} }
public class MockTimeoutControlBase : ITimeoutControl
{
private readonly ITimeoutControl _realTimeoutControl;
public MockTimeoutControlBase(ITimeoutControl realTimeoutControl)
{
_realTimeoutControl = realTimeoutControl;
}
public virtual TimeoutReason TimerReason => _realTimeoutControl.TimerReason;
public virtual void SetTimeout(long ticks, TimeoutReason timeoutReason)
{
_realTimeoutControl.SetTimeout(ticks, timeoutReason);
}
public virtual void ResetTimeout(long ticks, TimeoutReason timeoutReason)
{
_realTimeoutControl.ResetTimeout(ticks, timeoutReason);
}
public virtual void CancelTimeout()
{
_realTimeoutControl.CancelTimeout();
}
public virtual void StartTimingReads(MinDataRate minRate)
{
_realTimeoutControl.StartTimingReads(minRate);
}
public virtual void PauseTimingReads()
{
_realTimeoutControl.PauseTimingReads();
}
public virtual void ResumeTimingReads()
{
_realTimeoutControl.ResumeTimingReads();
}
public virtual void StopTimingReads()
{
_realTimeoutControl.StopTimingReads();
}
public virtual void BytesRead(long count)
{
_realTimeoutControl.BytesRead(count);
}
public virtual void StartTimingWrite(MinDataRate minRate, long size)
{
_realTimeoutControl.StartTimingWrite(minRate, size);
}
public virtual void StopTimingWrite()
{
_realTimeoutControl.StopTimingWrite();
}
}
} }
} }

View File

@ -0,0 +1,170 @@
// 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;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
public class Http2TimeoutTests : Http2TestBase
{
[Fact]
public async Task HEADERS_NotReceivedInitially_WithinKeepAliveTimeout_ClosesConnection()
{
var mockSystemClock = new MockSystemClock();
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
_timeoutControl.Initialize(mockSystemClock.UtcNow);
await InitializeConnectionAsync(_noopApplication);
mockSystemClock.UtcNow += limits.KeepAliveTimeout + Heartbeat.Interval;
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once);
await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_ClosesConnection()
{
var mockSystemClock = new MockSystemClock();
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
_timeoutControl.Initialize(mockSystemClock.UtcNow);
await InitializeConnectionAsync(_noopApplication);
mockSystemClock.UtcNow += limits.KeepAliveTimeout + Heartbeat.Interval;
_timeoutControl.Tick(mockSystemClock.UtcNow);
// keep-alive timeout set but not fired.
_mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny<long>(), TimeoutReason.KeepAlive), Times.Once);
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
// The KeepAlive timeout is set when the stream completes processing on a background thread, so we need to hook the
// keep-alive set afterwards to make a reliable test.
var setTimeoutTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_mockTimeoutControl.Setup(c => c.SetTimeout(It.IsAny<long>(), TimeoutReason.KeepAlive)).Callback<long, TimeoutReason>((t, r) =>
{
_timeoutControl.SetTimeout(t, r);
setTimeoutTcs.SetResult(null);
});
// Send continuation frame to verify intermediate request header timeout doesn't interfere with keep-alive timeout.
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS);
await SendDataAsync(1, new Memory<byte>(), endStream: true);
_mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny<long>(), TimeoutReason.RequestHeaders), Times.Once);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await setTimeoutTcs.Task.DefaultTimeout();
mockSystemClock.UtcNow += limits.KeepAliveTimeout + Heartbeat.Interval;
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once);
await WaitForConnectionStopAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_ReceivedWithoutAllCONTINUATIONs_WithinRequestHeadersTimeout_AbortsConnection()
{
var mockSystemClock = new MockSystemClock();
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
_mockConnectionContext.Setup(c => c.Abort(It.IsAny<ConnectionAbortedException>())).Callback<ConnectionAbortedException>(ex =>
{
// Emulate transport abort so the _connectionTask completes.
_pair.Application.Output.Complete(ex);
});
_timeoutControl.Initialize(mockSystemClock.UtcNow);
await InitializeConnectionAsync(_noopApplication);
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.NONE);
mockSystemClock.UtcNow += limits.RequestHeadersTimeout + Heartbeat.Interval;
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.NONE);
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.RequestHeaders), Times.Once);
await WaitForConnectionErrorAsync<BadHttpRequestException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 1,
Http2ErrorCode.INTERNAL_ERROR,
CoreStrings.BadRequest_RequestHeadersTimeout);
_mockConnectionContext.Verify(c =>c.Abort(It.Is<ConnectionAbortedException>(e =>
e.Message == CoreStrings.BadRequest_RequestHeadersTimeout)), Times.Once);
}
[Fact]
public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection()
{
var mockSystemClock = new MockSystemClock();
var limits = _connectionContext.ServiceContext.ServerOptions.Limits;
_timeoutControl.Initialize(mockSystemClock.UtcNow);
await InitializeConnectionAsync(_noopApplication);
await SendGoAwayAsync();
await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
mockSystemClock.UtcNow +=
TimeSpan.FromSeconds(limits.MaxResponseBufferSize.Value * 2 / limits.MinResponseDataRate.BytesPerSecond) +
Heartbeat.Interval;
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny<TimeoutReason>()), Times.Never);
_mockConnectionContext.Verify(c => c.Abort(It.IsAny<ConnectionAbortedException>()), Times.Never);
mockSystemClock.UtcNow += TimeSpan.FromTicks(1);
_timeoutControl.Tick(mockSystemClock.UtcNow);
_mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once);
_mockConnectionContext.Verify(c =>c.Abort(It.Is<ConnectionAbortedException>(e =>
e.Message == CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied)), Times.Once);
}
}
}

View File

@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await RegisterIPEndPoint_Success(endpoint, testUrl, 443); await RegisterIPEndPoint_Success(endpoint, testUrl, 443);
} }
[ConditionalTheory(Skip="https://github.com/aspnet/KestrelHttpServer/issues/2434")] [ConditionalTheory]
[MemberData(nameof(AddressRegistrationDataIPv6))] [MemberData(nameof(AddressRegistrationDataIPv6))]
[IPv6SupportedCondition] [IPv6SupportedCondition]
public async Task RegisterAddresses_IPv6_Success(string addressInput, string[] testUrls) public async Task RegisterAddresses_IPv6_Success(string addressInput, string[] testUrls)

View File

@ -456,95 +456,98 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact] [Fact]
public async Task ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate() public async Task ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate()
{ {
using (StartLog(out var loggerFactory, "ConnClosedWhenRespDoesNotSatisfyMin")) var logger = LoggerFactory.CreateLogger($"{ typeof(ResponseTests).FullName}.{ nameof(ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate)}");
const int chunkSize = 1024;
const int chunks = 256 * 1024;
var responseSize = chunks * chunkSize;
var chunkData = new byte[chunkSize];
var responseRateTimeoutMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var connectionStopMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestAborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var appFuncCompleted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var mockKestrelTrace = new Mock<IKestrelTrace>();
mockKestrelTrace
.Setup(trace => trace.ResponseMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()))
.Callback(() => responseRateTimeoutMessageLogged.SetResult(null));
mockKestrelTrace
.Setup(trace => trace.ConnectionStop(It.IsAny<string>()))
.Callback(() => connectionStopMessageLogged.SetResult(null));
var testContext = new TestServiceContext(LoggerFactory, mockKestrelTrace.Object)
{ {
var logger = loggerFactory.CreateLogger($"{ typeof(ResponseTests).FullName}.{ nameof(ConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate)}"); ServerOptions =
const int chunkSize = 1024;
const int chunks = 256 * 1024;
var responseSize = chunks * chunkSize;
var chunkData = new byte[chunkSize];
var responseRateTimeoutMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var connectionStopMessageLogged = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var requestAborted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var appFuncCompleted = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var mockKestrelTrace = new Mock<IKestrelTrace>();
mockKestrelTrace
.Setup(trace => trace.ResponseMinimumDataRateNotSatisfied(It.IsAny<string>(), It.IsAny<string>()))
.Callback(() => responseRateTimeoutMessageLogged.SetResult(null));
mockKestrelTrace
.Setup(trace => trace.ConnectionStop(It.IsAny<string>()))
.Callback(() => connectionStopMessageLogged.SetResult(null));
var testContext = new TestServiceContext(loggerFactory, mockKestrelTrace.Object)
{ {
ServerOptions = Limits =
{ {
Limits = MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2))
{
MinResponseDataRate = new MinDataRate(bytesPerSecond: 1024 * 1024, gracePeriod: TimeSpan.FromSeconds(2))
}
}
};
testContext.InitializeHeartbeat();
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(loggerFactory.CreateLogger<LoggingConnectionAdapter>()));
var appLogger = loggerFactory.CreateLogger("App");
async Task App(HttpContext context)
{
appLogger.LogInformation("Request received");
context.RequestAborted.Register(() => requestAborted.SetResult(null));
context.Response.ContentLength = responseSize;
try
{
for (var i = 0; i < chunks; i++)
{
await context.Response.Body.WriteAsync(chunkData, 0, chunkData.Length, context.RequestAborted);
appLogger.LogInformation("Wrote chunk of {chunkSize} bytes", chunkSize);
}
}
catch (OperationCanceledException)
{
appFuncCompleted.SetResult(null);
throw;
}
finally
{
await requestAborted.Task.DefaultTimeout();
} }
} }
};
using (var server = new TestServer(App, testContext, listenOptions)) testContext.InitializeHeartbeat();
var appLogger = LoggerFactory.CreateLogger("App");
async Task App(HttpContext context)
{
appLogger.LogInformation("Request received");
context.RequestAborted.Register(() => requestAborted.SetResult(null));
context.Response.ContentLength = responseSize;
var i = 0;
try
{ {
using (var connection = server.CreateConnection()) for (; i < chunks; i++)
{ {
logger.LogInformation("Sending request"); await context.Response.Body.WriteAsync(chunkData, 0, chunkData.Length, context.RequestAborted);
await connection.Send( await Task.Yield();
"GET / HTTP/1.1",
"Host:",
"",
"");
logger.LogInformation("Sent request");
var sw = Stopwatch.StartNew();
logger.LogInformation("Waiting for connection to abort.");
await requestAborted.Task.DefaultTimeout();
await responseRateTimeoutMessageLogged.Task.DefaultTimeout();
await connectionStopMessageLogged.Task.DefaultTimeout();
await appFuncCompleted.Task.DefaultTimeout();
await AssertStreamAborted(connection.Stream, chunkSize * chunks);
sw.Stop();
logger.LogInformation("Connection was aborted after {totalMilliseconds}ms.", sw.ElapsedMilliseconds);
} }
appFuncCompleted.SetException(new Exception("This shouldn't be reached."));
}
catch (OperationCanceledException)
{
appFuncCompleted.SetResult(null);
throw;
}
catch (Exception ex)
{
appFuncCompleted.SetException(ex);
}
finally
{
appLogger.LogInformation("Wrote {total} bytes", chunkSize * i);
await requestAborted.Task.DefaultTimeout();
}
}
using (var server = new TestServer(App, testContext))
{
using (var connection = server.CreateConnection())
{
logger.LogInformation("Sending request");
await connection.Send(
"GET / HTTP/1.1",
"Host:",
"",
"");
logger.LogInformation("Sent request");
var sw = Stopwatch.StartNew();
logger.LogInformation("Waiting for connection to abort.");
await requestAborted.Task.DefaultTimeout();
await responseRateTimeoutMessageLogged.Task.DefaultTimeout();
await connectionStopMessageLogged.Task.DefaultTimeout();
await appFuncCompleted.Task.DefaultTimeout();
await AssertStreamAborted(connection.Stream, chunkSize * chunks);
sw.Stop();
logger.LogInformation("Connection was aborted after {totalMilliseconds}ms.", sw.ElapsedMilliseconds);
} }
} }
} }