diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnection.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnection.cs index 7ae2f4c36a..6a0364f1f3 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnection.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnection.cs @@ -44,6 +44,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal // For testing internal Frame Frame => _frame; + internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton; + public bool TimedOut { get; private set; } @@ -263,14 +265,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal // TODO: Use PlatformApis.VolatileRead equivalent again if (timestamp > Interlocked.Read(ref _timeoutTimestamp)) { - CancelTimeout(); - - if (_timeoutAction == TimeoutAction.SendTimeoutResponse) + if (!Debugger.IsAttached) { - SetTimeoutResponse(); - } + CancelTimeout(); - Timeout(); + if (_timeoutAction == TimeoutAction.SendTimeoutResponse) + { + SetTimeoutResponse(); + } + + Timeout(); + } } else { @@ -285,7 +290,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal var elapsedSeconds = (double)_readTimingElapsedTicks / TimeSpan.TicksPerSecond; var rate = Interlocked.Read(ref _readTimingBytesRead) / elapsedSeconds; - if (rate < _frame.RequestBodyMinimumDataRate.Rate) + if (rate < _frame.RequestBodyMinimumDataRate.Rate && !Debugger.IsAttached) { Log.RequestBodyMininumDataRateNotSatisfied(_context.ConnectionId, _frame.TraceIdentifier, _frame.RequestBodyMinimumDataRate.Rate); Timeout(); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnectionContext.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnectionContext.cs index 165daf2ad5..9f01593810 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnectionContext.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/FrameConnectionContext.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/DebuggerWrapper.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/DebuggerWrapper.cs new file mode 100644 index 0000000000..df2b2644d9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/DebuggerWrapper.cs @@ -0,0 +1,17 @@ +// 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.Diagnostics; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure +{ + internal sealed class DebuggerWrapper : IDebugger + { + private DebuggerWrapper() + { } + + public static IDebugger Singleton { get; } = new DebuggerWrapper(); + + public bool IsAttached => Debugger.IsAttached; + } +} diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/IDebugger.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/IDebugger.cs new file mode 100644 index 0000000000..cb1448fd4f --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/Internal/Infrastructure/IDebugger.cs @@ -0,0 +1,10 @@ +// 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.Core.Internal.Infrastructure +{ + public interface IDebugger + { + bool IsAttached { get; } + } +} diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameConnectionTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameConnectionTests.cs index 9596ef625f..c28f731ae4 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameConnectionTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests/FrameConnectionTests.cs @@ -50,19 +50,60 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _pipeFactory.Dispose(); } + [Fact] + public void DoesNotTimeOutWhenDebuggerIsAttached() + { + var mockDebugger = new Mock(); + mockDebugger.SetupGet(g => g.IsAttached).Returns(true); + _frameConnection.Debugger = mockDebugger.Object; + _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Input.Reader, _frameConnectionContext.Output); + + var now = DateTimeOffset.Now; + _frameConnection.Tick(now); + _frameConnection.SetTimeout(1, TimeoutAction.SendTimeoutResponse); + _frameConnection.Tick(now.AddTicks(2).Add(Heartbeat.Interval)); + + Assert.False(_frameConnection.TimedOut); + } + + [Fact] + public void DoesNotTimeOutWhenRequestBodyDoesNotSatisfyMinimumDataRateButDebuggerIsAttached() + { + var mockDebugger = new Mock(); + mockDebugger.SetupGet(g => g.IsAttached).Returns(true); + _frameConnection.Debugger = mockDebugger.Object; + var requestBodyMinimumDataRate = 100; + var mockLogger = new Mock(); + mockLogger.Setup(l => l.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), It.IsAny())).Throws(new InvalidOperationException("Should not log")); + + TickBodyWithMinimumDataRate(mockLogger.Object, requestBodyMinimumDataRate); + + Assert.False(_frameConnection.TimedOut); + } + [Fact] public void TimesOutWhenRequestBodyDoesNotSatisfyMinimumDataRate() { var requestBodyMinimumDataRate = 100; + var mockLogger = new Mock(); + TickBodyWithMinimumDataRate(mockLogger.Object, requestBodyMinimumDataRate); + + // Timed out + Assert.True(_frameConnection.TimedOut); + mockLogger.Verify(logger => + logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), requestBodyMinimumDataRate), Times.Once); + } + + private void TickBodyWithMinimumDataRate(IKestrelTrace logger, int requestBodyMinimumDataRate) + { var requestBodyGracePeriod = TimeSpan.FromSeconds(5); _frameConnectionContext.ServiceContext.ServerOptions.Limits.RequestBodyMinimumDataRate = new MinimumDataRate(rate: requestBodyMinimumDataRate, gracePeriod: requestBodyGracePeriod); - var mockLogger = new Mock(); - _frameConnectionContext.ServiceContext.Log = mockLogger.Object; + _frameConnectionContext.ServiceContext.Log = logger; - _frameConnection.CreateFrame(new DummyApplication(context => Task.CompletedTask), _frameConnectionContext.Input.Reader, _frameConnectionContext.Output); + _frameConnection.CreateFrame(new DummyApplication(), _frameConnectionContext.Input.Reader, _frameConnectionContext.Output); _frameConnection.Frame.Reset(); // Initialize timestamp @@ -75,11 +116,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests now += requestBodyGracePeriod + TimeSpan.FromSeconds(1); _frameConnection.BytesRead(1); _frameConnection.Tick(now); - - // Timed out - Assert.True(_frameConnection.TimedOut); - mockLogger.Verify(logger => - logger.RequestBodyMininumDataRateNotSatisfied(It.IsAny(), It.IsAny(), requestBodyMinimumDataRate), Times.Once); } [Fact] diff --git a/test/shared/DummyApplication.cs b/test/shared/DummyApplication.cs index e944bf3562..389a799b2d 100644 --- a/test/shared/DummyApplication.cs +++ b/test/shared/DummyApplication.cs @@ -14,6 +14,11 @@ namespace Microsoft.AspNetCore.Testing private readonly RequestDelegate _requestDelegate; private readonly IHttpContextFactory _httpContextFactory; + public DummyApplication() + : this(_ => Task.CompletedTask) + { + } + public DummyApplication(RequestDelegate requestDelegate) : this(requestDelegate, null) {