From ea3e12075b5596be381c5c52c2cea55a7437a3b3 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 12 Jan 2016 23:19:43 +0000 Subject: [PATCH] [Wip] Add timeouts --- .../Http/Frame.FeatureCollection.cs | 7 +- .../Http/Frame.cs | 82 ++++++---- .../Http/FrameOfT.cs | 25 +-- .../Http/FrameState.cs | 146 ++++++++++++++++++ .../Http/RequestState.cs | 22 +++ .../Http/TcpListener.cs | 4 +- .../Http/TcpListenerPrimary.cs | 4 +- .../Http/TcpListenerSecondary.cs | 2 +- .../IKestrelServerInformation.cs | 7 + .../KestrelServer.cs | 24 ++- .../KestrelServerInformation.cs | 27 ++++ .../ServiceContext.cs | 9 +- .../EngineTests.cs | 4 +- .../FrameFacts.cs | 3 +- .../FrameResponseHeadersTests.cs | 6 +- .../TestConfiguration.cs | 41 +++++ .../TestServiceContext.cs | 1 + 17 files changed, 358 insertions(+), 56 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameState.cs create mode 100644 src/Microsoft.AspNetCore.Server.Kestrel/Http/RequestState.cs create mode 100644 test/Microsoft.AspNetCore.Server.KestrelTests/TestConfiguration.cs diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.FeatureCollection.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.FeatureCollection.cs index a1d03fd2b..a7f3d967f 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.FeatureCollection.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.FeatureCollection.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.Kestrel.Http @@ -314,7 +313,11 @@ async Task IHttpUpgradeFeature.UpgradeAsync() await FlushAsync(default(CancellationToken)); - return DuplexStream; + if (_frameState.TransitionToState(RequestState.UpgradedRequest) == RequestState.UpgradedRequest) + { + return DuplexStream; + } + throw new IOException("Failed to upgrade request"); } IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator(); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs index 81aa57bb5..18ba7657b 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs @@ -53,10 +53,8 @@ public abstract partial class Frame : FrameContext, IFrameControl protected List, object>> _onCompleted; - private bool _requestProcessingStarted; + protected FrameState _frameState; private Task _requestProcessingTask; - protected volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx - protected int _requestAborted; protected CancellationTokenSource _abortedCts; protected CancellationToken? _manuallySetRequestAbortToken; @@ -92,13 +90,15 @@ public Frame(ConnectionContext context, _localEndPoint = localEndPoint; _prepareRequest = prepareRequest; _pathBase = context.ServerAddress.PathBase; - if (ReuseStreams) + if (Settings.ReuseStreams) { _requestBody = new FrameRequestStream(); _responseBody = new FrameResponseStream(this); _duplexStream = new FrameDuplexStream(_requestBody, _responseBody); } + _frameState = new FrameState(this, context.Settings); + FrameControl = this; Reset(); } @@ -167,7 +167,7 @@ public CancellationToken RequestAborted var cts = _abortedCts; return cts != null ? cts.Token : - (Volatile.Read(ref _requestAborted) == 1) ? new CancellationToken(true) : + _frameState.CurrentState == RequestState.Aborted ? new CancellationToken(true) : RequestAbortedSource.Token; } set @@ -185,20 +185,26 @@ private CancellationTokenSource RequestAbortedSource // Get the abort token, lazily-initializing it if necessary. // Make sure it's canceled if an abort request already came in. var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource()); - if (Volatile.Read(ref _requestAborted) == 1) + if (_frameState.CurrentState == RequestState.Aborted) { cts.Cancel(); } return cts; } } + public bool HasResponseStarted { get { return _responseStarted; } } - public void Reset() + public bool Reset() { + if (_frameState.TransitionToState(RequestState.Waiting) != RequestState.Waiting) + { + return false; + } + _onStarting = null; _onCompleted = null; @@ -237,6 +243,8 @@ public void Reset() _manuallySetRequestAbortToken = null; _abortedCts = null; + + return true; } public void ResetResponseHeaders() @@ -255,9 +263,8 @@ public void ResetResponseHeaders() /// public void Start() { - if (!_requestProcessingStarted) + if (_frameState.TransitionToState(RequestState.Waiting) == RequestState.Waiting) { - _requestProcessingStarted = true; _requestProcessingTask = Task.Factory.StartNew( (o) => ((Frame)o).RequestProcessingAsync(), @@ -276,10 +283,8 @@ public void Start() /// public Task Stop() { - if (!_requestProcessingStopping) - { - _requestProcessingStopping = true; - } + _frameState.TransitionToState(RequestState.Stopped); + return _requestProcessingTask ?? TaskUtilities.CompletedTask; } @@ -288,10 +293,8 @@ public Task Stop() /// public void Abort() { - if (Interlocked.CompareExchange(ref _requestAborted, 1, 0) == 0) + if (_frameState.TransitionToState(RequestState.Aborted) != RequestState.Aborted) { - _requestProcessingStopping = true; - _requestBody?.Abort(); _responseBody?.Abort(); @@ -304,16 +307,18 @@ public void Abort() { Log.LogError(0, ex, "Abort"); } - - try - { - RequestAbortedSource.Cancel(); - } - catch (Exception ex) + finally { - Log.LogError(0, ex, "Abort"); + try + { + RequestAbortedSource.Cancel(); + } + catch (Exception ex) + { + Log.LogError(0, ex, "Abort"); + } + _abortedCts = null; } - _abortedCts = null; } } @@ -552,7 +557,7 @@ protected Task ProduceEnd() if (_responseStarted) { // We can no longer respond with a 500, so we simply close the connection. - _requestProcessingStopping = true; + _frameState.TransitionToState(RequestState.Stopped); return TaskUtilities.CompletedTask; } else @@ -567,6 +572,20 @@ protected Task ProduceEnd() if (!_responseStarted) { + var frameState = _frameState.CurrentState; + if (frameState > RequestState.Stopping) + { + if (frameState < RequestState.Stopped) + { + // State is status code + StatusCode = frameState; + } + else + { + StatusCode = 500; + } + ReasonPhrase = null; + } return ProduceEndAwaited(); } @@ -678,12 +697,23 @@ protected bool TakeStartLine(SocketInput input) { string method; var begin = scan; - if (!begin.GetKnownMethod(ref scan,out method)) + if (begin.GetKnownMethod(ref scan, out method)) + { + if (_frameState.TransitionToState(RequestState.ReadingHeaders) != RequestState.ReadingHeaders) + { + return false; + } + } + else { if (scan.Seek(ref _vectorSpaces) == -1) { return false; } + if (_frameState.TransitionToState(RequestState.ReadingHeaders) != RequestState.ReadingHeaders) + { + return false; + } method = begin.GetAsciiString(scan); scan.Take(); } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameOfT.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameOfT.cs index 0d9ea1d67..609bfece2 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameOfT.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameOfT.cs @@ -41,9 +41,9 @@ public override async Task RequestProcessingAsync() { try { - while (!_requestProcessingStopping) + while (_frameState.CurrentState == RequestState.Waiting) { - while (!_requestProcessingStopping && !TakeStartLine(SocketInput)) + while (_frameState.CurrentState < RequestState.Stopping && !TakeStartLine(SocketInput)) { if (SocketInput.RemoteIntakeFin) { @@ -52,7 +52,7 @@ public override async Task RequestProcessingAsync() await SocketInput; } - while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders)) + while (_frameState.CurrentState < RequestState.Stopping && !TakeMessageHeaders(SocketInput, _requestHeaders)) { if (SocketInput.RemoteIntakeFin) { @@ -61,13 +61,13 @@ public override async Task RequestProcessingAsync() await SocketInput; } - if (!_requestProcessingStopping) + if (_frameState.TransitionToState(RequestState.ExecutingRequest) == RequestState.ExecutingRequest) { var messageBody = MessageBody.For(HttpVersion, _requestHeaders, this); _keepAlive = messageBody.RequestKeepAlive; // _duplexStream may be null if flag switched while running - if (!ReuseStreams || _duplexStream == null) + if (!Settings.ReuseStreams || _duplexStream == null) { _requestBody = new FrameRequestStream(); _responseBody = new FrameResponseStream(this); @@ -111,8 +111,8 @@ public override async Task RequestProcessingAsync() _application.DisposeContext(context, _applicationException); - // If _requestAbort is set, the connection has already been closed. - if (Volatile.Read(ref _requestAborted) == 0) + // If Aborted, the connection has already been closed. + if (_frameState.CurrentState != RequestState.Aborted) { _responseBody.ResumeAcceptingWrites(); await ProduceEnd(); @@ -135,7 +135,10 @@ public override async Task RequestProcessingAsync() } } - Reset(); + if (!Reset()) + { + return; + } } } catch (Exception ex) @@ -146,10 +149,12 @@ public override async Task RequestProcessingAsync() { try { + var frameState = _frameState.CurrentState; + _frameState.Dispose(); _abortedCts = null; - // If _requestAborted is set, the connection has already been closed. - if (Volatile.Read(ref _requestAborted) == 0) + // If Aborted, the connection has already been closed. + if (frameState != RequestState.Aborted) { // Inform client no more data will ever arrive ConnectionControl.End(ProduceEndType.SocketShutdownSend); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameState.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameState.cs new file mode 100644 index 000000000..af39ed693 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameState.cs @@ -0,0 +1,146 @@ +// 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.Diagnostics; +using System.Threading; + +namespace Microsoft.AspNetCore.Server.Kestrel.Http +{ + public class FrameState : IDisposable + { + private static readonly TimerCallback _timeoutRequest = (o) => ((FrameState)o).TimeoutRequest(); + + private readonly Frame _frame; + private readonly IKestrelServerInformation _settings; + private readonly Timer _timeout; + // enum doesn't work with Interlocked + private int _frameState; + + + public int CurrentState => Volatile.Read(ref _frameState); + + public FrameState(Frame frame, IKestrelServerInformation settings) + { + _frame = frame; + _settings = settings; + _timeout = new Timer(_timeoutRequest, this, Timeout.Infinite, Timeout.Infinite); + _frameState = RequestState.NotStarted; + } + + private void TimeoutRequest() + { + // Don't abort if debugging + if (!Debugger.IsAttached && TransitionToState(RequestState.Timeout) == RequestState.Timeout) + { + _frame.Abort(); + } + } + + public int TransitionToState(int state) + { + int prevState = Volatile.Read(ref _frameState); + + switch (state) + { + case RequestState.Waiting: + return TransitionToWaiting(prevState); + case RequestState.ReadingHeaders: + if (prevState == RequestState.ReadingHeaders) return RequestState.ReadingHeaders; + // can only transition to ReadingHeaders from Waiting + prevState = Interlocked.CompareExchange(ref _frameState, RequestState.ReadingHeaders, RequestState.Waiting); + if (prevState == RequestState.Waiting) + { + // only reset timer on transition into this state + _timeout.Change((int)_settings.HeadersCompleteTimeout.TotalMilliseconds, Timeout.Infinite); + return RequestState.ReadingHeaders; + } + break; + case RequestState.ExecutingRequest: + // can only transition to ExecutingRequest from ReadingHeaders + prevState = Interlocked.CompareExchange(ref _frameState, RequestState.ExecutingRequest, RequestState.ReadingHeaders); + if (prevState == RequestState.ReadingHeaders) + { + // only reset timer if state correct + _timeout.Change((int)_settings.ExecutionTimeout.TotalMilliseconds, Timeout.Infinite); + return RequestState.ExecutingRequest; + } + break; + case RequestState.UpgradedRequest: + // can only transition to UpgradedRequest from ExecutingRequest + prevState = Interlocked.CompareExchange(ref _frameState, RequestState.UpgradedRequest, RequestState.ExecutingRequest); + if (prevState == RequestState.ExecutingRequest) + { + // switch off timer for upgraded request; upgraded pipeline should handle its own timeouts + _timeout.Change(Timeout.Infinite, Timeout.Infinite); + return RequestState.UpgradedRequest; + } + break; + case RequestState.Stopping: + // marker state, can't transition into it. + throw new InvalidOperationException(); + case RequestState.Timeout: + if (prevState >= RequestState.Timeout) return prevState; + // can transition to Timeout from states below it + do + { + prevState = Interlocked.CompareExchange(ref _frameState, RequestState.Timeout, prevState); + } while (prevState < RequestState.Timeout); + return prevState > RequestState.Timeout ? prevState : RequestState.Timeout; + case RequestState.Stopped: + if (prevState >= RequestState.Stopped) return prevState; + // can transition to Stopped from states below it + do + { + prevState = Interlocked.CompareExchange(ref _frameState, RequestState.Stopped, prevState); + } while (prevState < RequestState.Stopped); + return prevState > RequestState.Stopped ? prevState : RequestState.Stopped; + case RequestState.Aborted: + // can transition to Aborted from any state + _frameState = RequestState.Aborted; + return prevState; // return previous state to say if already aborted + } + return prevState; + } + + public int TransitionToWaiting(int prevState) + { + switch (prevState) + { + case RequestState.ExecutingRequest: + prevState = Interlocked.CompareExchange(ref _frameState, RequestState.Waiting, RequestState.ExecutingRequest); + if (prevState == RequestState.ExecutingRequest) + { + // only reset timer on transition into this state + _timeout.Change((int)_settings.KeepAliveTimeout.TotalMilliseconds, Timeout.Infinite); + return RequestState.Waiting; + } + break; + case RequestState.UpgradedRequest: + prevState = Interlocked.CompareExchange(ref _frameState, RequestState.Waiting, RequestState.UpgradedRequest); + if (prevState == RequestState.UpgradedRequest) + { + // only reset timer on transition into this state + _timeout.Change((int)_settings.KeepAliveTimeout.TotalMilliseconds, Timeout.Infinite); + return RequestState.Waiting; + } + break; + case RequestState.NotStarted: + prevState = Interlocked.CompareExchange(ref _frameState, RequestState.Waiting, RequestState.NotStarted); + if (prevState == RequestState.NotStarted) + { + // only reset timer on transition into this state + _timeout.Change((int)_settings.KeepAliveTimeout.TotalMilliseconds, Timeout.Infinite); + return RequestState.Waiting; + } + break; + } + return prevState; + } + + public void Dispose() + { + _timeout.Dispose(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/RequestState.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/RequestState.cs new file mode 100644 index 000000000..bfc75e997 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/RequestState.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Server.Kestrel.Http +{ + // enum, but enum doesn't work with Interlocked + class RequestState + { + public const int NotStarted = -1; + public const int Waiting = 0; + public const int ReadingHeaders = 1; + public const int ExecutingRequest = 2; + public const int UpgradedRequest = 3; + // Do not change order of these with out changing comparision tests + public const int Stopping = 99; + // States are status codes + public const int Timeout = 408; + // Other final states + public const int Stopped = 1000; + public const int Aborted = 1001; + } +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListener.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListener.cs index 4c0f8f19b..0b4620da4 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListener.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListener.cs @@ -23,7 +23,7 @@ protected override UvStreamHandle CreateListenSocket() { var socket = new UvTcpHandle(Log); socket.Init(Thread.Loop, Thread.QueueCloseHandle); - socket.NoDelay(NoDelay); + socket.NoDelay(Settings.NoDelay); socket.Bind(ServerAddress); socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this); return socket; @@ -41,7 +41,7 @@ protected override void OnConnection(UvStreamHandle listenSocket, int status) try { acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle); - acceptSocket.NoDelay(NoDelay); + acceptSocket.NoDelay(Settings.NoDelay); listenSocket.Accept(acceptSocket); DispatchConnection(acceptSocket); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListenerPrimary.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListenerPrimary.cs index 6c1697cfe..1c8c7afa0 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListenerPrimary.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListenerPrimary.cs @@ -25,7 +25,7 @@ protected override UvStreamHandle CreateListenSocket() { var socket = new UvTcpHandle(Log); socket.Init(Thread.Loop, Thread.QueueCloseHandle); - socket.NoDelay(NoDelay); + socket.NoDelay(Settings.NoDelay); socket.Bind(ServerAddress); socket.Listen(Constants.ListenBacklog, (stream, status, error, state) => ConnectionCallback(stream, status, error, state), this); return socket; @@ -43,7 +43,7 @@ protected override void OnConnection(UvStreamHandle listenSocket, int status) try { acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle); - acceptSocket.NoDelay(NoDelay); + acceptSocket.NoDelay(Settings.NoDelay); listenSocket.Accept(acceptSocket); DispatchConnection(acceptSocket); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListenerSecondary.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListenerSecondary.cs index ce0616171..f01b48cad 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListenerSecondary.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/TcpListenerSecondary.cs @@ -21,7 +21,7 @@ protected override UvStreamHandle CreateAcceptSocket() { var acceptSocket = new UvTcpHandle(Log); acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle); - acceptSocket.NoDelay(NoDelay); + acceptSocket.NoDelay(Settings.NoDelay); return acceptSocket; } } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/IKestrelServerInformation.cs b/src/Microsoft.AspNetCore.Server.Kestrel/IKestrelServerInformation.cs index a08db08ea..576420fb1 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/IKestrelServerInformation.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/IKestrelServerInformation.cs @@ -1,12 +1,19 @@ // 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 Microsoft.AspNetCore.Server.Kestrel.Filter; namespace Microsoft.AspNetCore.Server.Kestrel { public interface IKestrelServerInformation { + TimeSpan ExecutionTimeout { get; set; } + + TimeSpan HeadersCompleteTimeout { get; set; } + + TimeSpan KeepAliveTimeout { get; set; } + int ThreadCount { get; set; } bool NoDelay { get; set; } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs index fed5bea66..b9b0a7fcb 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs @@ -67,8 +67,7 @@ public void Start(IHttpApplication application) ThreadPool = new LoggingThreadPool(trace), DateHeaderValueManager = dateHeaderValueManager, ConnectionFilter = information.ConnectionFilter, - NoDelay = information.NoDelay, - ReuseStreams = information.ReuseStreams + Settings = information }); _disposables.Push(engine); @@ -83,6 +82,27 @@ public void Start(IHttpApplication application) "ThreadCount must be positive."); } + if (information.KeepAliveTimeout.TotalSeconds < 1) + { + throw new ArgumentOutOfRangeException(nameof(information.KeepAliveTimeout), + information.KeepAliveTimeout, + "KeepAliveTimeout must be greater than zero."); + } + + if (information.HeadersCompleteTimeout.TotalSeconds < 1) + { + throw new ArgumentOutOfRangeException(nameof(information.HeadersCompleteTimeout), + information.HeadersCompleteTimeout, + "HeadersCompleteTimeout must be greater than zero."); + } + + if (information.ExecutionTimeout.TotalSeconds < 1) + { + throw new ArgumentOutOfRangeException(nameof(information.ExecutionTimeout), + information.ExecutionTimeout, + "ExecutionTimeout must be greater than zero."); + } + engine.Start(threadCount); var atLeastOneListener = false; diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServerInformation.cs b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServerInformation.cs index 6f05a1da5..6753496fa 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServerInformation.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServerInformation.cs @@ -23,10 +23,19 @@ public KestrelServerInformation(IConfiguration configuration) ThreadCount = GetThreadCount(configuration); NoDelay = GetNoDelay(configuration); ReuseStreams = GetReuseStreams(configuration); + HeadersCompleteTimeout = GetTimeout(configuration, "kestrel.headersCompleteTimeout", defaultSeconds: 30); + ExecutionTimeout = GetTimeout(configuration, "server.executionTimeout", defaultSeconds: 110); + KeepAliveTimeout = GetTimeout(configuration, "server.keepAliveTimeout", defaultSeconds: 160); } public ICollection Addresses { get; } + public TimeSpan ExecutionTimeout { get; set; } + + public TimeSpan HeadersCompleteTimeout { get; set; } + + public TimeSpan KeepAliveTimeout { get; set; } + public int ThreadCount { get; set; } public bool NoDelay { get; set; } @@ -123,5 +132,23 @@ private static bool GetReuseStreams(IConfiguration configuration) return false; } + + private static TimeSpan GetTimeout(IConfiguration configuration, string configurationKey, int defaultSeconds) + { + var timeoutString = configuration[configurationKey]; + + if (string.IsNullOrEmpty(timeoutString)) + { + return TimeSpan.FromSeconds(defaultSeconds); + } + + TimeSpan timeout; + if (TimeSpan.TryParse(timeoutString, out timeout)) + { + return timeout; + } + + return TimeSpan.FromSeconds(defaultSeconds); + } } } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/ServiceContext.cs b/src/Microsoft.AspNetCore.Server.Kestrel/ServiceContext.cs index a299ff946..48a542841 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/ServiceContext.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/ServiceContext.cs @@ -25,14 +25,15 @@ public ServiceContext(ServiceContext context) FrameFactory = context.FrameFactory; DateHeaderValueManager = context.DateHeaderValueManager; ConnectionFilter = context.ConnectionFilter; - NoDelay = context.NoDelay; - ReuseStreams = context.ReuseStreams; + Settings = context.Settings; } public IApplicationLifetime AppLifetime { get; set; } public IKestrelTrace Log { get; set; } + public IKestrelServerInformation Settings { get; set; } + public IThreadPool ThreadPool { get; set; } public Func, Frame> FrameFactory { get; set; } @@ -40,9 +41,5 @@ public ServiceContext(ServiceContext context) public DateHeaderValueManager DateHeaderValueManager { get; set; } public IConnectionFilter ConnectionFilter { get; set; } - - public bool NoDelay { get; set; } - - public bool ReuseStreams { get; set; } } } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs index 95e9d304b..ad29e61e5 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs @@ -187,7 +187,7 @@ await connection.ReceiveEnd( [MemberData(nameof(ConnectionFilterData))] public async Task ReuseStreamsOn(ServiceContext testContext) { - testContext.ReuseStreams = true; + testContext.Settings.ReuseStreams = true; var streamCount = 0; var loopCount = 20; @@ -230,7 +230,7 @@ public async Task ReuseStreamsOn(ServiceContext testContext) [MemberData(nameof(ConnectionFilterData))] public async Task ReuseStreamsOff(ServiceContext testContext) { - testContext.ReuseStreams = false; + testContext.Settings.ReuseStreams = false; var streamCount = 0; var loopCount = 20; diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameFacts.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameFacts.cs index deefd5511..37dde9cc7 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameFacts.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameFacts.cs @@ -17,7 +17,8 @@ public void ResetResetsScheme() var connectionContext = new ConnectionContext() { DateHeaderValueManager = new DateHeaderValueManager(), - ServerAddress = ServerAddress.FromUrl("http://localhost:5000") + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + Settings = new KestrelServerInformation(new TestConfiguration()) }; var frame = new Frame(application: null, context: connectionContext); frame.Scheme = "https"; diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameResponseHeadersTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameResponseHeadersTests.cs index 5fa4f6bec..bb9a7affb 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameResponseHeadersTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameResponseHeadersTests.cs @@ -18,7 +18,8 @@ public void InitialDictionaryContainsServerAndDate() var connectionContext = new ConnectionContext { DateHeaderValueManager = new DateHeaderValueManager(), - ServerAddress = ServerAddress.FromUrl("http://localhost:5000") + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + Settings = new KestrelServerInformation(new TestConfiguration()) }; var frame = new Frame(application: null, context: connectionContext); IDictionary headers = frame.ResponseHeaders; @@ -46,7 +47,8 @@ public void InitialEntriesCanBeCleared() var connectionContext = new ConnectionContext { DateHeaderValueManager = new DateHeaderValueManager(), - ServerAddress = ServerAddress.FromUrl("http://localhost:5000") + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + Settings = new KestrelServerInformation(new TestConfiguration()) }; var frame = new Frame(application: null, context: connectionContext); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestConfiguration.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestConfiguration.cs new file mode 100644 index 000000000..494f198c3 --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/TestConfiguration.cs @@ -0,0 +1,41 @@ +// 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.Generic; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Server.KestrelTests +{ + public class TestConfiguration : IConfiguration + { + public string this[string key] + { + get + { + return string.Empty; + } + + set + { + throw new NotImplementedException(); + } + } + + public IEnumerable GetChildren() + { + throw new NotImplementedException(); + } + + public IChangeToken GetReloadToken() + { + throw new NotImplementedException(); + } + + public IConfigurationSection GetSection(string key) + { + throw new NotImplementedException(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs index 4224c776d..c5bc8c98f 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs @@ -18,6 +18,7 @@ public TestServiceContext() Log = new TestKestrelTrace(); ThreadPool = new LoggingThreadPool(Log); DateHeaderValueManager = new TestDateHeaderValueManager(); + Settings = new KestrelServerInformation(new TestConfiguration()); } public RequestDelegate App