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.
|
// 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.
|
// 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.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
|
|
@ -11,6 +12,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
{
|
{
|
||||||
public class ConnectionHandler<TContext> : IConnectionHandler
|
public class ConnectionHandler<TContext> : IConnectionHandler
|
||||||
{
|
{
|
||||||
|
private static long _lastFrameConnectionId = long.MinValue;
|
||||||
|
|
||||||
private readonly ListenOptions _listenOptions;
|
private readonly ListenOptions _listenOptions;
|
||||||
private readonly ServiceContext _serviceContext;
|
private readonly ServiceContext _serviceContext;
|
||||||
private readonly IHttpApplication<TContext> _application;
|
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 outputPipe = connectionInfo.PipeFactory.Create(GetOutputPipeOptions(connectionInfo.OutputReaderScheduler));
|
||||||
|
|
||||||
var connectionId = CorrelationIdGenerator.GetNextId();
|
var connectionId = CorrelationIdGenerator.GetNextId();
|
||||||
|
var frameConnectionId = Interlocked.Increment(ref _lastFrameConnectionId);
|
||||||
|
|
||||||
var frameContext = new FrameContext
|
var frameContext = new FrameContext
|
||||||
{
|
{
|
||||||
|
|
@ -44,6 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
var connection = new FrameConnection(new FrameConnectionContext
|
var connection = new FrameConnection(new FrameConnectionContext
|
||||||
{
|
{
|
||||||
ConnectionId = connectionId,
|
ConnectionId = connectionId,
|
||||||
|
FrameConnectionId = frameConnectionId,
|
||||||
ServiceContext = _serviceContext,
|
ServiceContext = _serviceContext,
|
||||||
PipeFactory = connectionInfo.PipeFactory,
|
PipeFactory = connectionInfo.PipeFactory,
|
||||||
ConnectionAdapters = _listenOptions.ConnectionAdapters,
|
ConnectionAdapters = _listenOptions.ConnectionAdapters,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
|
||||||
|
|
@ -16,13 +18,17 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
{
|
{
|
||||||
public class FrameConnection : IConnectionContext
|
public class FrameConnection : IConnectionContext, ITimeoutControl
|
||||||
{
|
{
|
||||||
private readonly FrameConnectionContext _context;
|
private readonly FrameConnectionContext _context;
|
||||||
private readonly Frame _frame;
|
private readonly Frame _frame;
|
||||||
private readonly List<IConnectionAdapter> _connectionAdapters;
|
private readonly List<IConnectionAdapter> _connectionAdapters;
|
||||||
private readonly TaskCompletionSource<object> _frameStartedTcs = new TaskCompletionSource<object>();
|
private readonly TaskCompletionSource<object> _frameStartedTcs = new TaskCompletionSource<object>();
|
||||||
|
|
||||||
|
private long _lastTimestamp;
|
||||||
|
private long _timeoutTimestamp = long.MaxValue;
|
||||||
|
private TimeoutAction _timeoutAction;
|
||||||
|
|
||||||
private AdaptedPipeline _adaptedPipeline;
|
private AdaptedPipeline _adaptedPipeline;
|
||||||
private Stream _filteredStream;
|
private Stream _filteredStream;
|
||||||
private Task _adaptedPipelineTask = TaskCache.CompletedTask;
|
private Task _adaptedPipelineTask = TaskCache.CompletedTask;
|
||||||
|
|
@ -32,6 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
_context = context;
|
_context = context;
|
||||||
_frame = context.Frame;
|
_frame = context.Frame;
|
||||||
_connectionAdapters = context.ConnectionAdapters;
|
_connectionAdapters = context.ConnectionAdapters;
|
||||||
|
context.ServiceContext.ConnectionManager.AddConnection(context.FrameConnectionId, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ConnectionId => _context.ConnectionId;
|
public string ConnectionId => _context.ConnectionId;
|
||||||
|
|
@ -55,11 +62,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
{
|
{
|
||||||
_frame.Input = _context.Input.Reader;
|
_frame.Input = _context.Input.Reader;
|
||||||
_frame.Output = _context.OutputProducer;
|
_frame.Output = _context.OutputProducer;
|
||||||
|
_frame.TimeoutControl = this;
|
||||||
|
|
||||||
if (_connectionAdapters.Count == 0)
|
if (_connectionAdapters.Count == 0)
|
||||||
{
|
{
|
||||||
_frame.Start();
|
StartFrame();
|
||||||
_frameStartedTcs.SetResult(null);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -75,6 +82,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
public void OnConnectionClosed()
|
public void OnConnectionClosed()
|
||||||
{
|
{
|
||||||
|
_context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId);
|
||||||
Log.ConnectionStop(ConnectionId);
|
Log.ConnectionStop(ConnectionId);
|
||||||
KestrelEventSource.Log.ConnectionStop(this);
|
KestrelEventSource.Log.ConnectionStop(this);
|
||||||
}
|
}
|
||||||
|
|
@ -127,8 +135,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
_frame.AdaptedConnections = adaptedConnections;
|
_frame.AdaptedConnections = adaptedConnections;
|
||||||
_frame.Start();
|
StartFrame();
|
||||||
_frameStartedTcs.SetResult(null);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -161,5 +168,58 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
_context.OutputProducer.Dispose();
|
_context.OutputProducer.Dispose();
|
||||||
_context.Input.Reader.Complete();
|
_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 class FrameConnectionContext
|
||||||
{
|
{
|
||||||
public string ConnectionId { get; set; }
|
public string ConnectionId { get; set; }
|
||||||
|
public long FrameConnectionId { get; set; }
|
||||||
public ServiceContext ServiceContext { get; set; }
|
public ServiceContext ServiceContext { get; set; }
|
||||||
public PipeFactory PipeFactory { get; set; }
|
public PipeFactory PipeFactory { get; set; }
|
||||||
public List<IConnectionAdapter> ConnectionAdapters { get; set; }
|
public List<IConnectionAdapter> ConnectionAdapters { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -12,23 +12,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages the generation of the date header value.
|
/// Manages the generation of the date header value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DateHeaderValueManager : IDisposable
|
public class DateHeaderValueManager : IHeartbeatHandler
|
||||||
{
|
{
|
||||||
private static readonly byte[] _datePreambleBytes = Encoding.ASCII.GetBytes("\r\nDate: ");
|
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 DateHeaderValues _dateValues;
|
||||||
|
|
||||||
private volatile bool _isDisposed = false;
|
|
||||||
private volatile bool _hadRequestsSinceLastTimerTick = false;
|
|
||||||
private Timer _dateValueTimer;
|
|
||||||
private long _lastRequestSeenTicks;
|
|
||||||
private volatile bool _timerIsRunning;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DateHeaderValueManager"/> class.
|
/// Initializes a new instance of the <see cref="DateHeaderValueManager"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -39,28 +28,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
internal DateHeaderValueManager(ISystemClock systemClock)
|
internal DateHeaderValueManager(ISystemClock systemClock)
|
||||||
: this(
|
|
||||||
systemClock: systemClock,
|
|
||||||
timeWithoutRequestsUntilIdle: TimeSpan.FromSeconds(10),
|
|
||||||
timerInterval: TimeSpan.FromSeconds(1))
|
|
||||||
{
|
{
|
||||||
}
|
SetDateValues(systemClock.UtcNow);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// in accordance with http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The value in string and byte[] format.</returns>
|
/// <returns>The value in string and byte[] format.</returns>
|
||||||
public DateHeaderValues GetDateHeaderValues()
|
public DateHeaderValues GetDateHeaderValues() => _dateValues;
|
||||||
{
|
|
||||||
_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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by the Timer (background) thread
|
// Called by the Timer (background) thread
|
||||||
private void TimerLoop(object state)
|
public void OnHeartbeat(DateTimeOffset now)
|
||||||
{
|
{
|
||||||
var now = _systemClock.UtcNow;
|
|
||||||
|
|
||||||
SetDateValues(now);
|
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>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private int _remainingRequestHeadersBytesAllowed;
|
private int _remainingRequestHeadersBytesAllowed;
|
||||||
private int _requestHeadersParsed;
|
private int _requestHeadersParsed;
|
||||||
|
|
||||||
protected readonly long _keepAliveMilliseconds;
|
protected readonly long _keepAliveTicks;
|
||||||
private readonly long _requestHeadersTimeoutMilliseconds;
|
private readonly long _requestHeadersTimeoutTicks;
|
||||||
|
|
||||||
protected long _responseBytesWritten;
|
protected long _responseBytesWritten;
|
||||||
|
|
||||||
|
|
@ -91,8 +91,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
_parser = ServiceContext.HttpParserFactory(new FrameAdapter(this));
|
_parser = ServiceContext.HttpParserFactory(new FrameAdapter(this));
|
||||||
|
|
||||||
FrameControl = this;
|
FrameControl = this;
|
||||||
_keepAliveMilliseconds = (long)ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
|
_keepAliveTicks = ServerOptions.Limits.KeepAliveTimeout.Ticks;
|
||||||
_requestHeadersTimeoutMilliseconds = (long)ServerOptions.Limits.RequestHeadersTimeout.TotalMilliseconds;
|
_requestHeadersTimeoutTicks = ServerOptions.Limits.RequestHeadersTimeout.Ticks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceContext ServiceContext => _frameContext.ServiceContext;
|
public ServiceContext ServiceContext => _frameContext.ServiceContext;
|
||||||
|
|
@ -102,10 +102,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
public ISocketOutput Output { get; set; }
|
public ISocketOutput Output { get; set; }
|
||||||
public IEnumerable<IAdaptedConnection> AdaptedConnections { get; set; }
|
public IEnumerable<IAdaptedConnection> AdaptedConnections { get; set; }
|
||||||
public ConnectionLifetimeControl LifetimeControl { get; set; }
|
public ConnectionLifetimeControl LifetimeControl { get; set; }
|
||||||
|
public ITimeoutControl TimeoutControl { get; set; }
|
||||||
|
|
||||||
protected ITimeoutControl TimeoutControl => ConnectionInformation.TimeoutControl;
|
|
||||||
protected IKestrelTrace Log => ServiceContext.Log;
|
protected IKestrelTrace Log => ServiceContext.Log;
|
||||||
|
|
||||||
private DateHeaderValueManager DateHeaderValueManager => ServiceContext.DateHeaderValueManager;
|
private DateHeaderValueManager DateHeaderValueManager => ServiceContext.DateHeaderValueManager;
|
||||||
// Hold direct reference to ServerOptions since this is used very often in the request processing path
|
// Hold direct reference to ServerOptions since this is used very often in the request processing path
|
||||||
private KestrelServerOptions ServerOptions { get; }
|
private KestrelServerOptions ServerOptions { get; }
|
||||||
|
|
@ -1017,7 +1016,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeoutControl.ResetTimeout(_requestHeadersTimeoutMilliseconds, TimeoutAction.SendTimeoutResponse);
|
TimeoutControl.ResetTimeout(_requestHeadersTimeoutTicks, TimeoutAction.SendTimeoutResponse);
|
||||||
|
|
||||||
_requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
|
_requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
|
||||||
goto case RequestProcessingStatus.ParsingRequestLine;
|
goto case RequestProcessingStatus.ParsingRequestLine;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
while (!_requestProcessingStopping)
|
while (!_requestProcessingStopping)
|
||||||
{
|
{
|
||||||
TimeoutControl.SetTimeout(_keepAliveMilliseconds, TimeoutAction.CloseConnection);
|
TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.CloseConnection);
|
||||||
|
|
||||||
InitializeHeaders();
|
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 ConnectionBadRequest(string connectionId, BadHttpRequestException ex);
|
||||||
|
|
||||||
void ApplicationError(string connectionId, string traceIdentifier, Exception 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>
|
/// <summary>
|
||||||
/// Abstracts the system clock to facilitate testing.
|
/// Abstracts the system clock to facilitate testing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface ISystemClock
|
public interface ISystemClock
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the current system time in UTC.
|
/// Retrieves the current system time in UTC.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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
|
public interface ITimeoutControl
|
||||||
{
|
{
|
||||||
void SetTimeout(long milliseconds, TimeoutAction timeoutAction);
|
void SetTimeout(long ticks, TimeoutAction timeoutAction);
|
||||||
void ResetTimeout(long milliseconds, TimeoutAction timeoutAction);
|
void ResetTimeout(long ticks, TimeoutAction timeoutAction);
|
||||||
void CancelTimeout();
|
void CancelTimeout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,6 +45,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
private static readonly Action<ILogger, string, Exception> _requestProcessingError =
|
private static readonly Action<ILogger, string, Exception> _requestProcessingError =
|
||||||
LoggerMessage.Define<string>(LogLevel.Information, 20, @"Connection id ""{ConnectionId}"" request processing ended abnormally.");
|
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;
|
protected readonly ILogger _logger;
|
||||||
|
|
||||||
public KestrelTrace(ILogger logger)
|
public KestrelTrace(ILogger logger)
|
||||||
|
|
@ -107,6 +110,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
_requestProcessingError(_logger, connectionId, ex);
|
_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)
|
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);
|
=> _logger.Log(logLevel, eventId, state, exception, formatter);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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
|
public enum TimeoutAction
|
||||||
{
|
{
|
||||||
|
|
@ -15,8 +15,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
|
||||||
|
|
||||||
public Func<FrameAdapter, IHttpParser<FrameAdapter>> HttpParserFactory { get; set; }
|
public Func<FrameAdapter, IHttpParser<FrameAdapter>> HttpParserFactory { get; set; }
|
||||||
|
|
||||||
|
public ISystemClock SystemClock { get; set; }
|
||||||
|
|
||||||
public DateHeaderValueManager DateHeaderValueManager { get; set; }
|
public DateHeaderValueManager DateHeaderValueManager { get; set; }
|
||||||
|
|
||||||
|
public FrameConnectionManager ConnectionManager { get; set; }
|
||||||
|
|
||||||
public KestrelServerOptions ServerOptions { get; set; }
|
public KestrelServerOptions ServerOptions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
private readonly ITransportFactory _transportFactory;
|
private readonly ITransportFactory _transportFactory;
|
||||||
|
|
||||||
private bool _isRunning;
|
private bool _isRunning;
|
||||||
private DateHeaderValueManager _dateHeaderValueManager;
|
private Heartbeat _heartbeat;
|
||||||
|
|
||||||
public KestrelServer(
|
public KestrelServer(
|
||||||
IOptions<KestrelServerOptions> options,
|
IOptions<KestrelServerOptions> options,
|
||||||
|
|
@ -85,9 +85,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
}
|
}
|
||||||
_isRunning = true;
|
_isRunning = true;
|
||||||
|
|
||||||
_dateHeaderValueManager = new DateHeaderValueManager();
|
|
||||||
var trace = new KestrelTrace(_logger);
|
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;
|
IThreadPool threadPool;
|
||||||
if (InternalOptions.ThreadPoolDispatching)
|
if (InternalOptions.ThreadPoolDispatching)
|
||||||
{
|
{
|
||||||
|
|
@ -103,7 +107,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
Log = trace,
|
Log = trace,
|
||||||
HttpParserFactory = frameParser => new HttpParser<FrameAdapter>(frameParser.Frame.ServiceContext.Log),
|
HttpParserFactory = frameParser => new HttpParser<FrameAdapter>(frameParser.Frame.ServiceContext.Log),
|
||||||
ThreadPool = threadPool,
|
ThreadPool = threadPool,
|
||||||
DateHeaderValueManager = _dateHeaderValueManager,
|
SystemClock = systemClock,
|
||||||
|
DateHeaderValueManager = dateHeaderValueManager,
|
||||||
|
ConnectionManager = connectionManager,
|
||||||
ServerOptions = Options
|
ServerOptions = Options
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -232,7 +238,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
Task.WaitAll(tasks);
|
Task.WaitAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
_dateHeaderValueManager?.Dispose();
|
_heartbeat?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateOptions()
|
private void ValidateOptions()
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions
|
||||||
void OnConnectionClosed();
|
void OnConnectionClosed();
|
||||||
Task StopAsync();
|
Task StopAsync();
|
||||||
void Abort(Exception ex);
|
void Abort(Exception ex);
|
||||||
void Timeout();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions
|
||||||
PipeFactory PipeFactory { get; }
|
PipeFactory PipeFactory { get; }
|
||||||
IScheduler InputWriterScheduler { get; }
|
IScheduler InputWriterScheduler { get; }
|
||||||
IScheduler OutputReaderScheduler { 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
|
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
{
|
{
|
||||||
public class LibuvConnection : LibuvConnectionContext, ITimeoutControl
|
public class LibuvConnection : LibuvConnectionContext
|
||||||
{
|
{
|
||||||
private const int MinAllocBufferSize = 2048;
|
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 TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>();
|
||||||
|
|
||||||
private long _lastTimestamp;
|
|
||||||
private long _timeoutTimestamp = long.MaxValue;
|
|
||||||
private TimeoutAction _timeoutAction;
|
|
||||||
private WritableBuffer? _currentWritableBuffer;
|
private WritableBuffer? _currentWritableBuffer;
|
||||||
|
|
||||||
public LibuvConnection(ListenerContext context, UvStreamHandle socket) : base(context)
|
public LibuvConnection(ListenerContext context, UvStreamHandle socket) : base(context)
|
||||||
{
|
{
|
||||||
_socket = socket;
|
_socket = socket;
|
||||||
socket.Connection = this;
|
socket.Connection = this;
|
||||||
TimeoutControl = this;
|
|
||||||
|
|
||||||
var tcpHandle = _socket as UvTcpHandle;
|
var tcpHandle = _socket as UvTcpHandle;
|
||||||
if (tcpHandle != null)
|
if (tcpHandle != null)
|
||||||
|
|
@ -45,8 +41,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
RemoteEndPoint = tcpHandle.GetPeerIPEndPoint();
|
RemoteEndPoint = tcpHandle.GetPeerIPEndPoint();
|
||||||
LocalEndPoint = tcpHandle.GetSockIPEndPoint();
|
LocalEndPoint = tcpHandle.GetSockIPEndPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastTimestamp = Thread.Loop.Now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For testing
|
// For testing
|
||||||
|
|
@ -74,7 +68,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
|
|
||||||
// Start socket prior to applying the ConnectionAdapter
|
// Start socket prior to applying the ConnectionAdapter
|
||||||
_socket.ReadStart(_allocCallback, _readCallback, this);
|
_socket.ReadStart(_allocCallback, _readCallback, this);
|
||||||
_lastTimestamp = Thread.Loop.Now();
|
|
||||||
|
|
||||||
// This *must* happen after socket.ReadStart
|
// This *must* happen after socket.ReadStart
|
||||||
// The socket output consumer is the only thing that can close the connection. If the
|
// 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);
|
_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)
|
private static LibuvFunctions.uv_buf_t AllocCallback(UvStreamHandle handle, int suggestedSize, object state)
|
||||||
{
|
{
|
||||||
return ((LibuvConnection)state).OnAlloc(handle, suggestedSize);
|
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 PipeFactory PipeFactory => ListenerContext.Thread.PipeFactory;
|
||||||
public IScheduler InputWriterScheduler => ListenerContext.Thread;
|
public IScheduler InputWriterScheduler => ListenerContext.Thread;
|
||||||
public IScheduler OutputReaderScheduler => 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 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
|
// 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
|
// 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
|
// 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 TaskCompletionSource<object> _threadTcs = new TaskCompletionSource<object>();
|
||||||
private readonly UvLoopHandle _loop;
|
private readonly UvLoopHandle _loop;
|
||||||
private readonly UvAsyncHandle _post;
|
private readonly UvAsyncHandle _post;
|
||||||
private readonly UvTimerHandle _heartbeatTimer;
|
|
||||||
private Queue<Work> _workAdding = new Queue<Work>(1024);
|
private Queue<Work> _workAdding = new Queue<Work>(1024);
|
||||||
private Queue<Work> _workRunning = new Queue<Work>(1024);
|
private Queue<Work> _workRunning = new Queue<Work>(1024);
|
||||||
private Queue<CloseHandle> _closeHandleAdding = new Queue<CloseHandle>(256);
|
private Queue<CloseHandle> _closeHandleAdding = new Queue<CloseHandle>(256);
|
||||||
|
|
@ -49,7 +38,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
private ExceptionDispatchInfo _closeError;
|
private ExceptionDispatchInfo _closeError;
|
||||||
private readonly ILibuvTrace _log;
|
private readonly ILibuvTrace _log;
|
||||||
private readonly TimeSpan _shutdownTimeout;
|
private readonly TimeSpan _shutdownTimeout;
|
||||||
private IntPtr _thisPtr;
|
|
||||||
|
|
||||||
public LibuvThread(LibuvTransport transport)
|
public LibuvThread(LibuvTransport transport)
|
||||||
{
|
{
|
||||||
|
|
@ -61,7 +49,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
_post = new UvAsyncHandle(_log);
|
_post = new UvAsyncHandle(_log);
|
||||||
_thread = new Thread(ThreadStart);
|
_thread = new Thread(ThreadStart);
|
||||||
_thread.Name = nameof(LibuvThread);
|
_thread.Name = nameof(LibuvThread);
|
||||||
_heartbeatTimer = new UvTimerHandle(_log);
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
// Mark the thread as being as unimportant to keeping the process alive.
|
// 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.
|
// 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()
|
private void AllowStop()
|
||||||
{
|
{
|
||||||
_heartbeatTimer.Stop();
|
|
||||||
_post.Unreference();
|
_post.Unreference();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,8 +255,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
{
|
{
|
||||||
_loop.Init(_transport.Libuv);
|
_loop.Init(_transport.Libuv);
|
||||||
_post.Init(_loop, OnPost, EnqueueCloseHandle);
|
_post.Init(_loop, OnPost, EnqueueCloseHandle);
|
||||||
_heartbeatTimer.Init(_loop, EnqueueCloseHandle);
|
|
||||||
_heartbeatTimer.Start(OnHeartbeat, timeout: HeartbeatMilliseconds, repeat: HeartbeatMilliseconds);
|
|
||||||
_initCompleted = true;
|
_initCompleted = true;
|
||||||
tcs.SetResult(0);
|
tcs.SetResult(0);
|
||||||
}
|
}
|
||||||
|
|
@ -286,8 +270,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_thisPtr = GCHandle.ToIntPtr(thisHandle);
|
|
||||||
|
|
||||||
_loop.Run();
|
_loop.Run();
|
||||||
if (_stopImmediate)
|
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
|
// run the loop one more time to delete the open handles
|
||||||
_post.Reference();
|
_post.Reference();
|
||||||
_post.Dispose();
|
_post.Dispose();
|
||||||
_heartbeatTimer.Dispose();
|
|
||||||
|
|
||||||
// Ensure the Dispose operations complete in the event loop.
|
// Ensure the Dispose operations complete in the event loop.
|
||||||
_loop.Run();
|
_loop.Run();
|
||||||
|
|
@ -333,12 +314,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
} while (wasWork && loopsRemaining > 0);
|
} while (wasWork && loopsRemaining > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnHeartbeat(UvTimerHandle timer)
|
|
||||||
{
|
|
||||||
Now = Loop.Now();
|
|
||||||
Walk(_heartbeatWalkCallback, _thisPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool DoPostWork()
|
private bool DoPostWork()
|
||||||
{
|
{
|
||||||
Queue<Work> queue;
|
Queue<Work> queue;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
@ -27,21 +29,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
UtcNow = now
|
UtcNow = now
|
||||||
};
|
};
|
||||||
var timeWithoutRequestsUntilIdle = TimeSpan.FromSeconds(1);
|
|
||||||
var timerInterval = TimeSpan.FromSeconds(10);
|
|
||||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock, timeWithoutRequestsUntilIdle, timerInterval);
|
|
||||||
string result;
|
|
||||||
|
|
||||||
try
|
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
|
||||||
{
|
Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||||
result = dateHeaderValueManager.GetDateHeaderValues().String;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
dateHeaderValueManager.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(now.ToString(Rfc1123DateFormat), result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -53,30 +43,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
UtcNow = now
|
UtcNow = now
|
||||||
};
|
};
|
||||||
var timeWithoutRequestsUntilIdle = TimeSpan.FromSeconds(1);
|
|
||||||
var timerInterval = TimeSpan.FromSeconds(10);
|
var timerInterval = TimeSpan.FromSeconds(10);
|
||||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock, timeWithoutRequestsUntilIdle, timerInterval);
|
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
|
||||||
string result1;
|
|
||||||
string result2;
|
|
||||||
|
|
||||||
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;
|
systemClock.UtcNow = future;
|
||||||
result2 = dateHeaderValueManager.GetDateHeaderValues().String;
|
Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
dateHeaderValueManager.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(now.ToString(Rfc1123DateFormat), result1);
|
|
||||||
Assert.Equal(now.ToString(Rfc1123DateFormat), result2);
|
|
||||||
Assert.Equal(1, systemClock.UtcNowCalled);
|
Assert.Equal(1, systemClock.UtcNowCalled);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetDateHeaderValue_ReturnsUpdatedValueAfterIdle()
|
public async Task GetDateHeaderValue_ReturnsUpdatedValueAfterHeartbeat()
|
||||||
{
|
{
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
var future = now.AddSeconds(10);
|
var future = now.AddSeconds(10);
|
||||||
|
|
@ -84,32 +66,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
UtcNow = now
|
UtcNow = now
|
||||||
};
|
};
|
||||||
var timeWithoutRequestsUntilIdle = TimeSpan.FromMilliseconds(250);
|
|
||||||
var timerInterval = TimeSpan.FromMilliseconds(100);
|
var timerInterval = TimeSpan.FromMilliseconds(100);
|
||||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock, timeWithoutRequestsUntilIdle, timerInterval);
|
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
|
||||||
string result1;
|
|
||||||
string result2;
|
|
||||||
|
|
||||||
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;
|
systemClock.UtcNow = future;
|
||||||
// Wait for longer than the idle timeout to ensure the timer is stopped
|
await heartbeatTcs.Task;
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
|
||||||
result2 = dateHeaderValueManager.GetDateHeaderValues().String;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
dateHeaderValueManager.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(now.ToString(Rfc1123DateFormat), result1);
|
Assert.Equal(future.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||||
Assert.Equal(future.ToString(Rfc1123DateFormat), result2);
|
Assert.True(systemClock.UtcNowCalled >= 2);
|
||||||
Assert.True(systemClock.UtcNowCalled >= 2);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetDateHeaderValue_ReturnsDateValueAfterDisposed()
|
public void GetDateHeaderValue_ReturnsLastDateValueAfterHeartbeatDisposed()
|
||||||
{
|
{
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
var future = now.AddSeconds(10);
|
var future = now.AddSeconds(10);
|
||||||
|
|
@ -117,17 +97,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
UtcNow = now
|
UtcNow = now
|
||||||
};
|
};
|
||||||
var timeWithoutRequestsUntilIdle = TimeSpan.FromSeconds(1);
|
|
||||||
var timerInterval = TimeSpan.FromSeconds(10);
|
var timerInterval = TimeSpan.FromSeconds(10);
|
||||||
var dateHeaderValueManager = new DateHeaderValueManager(systemClock, timeWithoutRequestsUntilIdle, timerInterval);
|
var dateHeaderValueManager = new DateHeaderValueManager(systemClock);
|
||||||
|
|
||||||
|
using (new Heartbeat(new IHeartbeatHandler[] { dateHeaderValueManager }, systemClock, null, timerInterval))
|
||||||
|
{
|
||||||
|
Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||||
|
}
|
||||||
|
|
||||||
var result1 = dateHeaderValueManager.GetDateHeaderValues().String;
|
|
||||||
dateHeaderValueManager.Dispose();
|
|
||||||
systemClock.UtcNow = future;
|
systemClock.UtcNow = future;
|
||||||
var result2 = dateHeaderValueManager.GetDateHeaderValues().String;
|
Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String);
|
||||||
|
|
||||||
Assert.Equal(now.ToString(Rfc1123DateFormat), result1);
|
|
||||||
Assert.Equal(future.ToString(Rfc1123DateFormat), result2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
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;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
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)
|
_frame = new TestFrame<object>(application: null, context: _frameContext)
|
||||||
{
|
{
|
||||||
Input = _input.Reader,
|
Input = _input.Reader,
|
||||||
Output = new MockSocketOutput()
|
Output = new MockSocketOutput(),
|
||||||
|
TimeoutControl = Mock.Of<ITimeoutControl>()
|
||||||
};
|
};
|
||||||
|
|
||||||
_frame.Reset();
|
_frame.Reset();
|
||||||
|
|
@ -320,16 +320,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ParseRequestStartsRequestHeadersTimeoutOnFirstByteAvailable()
|
public async Task ParseRequestStartsRequestHeadersTimeoutOnFirstByteAvailable()
|
||||||
{
|
{
|
||||||
var connectionInfo = (MockConnectionInformation)_frameContext.ConnectionInformation;
|
|
||||||
var connectionControl = new Mock<ITimeoutControl>();
|
var connectionControl = new Mock<ITimeoutControl>();
|
||||||
connectionInfo.TimeoutControl = connectionControl.Object;
|
_frame.TimeoutControl = connectionControl.Object;
|
||||||
|
|
||||||
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
|
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("G"));
|
||||||
|
|
||||||
_frame.ParseRequest((await _input.Reader.ReadAsync()).Buffer, out _consumed, out _examined);
|
_frame.ParseRequest((await _input.Reader.ReadAsync()).Buffer, out _consumed, out _examined);
|
||||||
_input.Reader.Advance(_consumed, _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));
|
connectionControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutAction.SendTimeoutResponse));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,13 +443,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RequestProcessingAsyncEnablesKeepAliveTimeout()
|
public void RequestProcessingAsyncEnablesKeepAliveTimeout()
|
||||||
{
|
{
|
||||||
var connectionInfo = (MockConnectionInformation)_frameContext.ConnectionInformation;
|
|
||||||
var connectionControl = new Mock<ITimeoutControl>();
|
var connectionControl = new Mock<ITimeoutControl>();
|
||||||
connectionInfo.TimeoutControl = connectionControl.Object;
|
_frame.TimeoutControl = connectionControl.Object;
|
||||||
|
|
||||||
var requestProcessingTask = _frame.RequestProcessingAsync();
|
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));
|
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
|
||||||
|
|
||||||
_frame.StopAsync();
|
_frame.StopAsync();
|
||||||
|
|
@ -817,7 +815,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
public PipeFactory PipeFactory { get; }
|
public PipeFactory PipeFactory { get; }
|
||||||
public IScheduler InputWriterScheduler { get; }
|
public IScheduler InputWriterScheduler { get; }
|
||||||
public IScheduler OutputReaderScheduler { get; }
|
public IScheduler OutputReaderScheduler { get; }
|
||||||
public ITimeoutControl TimeoutControl { get; set; } = Mock.Of<ITimeoutControl>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RequestHeadersWrapper : IHeaderDictionary
|
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
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
{
|
{
|
||||||
class TestInput : ITimeoutControl, IFrameControl, IDisposable
|
class TestInput : IFrameControl, IDisposable
|
||||||
{
|
{
|
||||||
private MemoryPool _memoryPool;
|
private MemoryPool _memoryPool;
|
||||||
private PipeFactory _pipelineFactory;
|
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()
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.TestHelpers;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Xunit;
|
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(
|
await connection.Send(
|
||||||
"GET / HTTP/1.1",
|
"GET / HTTP/1.1",
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
await ReceiveResponse(connection, server.Context);
|
await ReceiveResponse(connection);
|
||||||
await connection.WaitForConnectionClose().TimeoutAfter(LongDelay);
|
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++)
|
for (var i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -77,14 +79,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++)
|
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();
|
var cts = new CancellationTokenSource();
|
||||||
cts.CancelAfter(LongDelay);
|
cts.CancelAfter(LongDelay);
|
||||||
|
|
@ -108,13 +110,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
"0",
|
"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(
|
await connection.Send(
|
||||||
"GET /longrunning HTTP/1.1",
|
"GET /longrunning HTTP/1.1",
|
||||||
|
|
@ -127,28 +129,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ReceiveResponse(connection, server.Context);
|
await ReceiveResponse(connection);
|
||||||
|
|
||||||
await connection.Send(
|
await connection.Send(
|
||||||
"GET / HTTP/1.1",
|
"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 Task.Delay(LongDelay);
|
||||||
await connection.WaitForConnectionClose().TimeoutAfter(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(
|
await connection.Send(
|
||||||
"GET /upgrade HTTP/1.1",
|
"GET /upgrade HTTP/1.1",
|
||||||
|
|
@ -157,7 +159,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
await connection.Receive(
|
await connection.Receive(
|
||||||
"HTTP/1.1 101 Switching Protocols",
|
"HTTP/1.1 101 Switching Protocols",
|
||||||
"Connection: Upgrade",
|
"Connection: Upgrade",
|
||||||
$"Date: {server.Context.DateHeaderValue}",
|
"");
|
||||||
|
await connection.ReceiveStartsWith("Date: ");
|
||||||
|
await connection.Receive(
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
cts.CancelAfter(LongDelay);
|
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(
|
await connection.Receive(
|
||||||
"HTTP/1.1 200 OK",
|
"HTTP/1.1 200 OK",
|
||||||
$"Date: {testServiceContext.DateHeaderValue}",
|
"");
|
||||||
|
await connection.ReceiveStartsWith("Date: ");
|
||||||
|
await connection.Receive(
|
||||||
"Transfer-Encoding: chunked",
|
"Transfer-Encoding: chunked",
|
||||||
"",
|
"",
|
||||||
"c",
|
"c",
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.TestHelpers;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Xunit;
|
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())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
await connection.Send(
|
await connection.Send(
|
||||||
"GET / HTTP/1.1",
|
"GET / HTTP/1.1",
|
||||||
headers);
|
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())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
|
|
@ -60,20 +61,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
await Task.Delay(RequestHeadersTimeout);
|
await Task.Delay(RequestHeadersTimeout);
|
||||||
await connection.Send(
|
await connection.Send(
|
||||||
"a");
|
"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())
|
using (var connection = server.CreateConnection())
|
||||||
{
|
{
|
||||||
await connection.Send(requestLine);
|
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())
|
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.Request.Body.ReadAsync(new byte[1], 0, 1);
|
||||||
await httpContext.Response.WriteAsync("hello, world");
|
await httpContext.Response.WriteAsync("hello, world");
|
||||||
},
|
},
|
||||||
new TestServiceContext
|
new KestrelServerOptions
|
||||||
{
|
{
|
||||||
ServerOptions = new KestrelServerOptions
|
AddServerHeader = false,
|
||||||
|
Limits =
|
||||||
{
|
{
|
||||||
AddServerHeader = false,
|
RequestHeadersTimeout = RequestHeadersTimeout
|
||||||
Limits =
|
|
||||||
{
|
|
||||||
RequestHeadersTimeout = RequestHeadersTimeout
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReceiveResponse(TestConnection connection, TestServiceContext testServiceContext)
|
private async Task ReceiveResponse(TestConnection connection)
|
||||||
{
|
{
|
||||||
await connection.Receive(
|
await connection.Receive(
|
||||||
"HTTP/1.1 200 OK",
|
"HTTP/1.1 200 OK",
|
||||||
$"Date: {testServiceContext.DateHeaderValue}",
|
"");
|
||||||
|
await connection.ReceiveStartsWith("Date: ");
|
||||||
|
await connection.Receive(
|
||||||
"Transfer-Encoding: chunked",
|
"Transfer-Encoding: chunked",
|
||||||
"",
|
"",
|
||||||
"c",
|
"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",
|
"HTTP/1.1 408 Request Timeout",
|
||||||
"Connection: close",
|
"Connection: close",
|
||||||
$"Date: {testServiceContext.DateHeaderValue}",
|
"");
|
||||||
|
await connection.ReceiveStartsWith("Date: ");
|
||||||
|
await connection.ReceiveForcedEnd(
|
||||||
"Content-Length: 0",
|
"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 PipeFactory PipeFactory { get; }
|
||||||
public IScheduler InputWriterScheduler { get; }
|
public IScheduler InputWriterScheduler { get; }
|
||||||
public IScheduler OutputReaderScheduler { 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 NotAllConnectionsAborted() { }
|
||||||
public void NotAllConnectionsClosedGracefully() { }
|
public void NotAllConnectionsClosedGracefully() { }
|
||||||
public void RequestProcessingError(string connectionId, Exception ex) { }
|
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()
|
public MockConnection()
|
||||||
{
|
{
|
||||||
TimeoutControl = this;
|
|
||||||
RequestAbortedSource = new CancellationTokenSource();
|
RequestAbortedSource = new CancellationTokenSource();
|
||||||
ListenerContext = new ListenerContext(new LibuvTransportContext());
|
ListenerContext = new ListenerContext(new LibuvTransportContext());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -14,6 +15,8 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
// Application errors are logged using 13 as the eventId.
|
// Application errors are logged using 13 as the eventId.
|
||||||
private const int ApplicationErrorEventId = 13;
|
private const int ApplicationErrorEventId = 13;
|
||||||
|
|
||||||
|
private TaskCompletionSource<object> _messageLoggedTcs = new TaskCompletionSource<object>();
|
||||||
|
|
||||||
public bool ThrowOnCriticalErrors { get; set; } = true;
|
public bool ThrowOnCriticalErrors { get; set; } = true;
|
||||||
|
|
||||||
public ConcurrentBag<LogMessage> Messages { get; } = new ConcurrentBag<LogMessage>();
|
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 int ApplicationErrorsLogged => Messages.Count(message => message.EventId.Id == ApplicationErrorEventId);
|
||||||
|
|
||||||
|
public Task MessageLoggedTask => _messageLoggedTcs.Task;
|
||||||
|
|
||||||
public IDisposable BeginScope<TState>(TState state)
|
public IDisposable BeginScope<TState>(TState state)
|
||||||
{
|
{
|
||||||
return new Disposable(() => { });
|
return new Disposable(() => { });
|
||||||
|
|
@ -55,6 +60,8 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
Exception = exception,
|
Exception = exception,
|
||||||
Message = formatter(state, exception)
|
Message = formatter(state, exception)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_messageLoggedTcs.TrySetResult(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LogMessage
|
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)
|
public void Shutdown(SocketShutdown how)
|
||||||
{
|
{
|
||||||
_socket.Shutdown(how);
|
_socket.Shutdown(how);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
|
|
||||||
Log = new TestKestrelTrace(logger);
|
Log = new TestKestrelTrace(logger);
|
||||||
ThreadPool = new LoggingThreadPool(Log);
|
ThreadPool = new LoggingThreadPool(Log);
|
||||||
DateHeaderValueManager = new DateHeaderValueManager(systemClock: new MockSystemClock());
|
SystemClock = new MockSystemClock();
|
||||||
|
DateHeaderValueManager = new DateHeaderValueManager(SystemClock);
|
||||||
|
ConnectionManager = new FrameConnectionManager();
|
||||||
DateHeaderValue = DateHeaderValueManager.GetDateHeaderValues().String;
|
DateHeaderValue = DateHeaderValueManager.GetDateHeaderValues().String;
|
||||||
HttpParserFactory = frameAdapter => new HttpParser<FrameAdapter>(frameAdapter.Frame.ServiceContext.Log);
|
HttpParserFactory = frameAdapter => new HttpParser<FrameAdapter>(frameAdapter.Frame.ServiceContext.Log);
|
||||||
ServerOptions = new KestrelServerOptions
|
ServerOptions = new KestrelServerOptions
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue