Make timeout logic transport agnostic (#1649)
* Make timeout logic transport agnostic * PR feedback * More PR feedback
This commit is contained in:
parent
db159190bd
commit
11ab602b2f
|
|
@ -1,6 +1,7 @@
|
|||
// 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.Threading;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
|
|
@ -11,6 +12,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
public class ConnectionHandler<TContext> : IConnectionHandler
|
||||
{
|
||||
private static long _lastFrameConnectionId = long.MinValue;
|
||||
|
||||
private readonly ListenOptions _listenOptions;
|
||||
private readonly ServiceContext _serviceContext;
|
||||
private readonly IHttpApplication<TContext> _application;
|
||||
|
|
@ -28,6 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
var outputPipe = connectionInfo.PipeFactory.Create(GetOutputPipeOptions(connectionInfo.OutputReaderScheduler));
|
||||
|
||||
var connectionId = CorrelationIdGenerator.GetNextId();
|
||||
var frameConnectionId = Interlocked.Increment(ref _lastFrameConnectionId);
|
||||
|
||||
var frameContext = new FrameContext
|
||||
{
|
||||
|
|
@ -44,6 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
var connection = new FrameConnection(new FrameConnectionContext
|
||||
{
|
||||
ConnectionId = connectionId,
|
||||
FrameConnectionId = frameConnectionId,
|
||||
ServiceContext = _serviceContext,
|
||||
PipeFactory = connectionInfo.PipeFactory,
|
||||
ConnectionAdapters = _listenOptions.ConnectionAdapters,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
||||
|
|
@ -16,13 +18,17 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||
{
|
||||
public class FrameConnection : IConnectionContext
|
||||
public class FrameConnection : IConnectionContext, ITimeoutControl
|
||||
{
|
||||
private readonly FrameConnectionContext _context;
|
||||
private readonly Frame _frame;
|
||||
private readonly List<IConnectionAdapter> _connectionAdapters;
|
||||
private readonly TaskCompletionSource<object> _frameStartedTcs = new TaskCompletionSource<object>();
|
||||
|
||||
private long _lastTimestamp;
|
||||
private long _timeoutTimestamp = long.MaxValue;
|
||||
private TimeoutAction _timeoutAction;
|
||||
|
||||
private AdaptedPipeline _adaptedPipeline;
|
||||
private Stream _filteredStream;
|
||||
private Task _adaptedPipelineTask = TaskCache.CompletedTask;
|
||||
|
|
@ -32,6 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
_context = context;
|
||||
_frame = context.Frame;
|
||||
_connectionAdapters = context.ConnectionAdapters;
|
||||
context.ServiceContext.ConnectionManager.AddConnection(context.FrameConnectionId, this);
|
||||
}
|
||||
|
||||
public string ConnectionId => _context.ConnectionId;
|
||||
|
|
@ -55,11 +62,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
{
|
||||
_frame.Input = _context.Input.Reader;
|
||||
_frame.Output = _context.OutputProducer;
|
||||
_frame.TimeoutControl = this;
|
||||
|
||||
if (_connectionAdapters.Count == 0)
|
||||
{
|
||||
_frame.Start();
|
||||
_frameStartedTcs.SetResult(null);
|
||||
StartFrame();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -75,6 +82,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
|
||||
public void OnConnectionClosed()
|
||||
{
|
||||
_context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId);
|
||||
Log.ConnectionStop(ConnectionId);
|
||||
KestrelEventSource.Log.ConnectionStop(this);
|
||||
}
|
||||
|
|
@ -127,8 +135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
}
|
||||
|
||||
_frame.AdaptedConnections = adaptedConnections;
|
||||
_frame.Start();
|
||||
_frameStartedTcs.SetResult(null);
|
||||
StartFrame();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -161,5 +168,58 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
_context.OutputProducer.Dispose();
|
||||
_context.Input.Reader.Complete();
|
||||
}
|
||||
|
||||
private void StartFrame()
|
||||
{
|
||||
_lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks;
|
||||
_frame.Start();
|
||||
_frameStartedTcs.SetResult(null);
|
||||
}
|
||||
|
||||
public void Tick(DateTimeOffset now)
|
||||
{
|
||||
var timestamp = now.Ticks;
|
||||
|
||||
// TODO: Use PlatformApis.VolatileRead equivalent again
|
||||
if (timestamp > Interlocked.Read(ref _timeoutTimestamp))
|
||||
{
|
||||
CancelTimeout();
|
||||
|
||||
if (_timeoutAction == TimeoutAction.SendTimeoutResponse)
|
||||
{
|
||||
Timeout();
|
||||
}
|
||||
|
||||
var ignore = StopAsync();
|
||||
}
|
||||
|
||||
Interlocked.Exchange(ref _lastTimestamp, timestamp);
|
||||
}
|
||||
|
||||
public void SetTimeout(long ticks, TimeoutAction timeoutAction)
|
||||
{
|
||||
Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported");
|
||||
|
||||
AssignTimeout(ticks, timeoutAction);
|
||||
}
|
||||
|
||||
public void ResetTimeout(long ticks, TimeoutAction timeoutAction)
|
||||
|
||||
{
|
||||
AssignTimeout(ticks, timeoutAction);
|
||||
}
|
||||
|
||||
public void CancelTimeout()
|
||||
{
|
||||
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
|
||||
}
|
||||
|
||||
private void AssignTimeout(long ticks, TimeoutAction timeoutAction)
|
||||
{
|
||||
_timeoutAction = timeoutAction;
|
||||
|
||||
// Add Heartbeat.Interval since this can be called right before the next heartbeat.
|
||||
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + ticks + Heartbeat.Interval.Ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
public class FrameConnectionContext
|
||||
{
|
||||
public string ConnectionId { get; set; }
|
||||
public long FrameConnectionId { get; set; }
|
||||
public ServiceContext ServiceContext { get; set; }
|
||||
public PipeFactory PipeFactory { get; set; }
|
||||
public List<IConnectionAdapter> ConnectionAdapters { get; set; }
|
||||
|
|
|
|||
|
|
@ -12,23 +12,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
/// <summary>
|
||||
/// Manages the generation of the date header value.
|
||||
/// </summary>
|
||||
public class DateHeaderValueManager : IDisposable
|
||||
public class DateHeaderValueManager : IHeartbeatHandler
|
||||
{
|
||||
private static readonly byte[] _datePreambleBytes = Encoding.ASCII.GetBytes("\r\nDate: ");
|
||||
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly TimeSpan _timeWithoutRequestsUntilIdle;
|
||||
private readonly TimeSpan _timerInterval;
|
||||
private readonly object _timerLocker = new object();
|
||||
|
||||
private DateHeaderValues _dateValues;
|
||||
|
||||
private volatile bool _isDisposed = false;
|
||||
private volatile bool _hadRequestsSinceLastTimerTick = false;
|
||||
private Timer _dateValueTimer;
|
||||
private long _lastRequestSeenTicks;
|
||||
private volatile bool _timerIsRunning;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateHeaderValueManager"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -39,28 +28,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
// Internal for testing
|
||||
internal DateHeaderValueManager(ISystemClock systemClock)
|
||||
: this(
|
||||
systemClock: systemClock,
|
||||
timeWithoutRequestsUntilIdle: TimeSpan.FromSeconds(10),
|
||||
timerInterval: TimeSpan.FromSeconds(1))
|
||||
{
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal DateHeaderValueManager(
|
||||
ISystemClock systemClock,
|
||||
TimeSpan timeWithoutRequestsUntilIdle,
|
||||
TimeSpan timerInterval)
|
||||
{
|
||||
if (systemClock == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(systemClock));
|
||||
}
|
||||
|
||||
_systemClock = systemClock;
|
||||
_timeWithoutRequestsUntilIdle = timeWithoutRequestsUntilIdle;
|
||||
_timerInterval = timerInterval;
|
||||
_dateValueTimer = new Timer(TimerLoop, state: null, dueTime: Timeout.Infinite, period: Timeout.Infinite);
|
||||
SetDateValues(systemClock.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -68,102 +37,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
/// in accordance with http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18
|
||||
/// </summary>
|
||||
/// <returns>The value in string and byte[] format.</returns>
|
||||
public DateHeaderValues GetDateHeaderValues()
|
||||
{
|
||||
_hadRequestsSinceLastTimerTick = !_isDisposed;
|
||||
|
||||
if (!_timerIsRunning)
|
||||
{
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
return _dateValues;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases all resources used by the current instance of <see cref="DateHeaderValueManager"/>.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
_isDisposed = true;
|
||||
_hadRequestsSinceLastTimerTick = false;
|
||||
|
||||
lock (_timerLocker)
|
||||
{
|
||||
if (_dateValueTimer != null)
|
||||
{
|
||||
_timerIsRunning = false;
|
||||
_dateValueTimer.Dispose();
|
||||
_dateValueTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the timer
|
||||
/// </summary>
|
||||
private void StartTimer()
|
||||
{
|
||||
var now = _systemClock.UtcNow;
|
||||
SetDateValues(now);
|
||||
|
||||
if (!_isDisposed)
|
||||
{
|
||||
lock (_timerLocker)
|
||||
{
|
||||
if (!_timerIsRunning && _dateValueTimer != null)
|
||||
{
|
||||
_timerIsRunning = true;
|
||||
_dateValueTimer.Change(_timerInterval, _timerInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the timer
|
||||
/// </summary>
|
||||
private void StopTimer()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
lock (_timerLocker)
|
||||
{
|
||||
if (_dateValueTimer != null)
|
||||
{
|
||||
_timerIsRunning = false;
|
||||
_dateValueTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_hadRequestsSinceLastTimerTick = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public DateHeaderValues GetDateHeaderValues() => _dateValues;
|
||||
|
||||
// Called by the Timer (background) thread
|
||||
private void TimerLoop(object state)
|
||||
public void OnHeartbeat(DateTimeOffset now)
|
||||
{
|
||||
var now = _systemClock.UtcNow;
|
||||
|
||||
SetDateValues(now);
|
||||
|
||||
if (_hadRequestsSinceLastTimerTick)
|
||||
{
|
||||
// We served requests since the last tick, reset the flag and return as we're still active
|
||||
_hadRequestsSinceLastTimerTick = false;
|
||||
Interlocked.Exchange(ref _lastRequestSeenTicks, now.Ticks);
|
||||
return;
|
||||
}
|
||||
|
||||
// No requests since the last timer tick, we need to check if we're beyond the idle threshold
|
||||
// TODO: Use PlatformApis.VolatileRead equivalent again
|
||||
if ((now.Ticks - Interlocked.Read(ref _lastRequestSeenTicks)) >= _timeWithoutRequestsUntilIdle.Ticks)
|
||||
{
|
||||
// No requests since idle threshold so stop the timer if it's still running
|
||||
StopTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -74,8 +74,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
private int _remainingRequestHeadersBytesAllowed;
|
||||
private int _requestHeadersParsed;
|
||||
|
||||
protected readonly long _keepAliveMilliseconds;
|
||||
private readonly long _requestHeadersTimeoutMilliseconds;
|
||||
protected readonly long _keepAliveTicks;
|
||||
private readonly long _requestHeadersTimeoutTicks;
|
||||
|
||||
protected long _responseBytesWritten;
|
||||
|
||||
|
|
@ -91,8 +91,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
_parser = ServiceContext.HttpParserFactory(new FrameAdapter(this));
|
||||
|
||||
FrameControl = this;
|
||||
_keepAliveMilliseconds = (long)ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
|
||||
_requestHeadersTimeoutMilliseconds = (long)ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
|
||||
_keepAliveTicks = ServerOptions.Limits.KeepAliveTimeout.Ticks;
|
||||
_requestHeadersTimeoutTicks = ServerOptions.Limits.RequestHeadersTimeout.Ticks;
|
||||
}
|
||||
|
||||
public ServiceContext ServiceContext => _frameContext.ServiceContext;
|
||||
|
|
@ -102,10 +102,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
public ISocketOutput Output { get; set; }
|
||||
public IEnumerable<IAdaptedConnection> AdaptedConnections { get; set; }
|
||||
public ConnectionLifetimeControl LifetimeControl { get; set; }
|
||||
public ITimeoutControl TimeoutControl { get; set; }
|
||||
|
||||
protected ITimeoutControl TimeoutControl => ConnectionInformation.TimeoutControl;
|
||||
protected IKestrelTrace Log => ServiceContext.Log;
|
||||
|
||||
private DateHeaderValueManager DateHeaderValueManager => ServiceContext.DateHeaderValueManager;
|
||||
// Hold direct reference to ServerOptions since this is used very often in the request processing path
|
||||
private KestrelServerOptions ServerOptions { get; }
|
||||
|
|
@ -1017,7 +1016,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
break;
|
||||
}
|
||||
|
||||
TimeoutControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
|
||||
TimeoutControl.ResetTimeout(_requestHeadersTimeoutTicks, TimeoutAction.SendTimeoutResponse);
|
||||
|
||||
_requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
|
||||
goto case RequestProcessingStatus.ParsingRequestLine;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
{
|
||||
while (!_requestProcessingStopping)
|
||||
{
|
||||
TimeoutControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection);
|
||||
TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.CloseConnection);
|
||||
|
||||
InitializeHeaders();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
// 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.Collections.Concurrent;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||
{
|
||||
public class FrameConnectionManager : IHeartbeatHandler
|
||||
{
|
||||
private readonly ConcurrentDictionary<long, FrameConnection> _connections
|
||||
= new ConcurrentDictionary<long, FrameConnection>();
|
||||
|
||||
public void AddConnection(long id, FrameConnection connection)
|
||||
{
|
||||
if (!_connections.TryAdd(id, connection))
|
||||
{
|
||||
throw new ArgumentException(nameof(id));
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveConnection(long id)
|
||||
{
|
||||
if (!_connections.TryRemove(id, out _))
|
||||
{
|
||||
throw new ArgumentException(nameof(id));
|
||||
}
|
||||
}
|
||||
|
||||
public void OnHeartbeat(DateTimeOffset now)
|
||||
{
|
||||
foreach (var kvp in _connections)
|
||||
{
|
||||
kvp.Value.Tick(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// 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;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||
{
|
||||
public class Heartbeat : IDisposable
|
||||
{
|
||||
public static readonly TimeSpan Interval = TimeSpan.FromSeconds(1);
|
||||
|
||||
private readonly IHeartbeatHandler[] _callbacks;
|
||||
private readonly TimeSpan _interval;
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly IKestrelTrace _trace;
|
||||
private readonly Timer _timer;
|
||||
private int _executingOnHeartbeat;
|
||||
|
||||
public Heartbeat(IHeartbeatHandler[] callbacks, ISystemClock systemClock, IKestrelTrace trace)
|
||||
: this(callbacks, systemClock, trace, Interval)
|
||||
{
|
||||
}
|
||||
|
||||
// For testing
|
||||
internal Heartbeat(IHeartbeatHandler[] callbacks, ISystemClock systemClock, IKestrelTrace trace, TimeSpan interval)
|
||||
{
|
||||
_callbacks = callbacks;
|
||||
_interval = interval;
|
||||
_systemClock = systemClock;
|
||||
_trace = trace;
|
||||
_timer = new Timer(OnHeartbeat, state: this, dueTime: _interval, period: _interval);
|
||||
}
|
||||
|
||||
// Called by the Timer (background) thread
|
||||
private void OnHeartbeat(object state)
|
||||
{
|
||||
var now = _systemClock.UtcNow;
|
||||
|
||||
if (Interlocked.Exchange(ref _executingOnHeartbeat, 1) == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var callback in _callbacks)
|
||||
{
|
||||
callback.OnHeartbeat(now);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_trace.LogError(0, ex, $"{nameof(Heartbeat)}.{nameof(OnHeartbeat)}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _executingOnHeartbeat, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_trace.TimerSlow(_interval, now);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||
{
|
||||
public interface IHeartbeatHandler
|
||||
{
|
||||
void OnHeartbeat(DateTimeOffset now);
|
||||
}
|
||||
}
|
||||
|
|
@ -29,5 +29,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
void ConnectionBadRequest(string connectionId, BadHttpRequestException ex);
|
||||
|
||||
void ApplicationError(string connectionId, string traceIdentifier, Exception ex);
|
||||
|
||||
void TimerSlow(TimeSpan interval, DateTimeOffset now);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
|||
/// <summary>
|
||||
/// Abstracts the system clock to facilitate testing.
|
||||
/// </summary>
|
||||
internal interface ISystemClock
|
||||
public interface ISystemClock
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the current system time in UTC.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||
{
|
||||
public interface ITimeoutControl
|
||||
{
|
||||
void SetTimeout(long milliseconds, TimeoutAction timeoutAction);
|
||||
void ResetTimeout(long milliseconds, TimeoutAction timeoutAction);
|
||||
void SetTimeout(long ticks, TimeoutAction timeoutAction);
|
||||
void ResetTimeout(long ticks, TimeoutAction timeoutAction);
|
||||
void CancelTimeout();
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
private static readonly Action<ILogger, string, Exception> _requestProcessingError =
|
||||
LoggerMessage.Define<string>(LogLevel.Information, 20, @"Connection id ""{ConnectionId}"" request processing ended abnormally.");
|
||||
|
||||
private static readonly Action<ILogger, TimeSpan, DateTimeOffset, Exception> _timerSlow =
|
||||
LoggerMessage.Define<TimeSpan, DateTimeOffset>(LogLevel.Warning, 21, @"Heartbeat took longer than ""{interval}"" at ""{now}"".");
|
||||
|
||||
protected readonly ILogger _logger;
|
||||
|
||||
public KestrelTrace(ILogger logger)
|
||||
|
|
@ -107,6 +110,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
_requestProcessingError(_logger, connectionId, ex);
|
||||
}
|
||||
|
||||
public virtual void TimerSlow(TimeSpan interval, DateTimeOffset now)
|
||||
{
|
||||
_timerSlow(_logger, interval, now, null);
|
||||
}
|
||||
|
||||
public virtual void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
=> _logger.Log(logLevel, eventId, state, exception, formatter);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||
{
|
||||
public enum TimeoutAction
|
||||
{
|
||||
|
|
@ -15,8 +15,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
|||
|
||||
public Func<FrameAdapter, IHttpParser<FrameAdapter>> HttpParserFactory { get; set; }
|
||||
|
||||
public ISystemClock SystemClock { get; set; }
|
||||
|
||||
public DateHeaderValueManager DateHeaderValueManager { get; set; }
|
||||
|
||||
public FrameConnectionManager ConnectionManager { get; set; }
|
||||
|
||||
public KestrelServerOptions ServerOptions { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
private readonly ITransportFactory _transportFactory;
|
||||
|
||||
private bool _isRunning;
|
||||
private DateHeaderValueManager _dateHeaderValueManager;
|
||||
private Heartbeat _heartbeat;
|
||||
|
||||
public KestrelServer(
|
||||
IOptions<KestrelServerOptions> options,
|
||||
|
|
@ -85,9 +85,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
}
|
||||
_isRunning = true;
|
||||
|
||||
_dateHeaderValueManager = new DateHeaderValueManager();
|
||||
var trace = new KestrelTrace(_logger);
|
||||
|
||||
var systemClock = new SystemClock();
|
||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
|
||||
var connectionManager = new FrameConnectionManager();
|
||||
_heartbeat = new Heartbeat(new IHeartbeatHandler[] { dateHeaderValueManager, connectionManager }, systemClock, trace);
|
||||
|
||||
IThreadPool threadPool;
|
||||
if (InternalOptions.ThreadPoolDispatching)
|
||||
{
|
||||
|
|
@ -103,7 +107,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
Log = trace,
|
||||
HttpParserFactory = frameParser => new HttpParser<FrameAdapter>(frameParser.Frame.ServiceContext.Log),
|
||||
ThreadPool = threadPool,
|
||||
DateHeaderValueManager = _dateHeaderValueManager,
|
||||
SystemClock = systemClock,
|
||||
DateHeaderValueManager = dateHeaderValueManager,
|
||||
ConnectionManager = connectionManager,
|
||||
ServerOptions = Options
|
||||
};
|
||||
|
||||
|
|
@ -232,7 +238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
Task.WaitAll(tasks);
|
||||
}
|
||||
|
||||
_dateHeaderValueManager?.Dispose();
|
||||
_heartbeat?.Dispose();
|
||||
}
|
||||
|
||||
private void ValidateOptions()
|
||||
|
|
|
|||
|
|
@ -17,6 +17,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions
|
|||
void OnConnectionClosed();
|
||||
Task StopAsync();
|
||||
void Abort(Exception ex);
|
||||
void Timeout();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions
|
|||
PipeFactory PipeFactory { get; }
|
||||
IScheduler InputWriterScheduler { get; }
|
||||
IScheduler OutputReaderScheduler { get; }
|
||||
|
||||
// TODO: Remove timeout management from transport
|
||||
ITimeoutControl TimeoutControl { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
public class LibuvConnection : LibuvConnectionContext, ITimeoutControl
|
||||
public class LibuvConnection : LibuvConnectionContext
|
||||
{
|
||||
private const int MinAllocBufferSize = 2048;
|
||||
|
||||
|
|
@ -28,16 +28,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
|
||||
private TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>();
|
||||
|
||||
private long _lastTimestamp;
|
||||
private long _timeoutTimestamp = long.MaxValue;
|
||||
private TimeoutAction _timeoutAction;
|
||||
private WritableBuffer? _currentWritableBuffer;
|
||||
|
||||
public LibuvConnection(ListenerContext context, UvStreamHandle socket) : base(context)
|
||||
{
|
||||
_socket = socket;
|
||||
socket.Connection = this;
|
||||
TimeoutControl = this;
|
||||
|
||||
var tcpHandle = _socket as UvTcpHandle;
|
||||
if (tcpHandle != null)
|
||||
|
|
@ -45,8 +41,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
RemoteEndPoint = tcpHandle.GetPeerIPEndPoint();
|
||||
LocalEndPoint = tcpHandle.GetSockIPEndPoint();
|
||||
}
|
||||
|
||||
_lastTimestamp = Thread.Loop.Now();
|
||||
}
|
||||
|
||||
// For testing
|
||||
|
|
@ -74,7 +68,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
|
||||
// Start socket prior to applying the ConnectionAdapter
|
||||
_socket.ReadStart(_allocCallback, _readCallback, this);
|
||||
_lastTimestamp = Thread.Loop.Now();
|
||||
|
||||
// This *must* happen after socket.ReadStart
|
||||
// The socket output consumer is the only thing that can close the connection. If the
|
||||
|
|
@ -110,24 +103,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
_socketClosedTcs.TrySetResult(null);
|
||||
}
|
||||
|
||||
// Called on Libuv thread
|
||||
public void Tick(long timestamp)
|
||||
{
|
||||
if (timestamp > PlatformApis.VolatileRead(ref _timeoutTimestamp))
|
||||
{
|
||||
TimeoutControl.CancelTimeout();
|
||||
|
||||
if (_timeoutAction == TimeoutAction.SendTimeoutResponse)
|
||||
{
|
||||
_connectionContext.Timeout();
|
||||
}
|
||||
|
||||
StopAsync();
|
||||
}
|
||||
|
||||
Interlocked.Exchange(ref _lastTimestamp, timestamp);
|
||||
}
|
||||
|
||||
private static LibuvFunctions.uv_buf_t AllocCallback(UvStreamHandle handle, int suggestedSize, object state)
|
||||
{
|
||||
return ((LibuvConnection)state).OnAlloc(handle, suggestedSize);
|
||||
|
|
@ -251,30 +226,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ITimeoutControl.SetTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported");
|
||||
|
||||
AssignTimeout(milliseconds, timeoutAction);
|
||||
}
|
||||
|
||||
void ITimeoutControl.ResetTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
AssignTimeout(milliseconds, timeoutAction);
|
||||
}
|
||||
|
||||
void ITimeoutControl.CancelTimeout()
|
||||
{
|
||||
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
|
||||
}
|
||||
|
||||
private void AssignTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
_timeoutAction = timeoutAction;
|
||||
|
||||
// Add LibuvThread.HeartbeatMilliseconds extra milliseconds since this can be called right before the next heartbeat.
|
||||
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + milliseconds + LibuvThread.HeartbeatMilliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
public PipeFactory PipeFactory => ListenerContext.Thread.PipeFactory;
|
||||
public IScheduler InputWriterScheduler => ListenerContext.Thread;
|
||||
public IScheduler OutputReaderScheduler => ListenerContext.Thread;
|
||||
|
||||
public ITimeoutControl TimeoutControl { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -16,16 +16,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
{
|
||||
public class LibuvThread : IScheduler
|
||||
{
|
||||
public const long HeartbeatMilliseconds = 1000;
|
||||
|
||||
private static readonly LibuvFunctions.uv_walk_cb _heartbeatWalkCallback = (ptr, arg) =>
|
||||
{
|
||||
var streamHandle = UvMemory.FromIntPtr<UvHandle>(ptr) as UvStreamHandle;
|
||||
var thisHandle = GCHandle.FromIntPtr(arg);
|
||||
var libuvThread = (LibuvThread)thisHandle.Target;
|
||||
streamHandle?.Connection?.Tick(libuvThread.Now);
|
||||
};
|
||||
|
||||
// maximum times the work queues swapped and are processed in a single pass
|
||||
// as completing a task may immediately have write data to put on the network
|
||||
// otherwise it needs to wait till the next pass of the libuv loop
|
||||
|
|
@ -37,7 +27,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
private readonly TaskCompletionSource<object> _threadTcs = new TaskCompletionSource<object>();
|
||||
private readonly UvLoopHandle _loop;
|
||||
private readonly UvAsyncHandle _post;
|
||||
private readonly UvTimerHandle _heartbeatTimer;
|
||||
private Queue<Work> _workAdding = new Queue<Work>(1024);
|
||||
private Queue<Work> _workRunning = new Queue<Work>(1024);
|
||||
private Queue<CloseHandle> _closeHandleAdding = new Queue<CloseHandle>(256);
|
||||
|
|
@ -49,7 +38,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
private ExceptionDispatchInfo _closeError;
|
||||
private readonly ILibuvTrace _log;
|
||||
private readonly TimeSpan _shutdownTimeout;
|
||||
private IntPtr _thisPtr;
|
||||
|
||||
public LibuvThread(LibuvTransport transport)
|
||||
{
|
||||
|
|
@ -61,7 +49,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
_post = new UvAsyncHandle(_log);
|
||||
_thread = new Thread(ThreadStart);
|
||||
_thread.Name = nameof(LibuvThread);
|
||||
_heartbeatTimer = new UvTimerHandle(_log);
|
||||
#if !DEBUG
|
||||
// Mark the thread as being as unimportant to keeping the process alive.
|
||||
// Don't do this for debug builds, so we know if the thread isn't terminating.
|
||||
|
|
@ -170,7 +157,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
|
||||
private void AllowStop()
|
||||
{
|
||||
_heartbeatTimer.Stop();
|
||||
_post.Unreference();
|
||||
}
|
||||
|
||||
|
|
@ -269,8 +255,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
{
|
||||
_loop.Init(_transport.Libuv);
|
||||
_post.Init(_loop, OnPost, EnqueueCloseHandle);
|
||||
_heartbeatTimer.Init(_loop, EnqueueCloseHandle);
|
||||
_heartbeatTimer.Start(OnHeartbeat, timeout: HeartbeatMilliseconds, repeat: HeartbeatMilliseconds);
|
||||
_initCompleted = true;
|
||||
tcs.SetResult(0);
|
||||
}
|
||||
|
|
@ -286,8 +270,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
|
||||
try
|
||||
{
|
||||
_thisPtr = GCHandle.ToIntPtr(thisHandle);
|
||||
|
||||
_loop.Run();
|
||||
if (_stopImmediate)
|
||||
{
|
||||
|
|
@ -298,7 +280,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
// run the loop one more time to delete the open handles
|
||||
_post.Reference();
|
||||
_post.Dispose();
|
||||
_heartbeatTimer.Dispose();
|
||||
|
||||
// Ensure the Dispose operations complete in the event loop.
|
||||
_loop.Run();
|
||||
|
|
@ -333,12 +314,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
|||
} while (wasWork && loopsRemaining > 0);
|
||||
}
|
||||
|
||||
private void OnHeartbeat(UvTimerHandle timer)
|
||||
{
|
||||
Now = Loop.Now();
|
||||
Walk(_heartbeatWalkCallback, _thisPtr);
|
||||
}
|
||||
|
||||
private bool DoPostWork()
|
||||
{
|
||||
Queue<Work> queue;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||
|
|
@ -27,21 +29,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
UtcNow = now
|
||||
};
|
||||
var timeWithoutRequestsUntilIdle = TimeSpan.FromSeconds(1);
|
||||
var timerInterval = TimeSpan.FromSeconds(10);
|
||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock, timeWithoutRequestsUntilIdle, timerInterval);
|
||||
string result;
|
||||
|
||||
try
|
||||
{
|
||||
result = dateHeaderValueManager.GetDateHeaderValues().String;
|
||||
}
|
||||
finally
|
||||
{
|
||||
dateHeaderValueManager.Dispose();
|
||||
}
|
||||
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), result);
|
||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -53,30 +43,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
UtcNow = now
|
||||
};
|
||||
var timeWithoutRequestsUntilIdle = TimeSpan.FromSeconds(1);
|
||||
|
||||
var timerInterval = TimeSpan.FromSeconds(10);
|
||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock, timeWithoutRequestsUntilIdle, timerInterval);
|
||||
string result1;
|
||||
string result2;
|
||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
|
||||
|
||||
try
|
||||
using (new Heartbeat(new IHeartbeatHandler[] { dateHeaderValueManager }, systemClock, null, timerInterval))
|
||||
{
|
||||
result1 = dateHeaderValueManager.GetDateHeaderValues().String;
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||
systemClock.UtcNow = future;
|
||||
result2 = dateHeaderValueManager.GetDateHeaderValues().String;
|
||||
}
|
||||
finally
|
||||
{
|
||||
dateHeaderValueManager.Dispose();
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||
}
|
||||
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), result1);
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), result2);
|
||||
Assert.Equal(1, systemClock.UtcNowCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDateHeaderValue_ReturnsUpdatedValueAfterIdle()
|
||||
public async Task GetDateHeaderValue_ReturnsUpdatedValueAfterHeartbeat()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var future = now.AddSeconds(10);
|
||||
|
|
@ -84,32 +66,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
UtcNow = now
|
||||
};
|
||||
var timeWithoutRequestsUntilIdle = TimeSpan.FromMilliseconds(250);
|
||||
|
||||
var timerInterval = TimeSpan.FromMilliseconds(100);
|
||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock, timeWithoutRequestsUntilIdle, timerInterval);
|
||||
string result1;
|
||||
string result2;
|
||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
|
||||
|
||||
try
|
||||
var heartbeatTcs = new TaskCompletionSource<object>();
|
||||
var mockHeartbeatHandler = new Mock<IHeartbeatHandler>();
|
||||
|
||||
mockHeartbeatHandler.Setup(h => h.OnHeartbeat(future)).Callback(() => heartbeatTcs.TrySetResult(null));
|
||||
|
||||
using (new Heartbeat(new[] { dateHeaderValueManager, mockHeartbeatHandler.Object }, systemClock, null, timerInterval))
|
||||
{
|
||||
result1 = dateHeaderValueManager.GetDateHeaderValues().String;
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||
|
||||
// Wait for the next heartbeat before verifying GetDateHeaderValues picks up new time.
|
||||
systemClock.UtcNow = future;
|
||||
// Wait for longer than the idle timeout to ensure the timer is stopped
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
result2 = dateHeaderValueManager.GetDateHeaderValues().String;
|
||||
}
|
||||
finally
|
||||
{
|
||||
dateHeaderValueManager.Dispose();
|
||||
}
|
||||
await heartbeatTcs.Task;
|
||||
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), result1);
|
||||
Assert.Equal(future.ToString(Rfc1123DateFormat), result2);
|
||||
Assert.True(systemClock.UtcNowCalled >= 2);
|
||||
Assert.Equal(future.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||
Assert.True(systemClock.UtcNowCalled >= 2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDateHeaderValue_ReturnsDateValueAfterDisposed()
|
||||
public void GetDateHeaderValue_ReturnsLastDateValueAfterHeartbeatDisposed()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var future = now.AddSeconds(10);
|
||||
|
|
@ -117,17 +97,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
UtcNow = now
|
||||
};
|
||||
var timeWithoutRequestsUntilIdle = TimeSpan.FromSeconds(1);
|
||||
var timerInterval = TimeSpan.FromSeconds(10);
|
||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock, timeWithoutRequestsUntilIdle, timerInterval);
|
||||
|
||||
var result1 = dateHeaderValueManager.GetDateHeaderValues().String;
|
||||
dateHeaderValueManager.Dispose();
|
||||
var timerInterval = TimeSpan.FromSeconds(10);
|
||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
|
||||
|
||||
using (new Heartbeat(new IHeartbeatHandler[] { dateHeaderValueManager }, systemClock, null, timerInterval))
|
||||
{
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||
}
|
||||
|
||||
systemClock.UtcNow = future;
|
||||
var result2 = dateHeaderValueManager.GetDateHeaderValues().String;
|
||||
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), result1);
|
||||
Assert.Equal(future.ToString(Rfc1123DateFormat), result2);
|
||||
Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
|
|
@ -67,7 +66,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_frame = new TestFrame<object>(application: null, context: _frameContext)
|
||||
{
|
||||
Input = _input.Reader,
|
||||
Output = new MockSocketOutput()
|
||||
Output = new MockSocketOutput(),
|
||||
TimeoutControl = Mock.Of<ITimeoutControl>()
|
||||
};
|
||||
|
||||
_frame.Reset();
|
||||
|
|
@ -320,16 +320,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Fact]
|
||||
public async Task ParseRequestStartsRequestHeadersTimeoutOnFirstByteAvailable()
|
||||
{
|
||||
var connectionInfo = (MockConnectionInformation)_frameContext.ConnectionInformation;
|
||||
var connectionControl = new Mock<ITimeoutControl>();
|
||||
connectionInfo.TimeoutControl = connectionControl.Object;
|
||||
_frame.TimeoutControl = connectionControl.Object;
|
||||
|
||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
|
||||
|
||||
_frame.ParseRequest((await _input.Reader.ReadAsync()).Buffer, out _consumed, out _examined);
|
||||
_input.Reader.Advance(_consumed, _examined);
|
||||
|
||||
var expectedRequestHeadersTimeout = (long)_serviceContext.ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
|
||||
var expectedRequestHeadersTimeout = _serviceContext.ServerOptions.Limits.RequestHeadersTimeout.Ticks;
|
||||
connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
|
||||
}
|
||||
|
||||
|
|
@ -444,13 +443,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Fact]
|
||||
public void RequestProcessingAsyncEnablesKeepAliveTimeout()
|
||||
{
|
||||
var connectionInfo = (MockConnectionInformation)_frameContext.ConnectionInformation;
|
||||
var connectionControl = new Mock<ITimeoutControl>();
|
||||
connectionInfo.TimeoutControl = connectionControl.Object;
|
||||
_frame.TimeoutControl = connectionControl.Object;
|
||||
|
||||
var requestProcessingTask = _frame.RequestProcessingAsync();
|
||||
|
||||
var expectedKeepAliveTimeout = (long)_serviceContext.ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
|
||||
var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.Ticks;
|
||||
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
|
||||
|
||||
_frame.StopAsync();
|
||||
|
|
@ -817,7 +815,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
public PipeFactory PipeFactory { get; }
|
||||
public IScheduler InputWriterScheduler { get; }
|
||||
public IScheduler OutputReaderScheduler { get; }
|
||||
public ITimeoutControl TimeoutControl { get; set; } = Mock.Of<ITimeoutControl>();
|
||||
}
|
||||
|
||||
private class RequestHeadersWrapper : IHeaderDictionary
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
// 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.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||
{
|
||||
public class HeartbeatTests
|
||||
{
|
||||
[Fact]
|
||||
public void BlockedHeartbeatDoesntCauseOverlapsAndIsLoggedAsError()
|
||||
{
|
||||
var systemClock = new MockSystemClock();
|
||||
var heartbeatInterval = TimeSpan.FromMilliseconds(10);
|
||||
var heartbeatHandler = new Mock<IHeartbeatHandler>();
|
||||
var kestrelTrace = new Mock<IKestrelTrace>();
|
||||
var handlerMre = new ManualResetEventSlim();
|
||||
var traceMre = new ManualResetEventSlim();
|
||||
|
||||
heartbeatHandler.Setup(h => h.OnHeartbeat(systemClock.UtcNow)).Callback(() => handlerMre.Wait());
|
||||
kestrelTrace.Setup(t => t.TimerSlow(heartbeatInterval, systemClock.UtcNow)).Callback(() => traceMre.Set());
|
||||
|
||||
using (new Heartbeat(new[] {heartbeatHandler.Object}, systemClock, kestrelTrace.Object, heartbeatInterval))
|
||||
{
|
||||
Assert.True(traceMre.Wait(TimeSpan.FromSeconds(10)));
|
||||
}
|
||||
|
||||
handlerMre.Set();
|
||||
|
||||
heartbeatHandler.Verify(h => h.OnHeartbeat(systemClock.UtcNow), Times.Once());
|
||||
kestrelTrace.Verify(t => t.TimerSlow(heartbeatInterval, systemClock.UtcNow), Times.AtLeastOnce());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionFromHeartbeatHandlerIsLoggedAsError()
|
||||
{
|
||||
var systemClock = new MockSystemClock();
|
||||
var heartbeatHandler = new Mock<IHeartbeatHandler>();
|
||||
var kestrelTrace = new TestKestrelTrace();
|
||||
var ex = new Exception();
|
||||
|
||||
heartbeatHandler.Setup(h => h.OnHeartbeat(systemClock.UtcNow)).Throws(ex);
|
||||
|
||||
using (new Heartbeat(new[] { heartbeatHandler.Object }, systemClock, kestrelTrace))
|
||||
{
|
||||
Assert.True(kestrelTrace.Logger.MessageLoggedTask.Wait(TimeSpan.FromSeconds(10)));
|
||||
}
|
||||
|
||||
Assert.Equal(ex, kestrelTrace.Logger.Messages.Single(message => message.LogLevel == LogLevel.Error).Exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ using Microsoft.Extensions.Internal;
|
|||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||
{
|
||||
class TestInput : ITimeoutControl, IFrameControl, IDisposable
|
||||
class TestInput : IFrameControl, IDisposable
|
||||
{
|
||||
private MemoryPool _memoryPool;
|
||||
private PipeFactory _pipelineFactory;
|
||||
|
|
@ -58,18 +58,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
}
|
||||
|
||||
public void SetTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
}
|
||||
|
||||
public void ResetTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
}
|
||||
|
||||
public void CancelTimeout()
|
||||
{
|
||||
}
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.TestHelpers;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -49,22 +51,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionClosedWhenKeepAliveTimeoutExpires(TestServer server)
|
||||
private async Task ConnectionClosedWhenKeepAliveTimeoutExpires(TimeoutTestServer server)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
"",
|
||||
"");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
await ReceiveResponse(connection);
|
||||
await connection.WaitForConnectionClose().TimeoutAfter(LongDelay);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionKeptAliveBetweenRequests(TestServer server)
|
||||
private async Task ConnectionKeptAliveBetweenRequests(TimeoutTestServer server)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
|
|
@ -77,14 +79,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
await ReceiveResponse(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionNotTimedOutWhileRequestBeingSent(TestServer server)
|
||||
private async Task ConnectionNotTimedOutWhileRequestBeingSent(TimeoutTestServer server)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(LongDelay);
|
||||
|
|
@ -108,13 +110,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
"0",
|
||||
"",
|
||||
"");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
await ReceiveResponse(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionNotTimedOutWhileAppIsRunning(TestServer server, CancellationTokenSource cts)
|
||||
private async Task ConnectionNotTimedOutWhileAppIsRunning(TimeoutTestServer server, CancellationTokenSource cts)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"GET /longrunning HTTP/1.1",
|
||||
|
|
@ -127,28 +129,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
await ReceiveResponse(connection);
|
||||
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
"",
|
||||
"");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
await ReceiveResponse(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionTimesOutWhenOpenedButNoRequestSent(TestServer server)
|
||||
private async Task ConnectionTimesOutWhenOpenedButNoRequestSent(TimeoutTestServer server)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await Task.Delay(LongDelay);
|
||||
await connection.WaitForConnectionClose().TimeoutAfter(LongDelay);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections(TestServer server, CancellationTokenSource cts)
|
||||
private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections(TimeoutTestServer server, CancellationTokenSource cts)
|
||||
{
|
||||
using (var connection = new TestConnection(server.Port))
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"GET /upgrade HTTP/1.1",
|
||||
|
|
@ -157,7 +159,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
await connection.Receive(
|
||||
"HTTP/1.1 101 Switching Protocols",
|
||||
"Connection: Upgrade",
|
||||
$"Date: {server.Context.DateHeaderValue}",
|
||||
"");
|
||||
await connection.ReceiveStartsWith("Date: ");
|
||||
await connection.Receive(
|
||||
"",
|
||||
"");
|
||||
cts.CancelAfter(LongDelay);
|
||||
|
|
@ -171,18 +175,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
private TestServer CreateServer(CancellationToken longRunningCt, CancellationToken upgradeCt)
|
||||
private TimeoutTestServer CreateServer(CancellationToken longRunningCt, CancellationToken upgradeCt)
|
||||
{
|
||||
return new TestServer(httpContext => App(httpContext, longRunningCt, upgradeCt), new TestServiceContext
|
||||
return new TimeoutTestServer(httpContext => App(httpContext, longRunningCt, upgradeCt), new KestrelServerOptions
|
||||
{
|
||||
ServerOptions = new KestrelServerOptions
|
||||
{
|
||||
AddServerHeader = false,
|
||||
Limits =
|
||||
{
|
||||
KeepAliveTimeout = KeepAliveTimeout
|
||||
}
|
||||
}
|
||||
AddServerHeader = false,
|
||||
Limits = { KeepAliveTimeout = KeepAliveTimeout }
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -218,11 +216,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
private async Task ReceiveResponse(TestConnection connection, TestServiceContext testServiceContext)
|
||||
private async Task ReceiveResponse(TestConnection connection)
|
||||
{
|
||||
await connection.Receive(
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {testServiceContext.DateHeaderValue}",
|
||||
"");
|
||||
await connection.ReceiveStartsWith("Date: ");
|
||||
await connection.Receive(
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
"c",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.IO;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.TestHelpers;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -37,18 +38,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionAbortedWhenRequestHeadersNotReceivedInTime(TestServer server, string headers)
|
||||
private async Task ConnectionAbortedWhenRequestHeadersNotReceivedInTime(TimeoutTestServer server, string headers)
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(
|
||||
"GET / HTTP/1.1",
|
||||
headers);
|
||||
await ReceiveTimeoutResponse(connection, server.Context);
|
||||
await ReceiveTimeoutResponse(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RequestHeadersTimeoutCanceledAfterHeadersReceived(TestServer server)
|
||||
private async Task RequestHeadersTimeoutCanceledAfterHeadersReceived(TimeoutTestServer server)
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -60,20 +61,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
await Task.Delay(RequestHeadersTimeout);
|
||||
await connection.Send(
|
||||
"a");
|
||||
await ReceiveResponse(connection, server.Context);
|
||||
await ReceiveResponse(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(TestServer server, string requestLine)
|
||||
private async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(TimeoutTestServer server, string requestLine)
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(requestLine);
|
||||
await ReceiveTimeoutResponse(connection, server.Context);
|
||||
await ReceiveTimeoutResponse(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TimeoutNotResetOnEachRequestLineCharacterReceived(TestServer server)
|
||||
private async Task TimeoutNotResetOnEachRequestLineCharacterReceived(TimeoutTestServer server)
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
|
|
@ -88,31 +89,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
private TestServer CreateServer()
|
||||
private TimeoutTestServer CreateServer()
|
||||
{
|
||||
return new TestServer(async httpContext =>
|
||||
return new TimeoutTestServer(async httpContext =>
|
||||
{
|
||||
await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1);
|
||||
await httpContext.Response.WriteAsync("hello, world");
|
||||
},
|
||||
new TestServiceContext
|
||||
new KestrelServerOptions
|
||||
{
|
||||
ServerOptions = new KestrelServerOptions
|
||||
AddServerHeader = false,
|
||||
Limits =
|
||||
{
|
||||
AddServerHeader = false,
|
||||
Limits =
|
||||
{
|
||||
RequestHeadersTimeout = RequestHeadersTimeout
|
||||
}
|
||||
RequestHeadersTimeout = RequestHeadersTimeout
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ReceiveResponse(TestConnection connection, TestServiceContext testServiceContext)
|
||||
private async Task ReceiveResponse(TestConnection connection)
|
||||
{
|
||||
await connection.Receive(
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {testServiceContext.DateHeaderValue}",
|
||||
"");
|
||||
await connection.ReceiveStartsWith("Date: ");
|
||||
await connection.Receive(
|
||||
"Transfer-Encoding: chunked",
|
||||
"",
|
||||
"c",
|
||||
|
|
@ -122,12 +122,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
|||
"");
|
||||
}
|
||||
|
||||
private async Task ReceiveTimeoutResponse(TestConnection connection, TestServiceContext testServiceContext)
|
||||
private async Task ReceiveTimeoutResponse(TestConnection connection)
|
||||
{
|
||||
await connection.ReceiveForcedEnd(
|
||||
await connection.Receive(
|
||||
"HTTP/1.1 408 Request Timeout",
|
||||
"Connection: close",
|
||||
$"Date: {testServiceContext.DateHeaderValue}",
|
||||
"");
|
||||
await connection.ReceiveStartsWith("Date: ");
|
||||
await connection.ReceiveForcedEnd(
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
// 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.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.TestHelpers
|
||||
{
|
||||
public class TimeoutTestServer : IDisposable
|
||||
{
|
||||
private readonly KestrelServer _server;
|
||||
private readonly ListenOptions _listenOptions;
|
||||
|
||||
public TimeoutTestServer(RequestDelegate app, KestrelServerOptions serverOptions)
|
||||
{
|
||||
var loggerFactory = new KestrelTestLoggerFactory(new TestApplicationErrorLogger());
|
||||
var libuvTransportFactory = new LibuvTransportFactory(Options.Create(new LibuvTransportOptions()), new LifetimeNotImplemented(), loggerFactory);
|
||||
|
||||
_listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
|
||||
serverOptions.ListenOptions.Add(_listenOptions);
|
||||
_server = new KestrelServer(Options.Create(serverOptions), libuvTransportFactory, loggerFactory);
|
||||
|
||||
try
|
||||
{
|
||||
_server.Start(new DummyApplication(app));
|
||||
}
|
||||
catch
|
||||
{
|
||||
_server.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public TestConnection CreateConnection()
|
||||
{
|
||||
return new TestConnection(_listenOptions.IPEndPoint.Port, _listenOptions.IPEndPoint.AddressFamily);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_server?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,22 +15,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
public PipeFactory PipeFactory { get; }
|
||||
public IScheduler InputWriterScheduler { get; }
|
||||
public IScheduler OutputReaderScheduler { get; }
|
||||
|
||||
public ITimeoutControl TimeoutControl { get; } = new MockTimeoutControl();
|
||||
|
||||
private class MockTimeoutControl : ITimeoutControl
|
||||
{
|
||||
public void CancelTimeout()
|
||||
{
|
||||
}
|
||||
|
||||
public void ResetTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
}
|
||||
|
||||
public void SetTimeout(long milliseconds, TimeoutAction timeoutAction)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,5 +34,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
public void NotAllConnectionsAborted() { }
|
||||
public void NotAllConnectionsClosedGracefully() { }
|
||||
public void RequestProcessingError(string connectionId, Exception ex) { }
|
||||
public virtual void TimerSlow(TimeSpan interval, DateTimeOffset now) { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers
|
|||
|
||||
public MockConnection()
|
||||
{
|
||||
TimeoutControl = this;
|
||||
RequestAbortedSource = new CancellationTokenSource();
|
||||
ListenerContext = new ListenerContext(new LibuvTransportContext());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -14,6 +15,8 @@ namespace Microsoft.AspNetCore.Testing
|
|||
// Application errors are logged using 13 as the eventId.
|
||||
private const int ApplicationErrorEventId = 13;
|
||||
|
||||
private TaskCompletionSource<object> _messageLoggedTcs = new TaskCompletionSource<object>();
|
||||
|
||||
public bool ThrowOnCriticalErrors { get; set; } = true;
|
||||
|
||||
public ConcurrentBag<LogMessage> Messages { get; } = new ConcurrentBag<LogMessage>();
|
||||
|
|
@ -24,6 +27,8 @@ namespace Microsoft.AspNetCore.Testing
|
|||
|
||||
public int ApplicationErrorsLogged => Messages.Count(message => message.EventId.Id == ApplicationErrorEventId);
|
||||
|
||||
public Task MessageLoggedTask => _messageLoggedTcs.Task;
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
return new Disposable(() => { });
|
||||
|
|
@ -55,6 +60,8 @@ namespace Microsoft.AspNetCore.Testing
|
|||
Exception = exception,
|
||||
Message = formatter(state, exception)
|
||||
});
|
||||
|
||||
_messageLoggedTcs.TrySetResult(null);
|
||||
}
|
||||
|
||||
public class LogMessage
|
||||
|
|
|
|||
|
|
@ -126,6 +126,38 @@ namespace Microsoft.AspNetCore.Testing
|
|||
}
|
||||
}
|
||||
|
||||
public async Task ReceiveStartsWith(string prefix, int maxLineLength = 1024)
|
||||
{
|
||||
var actual = new char[maxLineLength];
|
||||
var offset = 0;
|
||||
|
||||
while (offset < maxLineLength)
|
||||
{
|
||||
// Read one char at a time so we don't read past the end of the line.
|
||||
var task = _reader.ReadAsync(actual, offset, 1);
|
||||
if (!Debugger.IsAttached)
|
||||
{
|
||||
Assert.True(task.Wait(4000), "timeout");
|
||||
}
|
||||
var count = await task;
|
||||
if (count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Assert.True(count == 1);
|
||||
offset++;
|
||||
|
||||
if (actual[offset - 1] == '\n')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var actualLine = new string(actual, 0, offset);
|
||||
Assert.StartsWith(prefix, actualLine);
|
||||
}
|
||||
|
||||
public void Shutdown(SocketShutdown how)
|
||||
{
|
||||
_socket.Shutdown(how);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ namespace Microsoft.AspNetCore.Testing
|
|||
|
||||
Log = new TestKestrelTrace(logger);
|
||||
ThreadPool = new LoggingThreadPool(Log);
|
||||
DateHeaderValueManager = new DateHeaderValueManager(systemClock: new MockSystemClock());
|
||||
SystemClock = new MockSystemClock();
|
||||
DateHeaderValueManager = new DateHeaderValueManager(SystemClock);
|
||||
ConnectionManager = new FrameConnectionManager();
|
||||
DateHeaderValue = DateHeaderValueManager.GetDateHeaderValues().String;
|
||||
HttpParserFactory = frameAdapter => new HttpParser<FrameAdapter>(frameAdapter.Frame.ServiceContext.Log);
|
||||
ServerOptions = new KestrelServerOptions
|
||||
|
|
|
|||
Loading…
Reference in New Issue