Improve keep-alive timeout.

- Track time more accurately
- Control timeout in Connection instead of Frame
This commit is contained in:
Cesar Blum Silveira 2016-09-12 11:40:30 -07:00
parent 84efe624a6
commit 1a273f5a34
16 changed files with 304 additions and 163 deletions

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -38,6 +39,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>();
private BufferSizeControl _bufferSizeControl;
private long _lastTimestamp;
private long _timeoutTimestamp = long.MaxValue;
public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
{
_socket = socket;
@ -62,6 +66,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
_frame = FrameFactory(this);
_lastTimestamp = Thread.Loop.Now();
}
// Internal for testing
@ -156,9 +162,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
// Called on Libuv thread
public void Tick()
public void Tick(long timestamp)
{
_frame.Tick();
if (timestamp > _timeoutTimestamp)
{
StopAsync();
}
Interlocked.Exchange(ref _lastTimestamp, timestamp);
}
private void ApplyConnectionFilter()
@ -282,9 +293,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
}
void IConnectionControl.Stop()
void IConnectionControl.SetTimeout(long milliseconds)
{
StopAsync();
Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported");
// Add KestrelThread.HeartbeatMilliseconds extra milliseconds since this can be called right before the next heartbeat.
Interlocked.Exchange(ref _timeoutTimestamp, _lastTimestamp + milliseconds + KestrelThread.HeartbeatMilliseconds);
}
void IConnectionControl.CancelTimeout()
{
Interlocked.Exchange(ref _timeoutTimestamp, long.MaxValue);
}
private static unsafe string GenerateConnectionId(long id)

View File

@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private int _remainingRequestHeadersBytesAllowed;
private int _requestHeadersParsed;
private int _secondsSinceLastRequest;
protected readonly long _keepAliveMilliseconds;
public Frame(ConnectionContext context)
: base(context)
@ -77,6 +77,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_pathBase = context.ServerAddress.PathBase;
FrameControl = this;
_keepAliveMilliseconds = (long)ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
}
public string ConnectionIdFeature { get; set; }
@ -805,6 +806,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return RequestLineStatus.Empty;
}
ConnectionControl.CancelTimeout();
_requestProcessingStatus = RequestProcessingStatus.RequestStarted;
int bytesScanned;
@ -1269,25 +1272,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
Log.ApplicationError(ConnectionId, ex);
}
public void Tick()
{
// we're in between requests and not about to start processing a new one
if (_requestProcessingStatus == RequestProcessingStatus.RequestPending && !SocketInput.IsCompleted)
{
if (_secondsSinceLastRequest > ServerOptions.Limits.KeepAliveTimeout.TotalSeconds)
{
ConnectionControl.Stop();
}
_secondsSinceLastRequest++;
}
}
public void RequestFinished()
{
_secondsSinceLastRequest = 0;
}
public enum RequestLineStatus
{
Empty,

View File

@ -32,6 +32,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
{
while (!_requestProcessingStopping)
{
ConnectionControl.SetTimeout(_keepAliveMilliseconds);
while (!_requestProcessingStopping && TakeStartLine(SocketInput) != RequestLineStatus.Done)
{
if (SocketInput.CheckFinOrThrow())

View File

@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
void Pause();
void Resume();
void End(ProduceEndType endType);
void Stop();
void SetTimeout(long milliseconds);
void CancelTimeout();
}
}

View File

@ -172,7 +172,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
var limit = buffer.Array == null ? inputLengthLimit : Math.Min(buffer.Count, inputLengthLimit);
if (limit == 0)
{
_context.RequestFinished();
return new ValueTask<int>(0);
}
@ -189,11 +188,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_context.RejectRequest(RequestRejectionReason.UnexpectedEndOfRequestContent);
}
if (_inputLength == 0)
{
_context.RequestFinished();
}
return new ValueTask<int>(actual);
}
else
@ -212,11 +206,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_context.RejectRequest(RequestRejectionReason.UnexpectedEndOfRequestContent);
}
if (_inputLength == 0)
{
_context.RequestFinished();
}
return actual;
}
}
@ -368,8 +357,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_mode = Mode.Complete;
}
_context.RequestFinished();
return 0;
}

View File

@ -19,6 +19,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
/// </summary>
public class KestrelThread
{
public const long HeartbeatMilliseconds = 1000;
private static readonly Action<object, object> _postCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
private static readonly Action<object, object> _postAsyncCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
@ -27,9 +29,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
// otherwise it needs to wait till the next pass of the libuv loop
private readonly int _maxLoops = 8;
// how often the heartbeat timer will tick connections
private const int _heartbeatMilliseconds = 1000;
private readonly KestrelEngine _engine;
private readonly IApplicationLifetime _appLifetime;
private readonly Thread _thread;
@ -280,7 +279,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
_loop.Init(_engine.Libuv);
_post.Init(_loop, OnPost, EnqueueCloseHandle);
_heartbeatTimer.Init(_loop, EnqueueCloseHandle);
_heartbeatTimer.Start(OnHeartbeat, timeout: 1000, repeat: 1000);
_heartbeatTimer.Start(OnHeartbeat, timeout: HeartbeatMilliseconds, repeat: HeartbeatMilliseconds);
_initCompleted = true;
tcs.SetResult(0);
}
@ -337,10 +336,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
private void OnHeartbeat(UvTimerHandle timer)
{
var now = Loop.Now();
Walk(ptr =>
{
var handle = UvMemory.FromIntPtr<UvHandle>(ptr);
(handle as UvStreamHandle)?.Connection?.Tick();
(handle as UvStreamHandle)?.Connection?.Tick(now);
});
}

View File

@ -56,6 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
_uv_timer_init = NativeMethods.uv_timer_init;
_uv_timer_start = NativeMethods.uv_timer_start;
_uv_timer_stop = NativeMethods.uv_timer_stop;
_uv_now = NativeMethods.uv_now;
}
// Second ctor that doesn't set any fields only to be used by MockLibuv
@ -434,6 +435,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
ThrowIfErrored(_uv_timer_stop(handle));
}
protected Func<UvLoopHandle, long> _uv_now;
unsafe public long now(UvLoopHandle loop)
{
loop.Validate();
return _uv_now(loop);
}
public delegate int uv_tcp_getsockname_func(UvTcpHandle handle, out SockAddr addr, ref int namelen);
protected uv_tcp_getsockname_func _uv_tcp_getsockname;
public void tcp_getsockname(UvTcpHandle handle, out SockAddr addr, ref int namelen)
@ -640,6 +648,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern int uv_timer_stop(UvTimerHandle handle);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern long uv_now(UvLoopHandle loop);
[DllImport("WS2_32.dll", CallingConvention = CallingConvention.Winapi)]
unsafe public static extern int WSAIoctl(
IntPtr socket,

View File

@ -33,6 +33,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Networking
_uv.stop(this);
}
public long Now()
{
return _uv.now(this);
}
unsafe protected override bool ReleaseHandle()
{
var memory = handle;

View File

@ -23,8 +23,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel
// Matches the default LimitRequestFields in Apache httpd.
private int _maxRequestHeaderCount = 100;
// Matches the default http.sys keep-alive timouet.
private TimeSpan _keepAliveTimeout = TimeSpan.FromMinutes(2);
// Matches the default http.sys connection timeout.
private TimeSpan _connectionTimeout = TimeSpan.FromMinutes(2);
/// <summary>
/// Gets or sets the maximum size of the response buffer before write
@ -146,17 +146,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel
/// Gets or sets the keep-alive timeout.
/// </summary>
/// <remarks>
/// Defaults to 2 minutes. Timeout granularity is in seconds. Sub-second values will be rounded to the next second.
/// Defaults to 2 minutes.
/// </remarks>
public TimeSpan KeepAliveTimeout
{
get
{
return _keepAliveTimeout;
return _connectionTimeout;
}
set
{
_keepAliveTimeout = TimeSpan.FromSeconds(Math.Ceiling(value.TotalSeconds));
_connectionTimeout = value;
}
}
}

View File

@ -3,8 +3,11 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing;
using Xunit;
@ -13,23 +16,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public class KeepAliveTimeoutTests
{
private static readonly TimeSpan KeepAliveTimeout = TimeSpan.FromSeconds(10);
private static readonly int LongDelay = (int)TimeSpan.FromSeconds(30).TotalMilliseconds;
private static readonly int ShortDelay = LongDelay / 10;
private static readonly TimeSpan LongDelay = TimeSpan.FromSeconds(30);
private static readonly TimeSpan ShortDelay = TimeSpan.FromSeconds(LongDelay.TotalSeconds / 10);
[Fact]
public async Task TestKeepAliveTimeout()
{
using (var server = CreateServer())
var longRunningCancellationTokenSource = new CancellationTokenSource();
var upgradeCancellationTokenSource = new CancellationTokenSource();
using (var server = CreateServer(longRunningCancellationTokenSource.Token, upgradeCancellationTokenSource.Token))
{
var tasks = new[]
{
ConnectionClosedWhenKeepAliveTimeoutExpires(server),
ConnectionClosedWhenKeepAliveTimeoutExpiresAfterChunkedRequest(server),
KeepAliveTimeoutResetsBetweenContentLengthRequests(server),
KeepAliveTimeoutResetsBetweenChunkedRequests(server),
KeepAliveTimeoutNotTriggeredMidContentLengthRequest(server),
KeepAliveTimeoutNotTriggeredMidChunkedRequest(server),
ConnectionTimesOutWhenOpenedButNoRequestSent(server)
ConnectionKeptAliveBetweenRequests(server),
ConnectionNotTimedOutWhileRequestBeingSent(server),
ConnectionNotTimedOutWhileAppIsRunning(server, longRunningCancellationTokenSource),
ConnectionTimesOutWhenOpenedButNoRequestSent(server),
KeepAliveTimeoutDoesNotApplyToUpgradedConnections(server, upgradeCancellationTokenSource)
};
await Task.WhenAll(tasks);
@ -59,110 +64,76 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
private async Task ConnectionClosedWhenKeepAliveTimeoutExpiresAfterChunkedRequest(TestServer server)
private async Task ConnectionKeptAliveBetweenRequests(TestServer server)
{
using (var connection = new TestConnection(server.Port))
{
for (var i = 0; i < 10; i++)
{
await connection.Send(
"GET / HTTP/1.1",
"",
"");
await Task.Delay(ShortDelay);
}
for (var i = 0; i < 10; i++)
{
await ReceiveResponse(connection, server.Context);
}
}
}
private async Task ConnectionNotTimedOutWhileRequestBeingSent(TestServer server)
{
using (var connection = new TestConnection(server.Port))
{
var cts = new CancellationTokenSource();
cts.CancelAfter(LongDelay);
await connection.Send(
"POST / HTTP/1.1",
"Transfer-Encoding: chunked",
"",
"5", "hello",
"6", " world",
"");
while (!cts.IsCancellationRequested)
{
await connection.Send(
"1",
"a",
"");
}
await connection.Send(
"0",
"",
"");
"",
"");
await ReceiveResponse(connection, server.Context);
await Task.Delay(LongDelay);
await Assert.ThrowsAsync<IOException>(async () =>
{
await connection.Send(
"GET / HTTP/1.1",
"",
"");
await ReceiveResponse(connection, server.Context);
});
}
}
private async Task KeepAliveTimeoutResetsBetweenContentLengthRequests(TestServer server)
{
using (var connection = new TestConnection(server.Port))
{
for (var i = 0; i < 10; i++)
{
await connection.Send(
"GET / HTTP/1.1",
"",
"");
await Task.Delay(ShortDelay);
}
for (var i = 0; i < 10; i++)
{
await ReceiveResponse(connection, server.Context);
}
}
}
private async Task KeepAliveTimeoutResetsBetweenChunkedRequests(TestServer server)
{
using (var connection = new TestConnection(server.Port))
{
for (var i = 0; i < 10; i++)
{
await connection.Send(
"POST / HTTP/1.1",
"Transfer-Encoding: chunked",
"",
"5", "hello",
"6", " world",
"0",
"",
"");
await Task.Delay(ShortDelay);
}
for (var i = 0; i < 10; i++)
{
await ReceiveResponse(connection, server.Context);
}
}
}
private async Task KeepAliveTimeoutNotTriggeredMidContentLengthRequest(TestServer server)
private async Task ConnectionNotTimedOutWhileAppIsRunning(TestServer server, CancellationTokenSource cts)
{
using (var connection = new TestConnection(server.Port))
{
await connection.Send(
"POST / HTTP/1.1",
"Content-Length: 8",
"GET /longrunning HTTP/1.1",
"",
"a");
await Task.Delay(LongDelay);
await connection.Send("bcdefgh");
await ReceiveResponse(connection, server.Context);
}
}
"");
cts.CancelAfter(LongDelay);
while (!cts.IsCancellationRequested)
{
await Task.Delay(1000);
}
await ReceiveResponse(connection, server.Context);
private async Task KeepAliveTimeoutNotTriggeredMidChunkedRequest(TestServer server)
{
using (var connection = new TestConnection(server.Port))
{
await connection.Send(
"POST / HTTP/1.1",
"Transfer-Encoding: chunked",
"",
"5", "hello",
"");
await Task.Delay(LongDelay);
await connection.Send(
"6", " world",
"0",
"",
"");
"GET / HTTP/1.1",
"",
"");
await ReceiveResponse(connection, server.Context);
}
}
@ -182,9 +153,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
private TestServer CreateServer()
private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections(TestServer server, CancellationTokenSource cts)
{
return new TestServer(App, new TestServiceContext
using (var connection = new TestConnection(server.Port))
{
await connection.Send(
"GET /upgrade HTTP/1.1",
"",
"");
await connection.Receive(
"HTTP/1.1 101 Switching Protocols",
"Connection: Upgrade",
$"Date: {server.Context.DateHeaderValue}",
"",
"");
cts.CancelAfter(LongDelay);
while (!cts.IsCancellationRequested)
{
await Task.Delay(1000);
}
await connection.Receive("hello, world");
}
}
private TestServer CreateServer(CancellationToken longRunningCt, CancellationToken upgradeCt)
{
return new TestServer(httpContext => App(httpContext, longRunningCt, upgradeCt), new TestServiceContext
{
ServerOptions = new KestrelServerOptions
{
@ -197,11 +193,36 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
});
}
private async Task App(HttpContext httpContext)
private async Task App(HttpContext httpContext, CancellationToken longRunningCt, CancellationToken upgradeCt)
{
const string response = "hello, world";
httpContext.Response.ContentLength = response.Length;
await httpContext.Response.WriteAsync(response);
var ct = httpContext.RequestAborted;
if (httpContext.Request.Path == "/longrunning")
{
while (!longRunningCt.IsCancellationRequested)
{
await Task.Delay(1000);
}
await httpContext.Response.WriteAsync("hello, world");
}
else if (httpContext.Request.Path == "/upgrade")
{
using (var stream = await httpContext.Features.Get<IHttpUpgradeFeature>().UpgradeAsync())
{
while (!upgradeCt.IsCancellationRequested)
{
await Task.Delay(LongDelay);
}
var responseBytes = Encoding.ASCII.GetBytes("hello, world");
await stream.WriteAsync(responseBytes, 0, responseBytes.Length);
}
}
else
{
await httpContext.Response.WriteAsync("hello, world");
}
}
private async Task ReceiveResponse(TestConnection connection, TestServiceContext testServiceContext)
@ -209,9 +230,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {testServiceContext.DateHeaderValue}",
"Content-Length: 12",
"Transfer-Encoding: chunked",
"",
"hello, world");
"c",
"hello, world",
"0",
"",
"");
}
}
}

View File

@ -1,4 +1,8 @@
using System.Threading;
// 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 System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Internal;
@ -14,7 +18,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
public class ConnectionTests
{
[Fact]
public void DoesNotEndConnectionOnZeroRead()
public async Task DoesNotEndConnectionOnZeroRead()
{
var mockLibuv = new MockLibuv();
@ -30,14 +34,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
ServerAddress = ServerAddress.FromUrl("http://127.0.0.1:0"),
Thread = engine.Threads[0]
};
var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, trace);
var connection = new Connection(context, socket);
connection.Start();
Libuv.uv_buf_t ignored;
mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out ignored);
mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored);
Assert.False(connection.SocketInput.CheckFinOrThrow());
Connection connection = null;
await context.Thread.PostAsync(_ =>
{
var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, trace);
connection = new Connection(context, socket);
connection.Start();
Libuv.uv_buf_t ignored;
mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out ignored);
mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored);
Assert.False(connection.SocketInput.CheckFinOrThrow());
}, null);
connection.ConnectionControl.End(ProduceEndType.SocketDisconnect);
}

View File

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Internal;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.KestrelTests
@ -724,6 +725,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
var connectionContext = new ConnectionContext()
{
ConnectionControl = new Mock<IConnectionControl>().Object,
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
ServerOptions = new KestrelServerOptions(),
@ -770,6 +772,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
var connectionContext = new ConnectionContext()
{
ConnectionControl = new Mock<IConnectionControl>().Object,
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
ServerOptions = new KestrelServerOptions(),
@ -786,6 +789,59 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
}
}
[Fact]
public void TakeStartLineDisablesKeepAliveTimeoutOnFirstByteAvailable()
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
using (var pool = new MemoryPool())
using (var socketInput = new SocketInput(pool, ltp))
{
var connectionControl = new Mock<IConnectionControl>();
var connectionContext = new ConnectionContext()
{
ConnectionControl = connectionControl.Object,
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
ServerOptions = new KestrelServerOptions(),
Log = trace
};
var frame = new Frame<object>(application: null, context: connectionContext);
frame.Reset();
var requestLineBytes = Encoding.ASCII.GetBytes("G");
socketInput.IncomingData(requestLineBytes, 0, requestLineBytes.Length);
frame.TakeStartLine(socketInput);
connectionControl.Verify(cc => cc.CancelTimeout());
}
}
[Fact]
public void TakeStartLineDoesNotDisableKeepAliveTimeoutIfNoDataAvailable()
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
using (var pool = new MemoryPool())
using (var socketInput = new SocketInput(pool, ltp))
{
var connectionControl = new Mock<IConnectionControl>();
var connectionContext = new ConnectionContext()
{
ConnectionControl = connectionControl.Object,
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
ServerOptions = new KestrelServerOptions(),
Log = trace
};
var frame = new Frame<object>(application: null, context: connectionContext);
frame.Reset();
frame.TakeStartLine(socketInput);
connectionControl.Verify(cc => cc.CancelTimeout(), Times.Never);
}
}
[Fact]
public void TakeMessageHeadersCallsConsumingCompleteWithFurthestExamined()
{
@ -864,5 +920,35 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.Equal(false, frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
}
}
[Fact]
public void RequestProcessingAsyncEnablesKeepAliveTimeout()
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
using (var pool = new MemoryPool())
using (var socketInput = new SocketInput(pool, ltp))
{
var connectionControl = new Mock<IConnectionControl>();
var connectionContext = new ConnectionContext()
{
ConnectionControl = connectionControl.Object,
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
ServerOptions = new KestrelServerOptions(),
Log = trace
};
var frame = new Frame<object>(application: null, context: connectionContext);
frame.Reset();
var requestProcessingTask = frame.RequestProcessingAsync();
connectionControl.Verify(cc => cc.SetTimeout((long)connectionContext.ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds));
frame.Stop();
socketInput.IncomingFin();
requestProcessingTask.Wait();
}
}
}
}

View File

@ -161,11 +161,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[InlineData(2.1)]
[InlineData(2.5)]
[InlineData(2.9)]
public void KeepAliveTimeoutIsRoundedToTheNextSecond(double seconds)
public void KeepAliveTimeoutValid(double seconds)
{
var o = new KestrelServerLimits();
o.KeepAliveTimeout = TimeSpan.FromSeconds(seconds);
Assert.Equal(Math.Ceiling(seconds), o.KeepAliveTimeout.TotalSeconds);
Assert.Equal(seconds, o.KeepAliveTimeout.TotalSeconds);
}
}
}

View File

@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
public MockConnection(KestrelServerOptions options)
{
ConnectionControl = this;
RequestAbortedSource = new CancellationTokenSource();
ServerOptions = options;
}

View File

@ -129,6 +129,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
_uv_timer_init = (loop, handle) => 0;
_uv_timer_start = (handle, callback, timeout, repeat) => 0;
_uv_timer_stop = handle => 0;
_uv_now = (loop) => DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;
}
public Func<UvStreamHandle, int, Action<int>, int> OnWrite { get; set; }

View File

@ -22,7 +22,12 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
var trace = new KestrelTrace(new TestKestrelTrace());
var ltp = new LoggingThreadPool(trace);
var context = new Frame<object>(null, new ConnectionContext() { ServerAddress = new ServerAddress() })
var connectionContext = new ConnectionContext()
{
ServerAddress = new ServerAddress(),
ServerOptions = new KestrelServerOptions()
};
var context = new Frame<object>(null, connectionContext)
{
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
@ -63,7 +68,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{
}
public void Stop()
public void SetTimeout(long milliseconds)
{
}
public void CancelTimeout()
{
}