From bd9d35abbbd1061f142315a77932b43cfcd735c5 Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 4 May 2023 14:37:30 -0700 Subject: [PATCH 01/17] Replace ISystemClock with TimeProvider in Kestrel --- .../src/Internal/Http2/Http2Connection.cs | 10 +- .../Core/src/Internal/Http2/Http2KeepAlive.cs | 11 +- .../Core/src/Internal/Http3/Http3Stream.cs | 2 +- .../Core/src/Internal/HttpConnection.cs | 8 +- .../src/Internal/Infrastructure/Heartbeat.cs | 10 +- .../Infrastructure/HeartbeatManager.cs | 10 +- .../Internal/Infrastructure/ISystemClock.cs | 26 ---- .../Internal/Infrastructure/SystemClock.cs | 25 ---- .../Core/src/Internal/KestrelServerImpl.cs | 4 +- .../Core/src/Internal/ServiceContext.cs | 2 +- .../Core/test/DateHeaderValueManagerTests.cs | 33 ++---- .../Kestrel/Core/test/HeartbeatTests.cs | 28 ++--- .../Kestrel/Core/test/KestrelServerTests.cs | 6 +- .../Kestrel/Core/test/TimeoutControlTests.cs | 112 +++++++++--------- .../src/Internal/ISystemClock.cs | 20 ---- .../src/Internal/QuicConnectionContext.cs | 4 +- .../src/QuicTransportOptions.cs | 3 +- .../test/QuicConnectionContextTests.cs | 29 +++-- .../Transport.Quic/test/QuicTestHelpers.cs | 14 +-- .../Http2/Http2ConnectionBenchmarkBase.cs | 2 +- .../Http3/Http3ConnectionBenchmarkBase.cs | 8 +- ...Core.Server.Kestrel.Microbenchmarks.csproj | 2 +- .../Microbenchmarks/Mocks/MockSystemClock.cs | 14 --- .../shared/test/Http3/Http3InMemory.cs | 26 ++-- ...MockSystemClock.cs => MockTimeProvider.cs} | 38 +++--- .../Kestrel/shared/test/TestContextFactory.cs | 8 +- .../Kestrel/shared/test/TestServiceContext.cs | 14 +-- .../Http2/Http2ConnectionTests.cs | 6 +- .../Http2/Http2KeepAliveTests.cs | 20 ++-- .../Http2/Http2TestBase.cs | 16 +-- .../Http2/Http2TimeoutTests.cs | 72 +++++------ .../Http2/Http2WebSocketTests.cs | 2 +- .../Http3/Http3ConnectionTests.cs | 4 +- .../Http3/Http3TestBase.cs | 2 +- .../Http3/Http3TimeoutTests.cs | 94 +++++++-------- .../KeepAliveTimeoutTests.cs | 24 ++-- .../RequestBodyTimeoutTests.cs | 24 ++-- .../RequestHeadersTimeoutTests.cs | 16 +-- .../InMemory.FunctionalTests/RequestTests.cs | 4 +- .../ResponseDrainingTests.cs | 12 +- .../Sockets.BindTests.csproj | 2 +- .../Sockets.FunctionalTests.csproj | 2 +- 42 files changed, 340 insertions(+), 429 deletions(-) delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/ISystemClock.cs delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/SystemClock.cs delete mode 100644 src/Servers/Kestrel/Transport.Quic/src/Internal/ISystemClock.cs delete mode 100644 src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockSystemClock.cs rename src/Servers/Kestrel/shared/test/{MockSystemClock.cs => MockTimeProvider.cs} (57%) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 846e751870ca..4006062071df 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -111,7 +111,7 @@ public Http2Connection(HttpConnectionContext context) _keepAlive = new Http2KeepAlive( http2Limits.KeepAlivePingDelay, http2Limits.KeepAlivePingTimeout, - context.ServiceContext.SystemClock); + context.ServiceContext.TimeProvider); } _serverSettings.MaxConcurrentStreams = (uint)http2Limits.MaxStreamsPerConnection; @@ -145,7 +145,7 @@ public Http2Connection(HttpConnectionContext context) public KestrelTrace Log => _context.ServiceContext.Log; public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures; - public ISystemClock SystemClock => _context.ServiceContext.SystemClock; + public TimeProvider TimeProvider => _context.ServiceContext.TimeProvider; public ITimeoutControl TimeoutControl => _context.TimeoutControl; public KestrelServerLimits Limits => _context.ServiceContext.ServerOptions.Limits; @@ -240,7 +240,7 @@ public async Task ProcessRequestsAsync(IHttpApplication appl if (result.IsCanceled) { // Heartbeat will cancel ReadAsync and trigger expiring unused streams from pool. - StreamPool.RemoveExpired(SystemClock.UtcNowTicks); + StreamPool.RemoveExpired(TimeProvider.GetUtcNow().Ticks); } try @@ -1255,7 +1255,7 @@ void IHttp2StreamLifetimeHandler.OnStreamCompleted(Http2Stream stream) private void UpdateCompletedStreams() { Http2Stream? firstRequedStream = null; - var now = SystemClock.UtcNowTicks; + var now = TimeProvider.GetUtcNow().Ticks; while (_completedStreams.TryDequeue(out var stream)) { @@ -1304,7 +1304,7 @@ private void RemoveStream(Http2Stream stream) // Pool and reuse the stream if it finished in a graceful state and there is space in the pool. // This property is used to remove unused streams from the pool - stream.DrainExpirationTicks = SystemClock.UtcNowTicks + StreamPoolExpiryTicks; + stream.DrainExpirationTicks = TimeProvider.GetUtcNow().Ticks + StreamPoolExpiryTicks; StreamPool.Push(stream); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs index 03ae6468568d..7dab4259ed84 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; @@ -21,23 +20,23 @@ internal sealed class Http2KeepAlive private readonly TimeSpan _keepAliveInterval; private readonly TimeSpan _keepAliveTimeout; - private readonly ISystemClock _systemClock; + private readonly TimeProvider _timeProvider; private long _lastFrameReceivedTimestamp; private long _pingSentTimestamp; // Internal for testing internal KeepAliveState _state; - public Http2KeepAlive(TimeSpan keepAliveInterval, TimeSpan keepAliveTimeout, ISystemClock systemClock) + public Http2KeepAlive(TimeSpan keepAliveInterval, TimeSpan keepAliveTimeout, TimeProvider timeProvider) { _keepAliveInterval = keepAliveInterval; _keepAliveTimeout = keepAliveTimeout; - _systemClock = systemClock; + _timeProvider = timeProvider; } public KeepAliveState ProcessKeepAlive(bool frameReceived) { - var timestamp = _systemClock.UtcNowTicks; + var timestamp = _timeProvider.GetUtcNow().Ticks; if (frameReceived) { @@ -65,7 +64,7 @@ public KeepAliveState ProcessKeepAlive(bool frameReceived) _state = KeepAliveState.PingSent; // System clock only has 1 second of precision, so the clock could be up to 1 second in the past. // To err on the side of caution, add a second to the clock when calculating the ping sent time. - _pingSentTimestamp = _systemClock.UtcNowTicks + TimeSpan.TicksPerSecond; + _pingSentTimestamp = _timeProvider.GetUtcNow().Ticks + TimeSpan.TicksPerSecond; // Indicate that the ping needs to be sent. This is only returned once return KeepAliveState.SendPing; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 87a99b21304c..aed761bd6b05 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -70,7 +70,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS public QPackDecoder QPackDecoder { get; private set; } = default!; public PipeReader Input => _context.Transport.Input; - public ISystemClock SystemClock => _context.ServiceContext.SystemClock; + public TimeProvider TimeProvider => _context.ServiceContext.TimeProvider; public KestrelServerLimits Limits => _context.ServiceContext.ServerOptions.Limits; public long StreamId => _streamIdFeature.StreamId; public long StreamTimeoutTicks { get; set; } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 89a28d23e494..29bd16c41cb3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -21,7 +21,7 @@ internal sealed class HttpConnection : ITimeoutHandler private static ReadOnlySpan Http2Id => "h2"u8; private readonly BaseHttpConnectionContext _context; - private readonly ISystemClock _systemClock; + private readonly TimeProvider _timeProvider; private readonly TimeoutControl _timeoutControl; private readonly object _protocolSelectionLock = new object(); @@ -34,7 +34,7 @@ internal sealed class HttpConnection : ITimeoutHandler public HttpConnection(BaseHttpConnectionContext context) { _context = context; - _systemClock = _context.ServiceContext.SystemClock; + _timeProvider = _context.ServiceContext.TimeProvider; _timeoutControl = new TimeoutControl(this); @@ -49,7 +49,7 @@ public async Task ProcessRequestsAsync(IHttpApplication http try { // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. - _timeoutControl.Initialize(_systemClock.UtcNowTicks); + _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); IRequestProcessor? requestProcessor = null; @@ -237,7 +237,7 @@ private void Tick() } // It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat. - var now = _systemClock.UtcNowUnsynchronized; + var now = _timeProvider.GetUtcNow(); _timeoutControl.Tick(now); _requestProcessor!.Tick(now); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs index 0895a760e384..61829a93f499 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs @@ -11,17 +11,17 @@ internal sealed class Heartbeat : IDisposable public static readonly TimeSpan Interval = TimeSpan.FromSeconds(1); private readonly IHeartbeatHandler[] _callbacks; - private readonly ISystemClock _systemClock; + private readonly TimeProvider _timeProvider; private readonly IDebugger _debugger; private readonly KestrelTrace _trace; private readonly TimeSpan _interval; private readonly Thread _timerThread; private readonly ManualResetEventSlim _stopEvent; - public Heartbeat(IHeartbeatHandler[] callbacks, ISystemClock systemClock, IDebugger debugger, KestrelTrace trace, TimeSpan interval) + public Heartbeat(IHeartbeatHandler[] callbacks, TimeProvider timeProvider, IDebugger debugger, KestrelTrace trace, TimeSpan interval) { _callbacks = callbacks; - _systemClock = systemClock; + _timeProvider = timeProvider; _debugger = debugger; _trace = trace; _interval = interval; @@ -42,7 +42,7 @@ public void Start() internal void OnHeartbeat() { - var now = _systemClock.UtcNow; + var now = _timeProvider.GetUtcNow(); try { @@ -53,7 +53,7 @@ internal void OnHeartbeat() if (!_debugger.IsAttached) { - var after = _systemClock.UtcNow; + var after = _timeProvider.GetUtcNow(); var duration = TimeSpan.FromTicks(after.Ticks - now.Ticks); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs index 27239e5b403c..a502926f9efc 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs @@ -3,11 +3,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -internal sealed class HeartbeatManager : IHeartbeatHandler, ISystemClock +internal sealed class HeartbeatManager : TimeProvider, IHeartbeatHandler { private readonly ConnectionManager _connectionManager; private readonly Action _walkCallback; - private DateTimeOffset _now; private long _nowTicks; public HeartbeatManager(ConnectionManager connectionManager) @@ -16,15 +15,12 @@ public HeartbeatManager(ConnectionManager connectionManager) _walkCallback = WalkCallback; } - public DateTimeOffset UtcNow => new DateTimeOffset(UtcNowTicks, TimeSpan.Zero); + public override DateTimeOffset GetUtcNow() => new(GetTimestamp(), TimeSpan.Zero); - public long UtcNowTicks => Volatile.Read(ref _nowTicks); - - public DateTimeOffset UtcNowUnsynchronized => _now; + public override long GetTimestamp() => Volatile.Read(ref _nowTicks); public void OnHeartbeat(DateTimeOffset now) { - _now = now; Volatile.Write(ref _nowTicks, now.Ticks); _connectionManager.Walk(_walkCallback); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ISystemClock.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ISystemClock.cs deleted file mode 100644 index 24985259f316..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ISystemClock.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; - -/// -/// Abstracts the system clock to facilitate testing. -/// -internal interface ISystemClock -{ - /// - /// Retrieves the current UTC system time. - /// - DateTimeOffset UtcNow { get; } - - /// - /// Retrieves ticks for the current UTC system time. - /// - long UtcNowTicks { get; } - - /// - /// Retrieves the current UTC system time. - /// This is only safe to use from code called by the . - /// - DateTimeOffset UtcNowUnsynchronized { get; } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/SystemClock.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/SystemClock.cs deleted file mode 100644 index 662038772a9f..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/SystemClock.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; - -/// -/// Provides access to the normal system clock. -/// -internal sealed class SystemClock : ISystemClock -{ - /// - /// Retrieves the current UTC system time. - /// - public DateTimeOffset UtcNow => DateTimeOffset.UtcNow; - - /// - /// Retrieves ticks for the current UTC system time. - /// - public long UtcNowTicks => DateTimeOffset.UtcNow.Ticks; - - /// - /// Retrieves the current UTC system time. - /// - public DateTimeOffset UtcNowUnsynchronized => DateTimeOffset.UtcNow; -} diff --git a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs index c6f2e2b3a0ec..75171aaf698a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs @@ -88,7 +88,7 @@ private static ServiceContext CreateServiceContext(IOptions(trace.IsEnabled(LogLevel.Information), serverOptions.DisableHttp1LineFeedTerminators), - SystemClock = heartbeatManager, + TimeProvider = heartbeatManager, DateHeaderValueManager = dateHeaderValueManager, ConnectionManager = connectionManager, Heartbeat = heartbeat, diff --git a/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs b/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs index fb7021187c76..66421c4b519e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ServiceContext.cs @@ -21,7 +21,7 @@ internal class ServiceContext public IHttpParser HttpParser { get; set; } = default!; - public ISystemClock SystemClock { get; set; } = default!; + public TimeProvider TimeProvider { get; set; } = default!; public DateHeaderValueManager DateHeaderValueManager { get; set; } = default!; diff --git a/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs b/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs index 9c39a580d756..53b66dd37c12 100644 --- a/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs +++ b/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs @@ -1,13 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Abstractions; using Moq; -using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; @@ -37,24 +35,21 @@ public void GetDateHeaderValue_ReturnsCachedValueBetweenTimerTicks() { var now = DateTimeOffset.UtcNow; var future = now.AddSeconds(10); - var systemClock = new MockSystemClock - { - UtcNow = now - }; + var timeProvider = new MockTimeProvider(now); var dateHeaderValueManager = new DateHeaderValueManager(); dateHeaderValueManager.OnHeartbeat(now); var testKestrelTrace = new KestrelTrace(NullLoggerFactory.Instance); - using (var heartbeat = new Heartbeat(new IHeartbeatHandler[] { dateHeaderValueManager }, systemClock, DebuggerWrapper.Singleton, testKestrelTrace, Heartbeat.Interval)) + using (var heartbeat = new Heartbeat(new IHeartbeatHandler[] { dateHeaderValueManager }, timeProvider, DebuggerWrapper.Singleton, testKestrelTrace, Heartbeat.Interval)) { Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); - systemClock.UtcNow = future; + timeProvider.SetUtcNow(future); Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); } - Assert.Equal(0, systemClock.UtcNowCalled); + Assert.Equal(0, timeProvider.UtcNowCalled); } [Fact] @@ -62,10 +57,7 @@ public void GetDateHeaderValue_ReturnsUpdatedValueAfterHeartbeat() { var now = DateTimeOffset.UtcNow; var future = now.AddSeconds(10); - var systemClock = new MockSystemClock - { - UtcNow = now - }; + var timeProvider = new MockTimeProvider(now); var dateHeaderValueManager = new DateHeaderValueManager(); dateHeaderValueManager.OnHeartbeat(now); @@ -74,19 +66,19 @@ public void GetDateHeaderValue_ReturnsUpdatedValueAfterHeartbeat() var mockHeartbeatHandler = new Mock(); - using (var heartbeat = new Heartbeat(new[] { dateHeaderValueManager, mockHeartbeatHandler.Object }, systemClock, DebuggerWrapper.Singleton, testKestrelTrace, Heartbeat.Interval)) + using (var heartbeat = new Heartbeat(new[] { dateHeaderValueManager, mockHeartbeatHandler.Object }, timeProvider, DebuggerWrapper.Singleton, testKestrelTrace, Heartbeat.Interval)) { heartbeat.OnHeartbeat(); Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); // Wait for the next heartbeat before verifying GetDateHeaderValues picks up new time. - systemClock.UtcNow = future; + timeProvider.SetUtcNow(future); heartbeat.OnHeartbeat(); Assert.Equal(future.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); - Assert.Equal(4, systemClock.UtcNowCalled); + Assert.Equal(4, timeProvider.UtcNowCalled); } } @@ -95,23 +87,20 @@ public void GetDateHeaderValue_ReturnsLastDateValueAfterHeartbeatDisposed() { var now = DateTimeOffset.UtcNow; var future = now.AddSeconds(10); - var systemClock = new MockSystemClock - { - UtcNow = now - }; + var timeProvider = new MockTimeProvider(now); var dateHeaderValueManager = new DateHeaderValueManager(); dateHeaderValueManager.OnHeartbeat(now); var testKestrelTrace = new KestrelTrace(NullLoggerFactory.Instance); - using (var heartbeat = new Heartbeat(new IHeartbeatHandler[] { dateHeaderValueManager }, systemClock, DebuggerWrapper.Singleton, testKestrelTrace, Heartbeat.Interval)) + using (var heartbeat = new Heartbeat(new IHeartbeatHandler[] { dateHeaderValueManager }, timeProvider, DebuggerWrapper.Singleton, testKestrelTrace, Heartbeat.Interval)) { heartbeat.OnHeartbeat(); Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); } - systemClock.UtcNow = future; + timeProvider.SetUtcNow(future); Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); } } diff --git a/src/Servers/Kestrel/Core/test/HeartbeatTests.cs b/src/Servers/Kestrel/Core/test/HeartbeatTests.cs index 07f5416c3a01..1daa4f0b60b5 100644 --- a/src/Servers/Kestrel/Core/test/HeartbeatTests.cs +++ b/src/Servers/Kestrel/Core/test/HeartbeatTests.cs @@ -28,11 +28,11 @@ public async void HeartbeatLoopRunsWithSpecifiedInterval() { var heartbeatCallCount = 0; var tcs = new TaskCompletionSource(); - var systemClock = new MockSystemClock(); + var timeProvider = new MockTimeProvider(); var heartbeatHandler = new Mock(); var debugger = new Mock(); var kestrelTrace = new KestrelTrace(LoggerFactory); - var now = systemClock.UtcNow; + var now = timeProvider.GetUtcNow(); var splits = new List(); Stopwatch sw = null; @@ -62,7 +62,7 @@ public async void HeartbeatLoopRunsWithSpecifiedInterval() var intervalMs = 300; - using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, systemClock, debugger.Object, kestrelTrace, TimeSpan.FromMilliseconds(intervalMs))) + using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, timeProvider, debugger.Object, kestrelTrace, TimeSpan.FromMilliseconds(intervalMs))) { heartbeat.Start(); @@ -99,13 +99,13 @@ static void AssertApproxEqual(double intervalMs, double actualMs) [Fact] public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsWarning() { - var systemClock = new MockSystemClock(); + var timeProvider = new MockTimeProvider(); var heartbeatHandler = new Mock(); var debugger = new Mock(); var kestrelTrace = new KestrelTrace(LoggerFactory); var handlerMre = new ManualResetEventSlim(); var handlerStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var now = systemClock.UtcNow; + var now = timeProvider.GetUtcNow(); var heartbeatDuration = TimeSpan.FromSeconds(2); heartbeatHandler.Setup(h => h.OnHeartbeat(now)).Callback(() => @@ -117,7 +117,7 @@ public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsWarning() Task blockedHeartbeatTask; - using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, systemClock, debugger.Object, kestrelTrace, Heartbeat.Interval)) + using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, timeProvider, debugger.Object, kestrelTrace, Heartbeat.Interval)) { blockedHeartbeatTask = Task.Run(() => heartbeat.OnHeartbeat()); @@ -125,7 +125,7 @@ public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsWarning() } // 2 seconds passes... - systemClock.UtcNow = systemClock.UtcNow.AddSeconds(2); + timeProvider.Advance(TimeSpan.FromSeconds(2)); handlerMre.Set(); @@ -143,13 +143,13 @@ public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsWarning() [Fact] public async Task HeartbeatTakingLongerThanIntervalIsNotLoggedIfDebuggerAttached() { - var systemClock = new MockSystemClock(); + var timeProvider = new MockTimeProvider(); var heartbeatHandler = new Mock(); var debugger = new Mock(); var kestrelTrace = new KestrelTrace(LoggerFactory); var handlerMre = new ManualResetEventSlim(); var handlerStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var now = systemClock.UtcNow; + var now = timeProvider.GetUtcNow(); heartbeatHandler.Setup(h => h.OnHeartbeat(now)).Callback(() => { @@ -161,7 +161,7 @@ public async Task HeartbeatTakingLongerThanIntervalIsNotLoggedIfDebuggerAttached Task blockedHeartbeatTask; - using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, systemClock, debugger.Object, kestrelTrace, Heartbeat.Interval)) + using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, timeProvider, debugger.Object, kestrelTrace, Heartbeat.Interval)) { blockedHeartbeatTask = Task.Run(() => heartbeat.OnHeartbeat()); @@ -169,7 +169,7 @@ public async Task HeartbeatTakingLongerThanIntervalIsNotLoggedIfDebuggerAttached } // 2 seconds passes... - systemClock.UtcNow = systemClock.UtcNow.AddSeconds(2); + timeProvider.Advance(TimeSpan.FromSeconds(2)); handlerMre.Set(); @@ -183,14 +183,14 @@ public async Task HeartbeatTakingLongerThanIntervalIsNotLoggedIfDebuggerAttached [Fact] public void ExceptionFromHeartbeatHandlerIsLoggedAsError() { - var systemClock = new MockSystemClock(); + var timeProvider = new MockTimeProvider(); var heartbeatHandler = new Mock(); var kestrelTrace = new KestrelTrace(LoggerFactory); var ex = new Exception(); - heartbeatHandler.Setup(h => h.OnHeartbeat(systemClock.UtcNow)).Throws(ex); + heartbeatHandler.Setup(h => h.OnHeartbeat(timeProvider.GetUtcNow())).Throws(ex); - using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, systemClock, DebuggerWrapper.Singleton, kestrelTrace, Heartbeat.Interval)) + using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, timeProvider, DebuggerWrapper.Singleton, kestrelTrace, Heartbeat.Interval)) { heartbeat.OnHeartbeat(); } diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index a6415f735e77..0119f60b3c1c 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -725,7 +725,7 @@ public void StartingServerInitializesHeartbeat() testContext.Heartbeat = new Heartbeat( new IHeartbeatHandler[] { testContext.DateHeaderValueManager }, - testContext.MockSystemClock, + testContext.MockTimeProvider, DebuggerWrapper.Singleton, testContext.Log, Heartbeat.Interval); @@ -736,11 +736,11 @@ public void StartingServerInitializesHeartbeat() // Ensure KestrelServer is started at a different time than when it was constructed, since we're // verifying the heartbeat is initialized during KestrelServer.StartAsync(). - testContext.MockSystemClock.UtcNow += TimeSpan.FromDays(1); + testContext.MockTimeProvider.Advance(TimeSpan.FromDays(1)); StartDummyApplication(server); - Assert.Equal(HeaderUtilities.FormatDate(testContext.MockSystemClock.UtcNow), + Assert.Equal(HeaderUtilities.FormatDate(testContext.MockTimeProvider.GetUtcNow()), testContext.DateHeaderValueManager.GetDateHeaderValues().String); } } diff --git a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs index 4d40887d8a7c..ba9e283fd0b6 100644 --- a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs +++ b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -14,13 +14,13 @@ public class TimeoutControlTests { private readonly Mock _mockTimeoutHandler; private readonly TimeoutControl _timeoutControl; - private readonly MockSystemClock _systemClock; + private readonly MockTimeProvider _timeProvider; public TimeoutControlTests() { _mockTimeoutHandler = new Mock(); _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object); - _systemClock = new MockSystemClock(); + _timeProvider = new MockTimeProvider(); } [Fact] @@ -157,51 +157,51 @@ public void RequestBodyDataRateNotComputedOnPausedTime() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_systemClock.UtcNow.Ticks); + _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); // Tick at 3s, expected counted time is 3s, expected data rate is 200 bytes/second - _systemClock.UtcNow += TimeSpan.FromSeconds(1); - _timeoutControl.Tick(_systemClock.UtcNow); - _systemClock.UtcNow += TimeSpan.FromSeconds(1); - _timeoutControl.Tick(_systemClock.UtcNow); - _systemClock.UtcNow += TimeSpan.FromSeconds(1); + _timeProvider.Advance(TimeSpan.FromSeconds(1)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeProvider.Advance(TimeSpan.FromSeconds(1)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeProvider.Advance(TimeSpan.FromSeconds(1)); _timeoutControl.BytesRead(600); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); // Pause at 3.5s - _systemClock.UtcNow += TimeSpan.FromSeconds(0.5); + _timeProvider.Advance(TimeSpan.FromSeconds(0.5)); _timeoutControl.StopTimingRead(); // Tick at 4s, expected counted time is 4s (first tick after pause goes through), expected data rate is 150 bytes/second - _systemClock.UtcNow += TimeSpan.FromSeconds(0.5); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.Advance(TimeSpan.FromSeconds(0.5)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); // Tick at 6s, expected counted time is 4s, expected data rate is 150 bytes/second - _systemClock.UtcNow += TimeSpan.FromSeconds(2); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.Advance(TimeSpan.FromSeconds(2)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); // Not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Resume at 6.5s - _systemClock.UtcNow += TimeSpan.FromSeconds(0.5); + _timeProvider.Advance(TimeSpan.FromSeconds(0.5)); _timeoutControl.StartTimingRead(); // Tick at 9s, expected counted time is 6s, expected data rate is 100 bytes/second - _systemClock.UtcNow += TimeSpan.FromSeconds(1.0); - _timeoutControl.Tick(_systemClock.UtcNow); - _systemClock.UtcNow += TimeSpan.FromSeconds(.5); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.Advance(TimeSpan.FromSeconds(1.0)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeProvider.Advance(TimeSpan.FromSeconds(.5)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); // Not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Tick at 10s, expected counted time is 7s, expected data rate drops below 100 bytes/second - _systemClock.UtcNow += TimeSpan.FromSeconds(1); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.Advance(TimeSpan.FromSeconds(1)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); // Timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -213,40 +213,40 @@ public void ReadTimingNotPausedWhenResumeCalledBeforeNextTick() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_systemClock.UtcNow.Ticks); + _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); // Tick at 2s, expected counted time is 2s, expected data rate is 100 bytes/second - _systemClock.UtcNow += TimeSpan.FromSeconds(1); - _timeoutControl.Tick(_systemClock.UtcNow); - _systemClock.UtcNow += TimeSpan.FromSeconds(1); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.Advance(TimeSpan.FromSeconds(1)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeProvider.Advance(TimeSpan.FromSeconds(1)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); _timeoutControl.BytesRead(200); // Not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Pause at 2.25s - _systemClock.UtcNow += TimeSpan.FromSeconds(0.25); + _timeProvider.Advance(TimeSpan.FromSeconds(0.25)); _timeoutControl.StopTimingRead(); // Resume at 2.5s - _systemClock.UtcNow += TimeSpan.FromSeconds(0.25); + _timeProvider.Advance(TimeSpan.FromSeconds(0.25)); _timeoutControl.StartTimingRead(); // Tick at 3s, expected counted time is 3s, expected data rate is 100 bytes/second - _systemClock.UtcNow += TimeSpan.FromSeconds(0.5); + _timeProvider.Advance(TimeSpan.FromSeconds(0.5)); _timeoutControl.BytesRead(100); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); // Not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Tick at 4s, expected counted time is 4s, expected data rate drops below 100 bytes/second - _systemClock.UtcNow += TimeSpan.FromSeconds(1); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.Advance(TimeSpan.FromSeconds(1)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); // Timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -258,7 +258,7 @@ public void ReadTimingNotEnforcedWhenTimeoutIsSet() var timeout = TimeSpan.FromSeconds(5); var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); - var startTime = _systemClock.UtcNow; + var startTime = _timeProvider.GetUtcNow(); // Initialize timestamp _timeoutControl.Initialize(startTime.Ticks); @@ -269,16 +269,16 @@ public void ReadTimingNotEnforcedWhenTimeoutIsSet() _timeoutControl.SetTimeout(timeout.Ticks, TimeoutReason.RequestBodyDrain); // Tick beyond grace period with low data rate - _systemClock.UtcNow += TimeSpan.FromSeconds(3); + _timeProvider.Advance(TimeSpan.FromSeconds(3)); _timeoutControl.BytesRead(1); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); // Not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Tick just past timeout period, adjusted by Heartbeat.Interval - _systemClock.UtcNow = startTime + timeout + Heartbeat.Interval + TimeSpan.FromTicks(1); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.SetUtcNow(startTime + timeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); // Timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.RequestBodyDrain), Times.Once); @@ -377,7 +377,7 @@ public void WriteTimingAbortsConnectionWhenWriteDoesNotCompleteWithMinimumDataRa var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_systemClock.UtcNow.Ticks); + _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); // Should complete within 4 seconds, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 400); @@ -395,7 +395,7 @@ public void WriteTimingAbortsConnectionWhenSmallWriteDoesNotCompleteWithinGraceP var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(5)); // Initialize timestamp - var startTime = _systemClock.UtcNow; + var startTime = _timeProvider.GetUtcNow(); _timeoutControl.Initialize(startTime.Ticks); // Should complete within 1 second, but the timeout is adjusted by adding Heartbeat.Interval @@ -420,7 +420,7 @@ public void WriteTimingTimeoutPushedOnConcurrentWrite() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_systemClock.UtcNow.Ticks); + _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); // Should complete within 5 seconds, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 500); @@ -453,7 +453,7 @@ public void WriteTimingAbortsConnectionWhenRepeatedSmallWritesDoNotCompleteWithM var writeSize = 100; // Initialize timestamp - var startTime = _systemClock.UtcNow; + var startTime = _timeProvider.GetUtcNow(); _timeoutControl.Initialize(startTime.Ticks); // 5 consecutive 100 byte writes. @@ -473,8 +473,8 @@ public void WriteTimingAbortsConnectionWhenRepeatedSmallWritesDoNotCompleteWithM _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // On more tick forward triggers the timeout. - _systemClock.UtcNow += TimeSpan.FromTicks(1); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.Advance(TimeSpan.FromTicks(1)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); } @@ -485,15 +485,15 @@ public void WriteTimingOnlyCountsUpToOneHeartbeatIntervalPerTick() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_systemClock.UtcNow.Ticks); + _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); // Should complete within 4 seconds, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 400); _timeoutControl.StartTimingWrite(); // Tick just past 4s plus Heartbeat.Interval at once - _systemClock.UtcNow += TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.Advance(TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1)); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Never); @@ -510,7 +510,7 @@ private void TickBodyWithMinimumDataRate(int bytesPerSecond) var minRate = new MinDataRate(bytesPerSecond, gracePeriod); // Initialize timestamp - _timeoutControl.Initialize(_systemClock.UtcNow.Ticks); + _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); @@ -518,22 +518,22 @@ private void TickBodyWithMinimumDataRate(int bytesPerSecond) AdvanceClock(gracePeriod); // Tick after grace period w/ low data rate - _systemClock.UtcNow += TimeSpan.FromSeconds(1); + _timeProvider.Advance(TimeSpan.FromSeconds(1)); _timeoutControl.BytesRead(1); - _timeoutControl.Tick(_systemClock.UtcNow); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); } private void AdvanceClock(TimeSpan timeSpan) { - var endTime = _systemClock.UtcNow + timeSpan; + var endTime = _timeProvider.GetUtcNow() + timeSpan; - while (_systemClock.UtcNow + Heartbeat.Interval < endTime) + while (_timeProvider.GetUtcNow() + Heartbeat.Interval < endTime) { - _systemClock.UtcNow += Heartbeat.Interval; - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.Advance(Heartbeat.Interval); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); } - _systemClock.UtcNow = endTime; - _timeoutControl.Tick(_systemClock.UtcNow); + _timeProvider.SetUtcNow(endTime); + _timeoutControl.Tick(_timeProvider.GetUtcNow()); } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/ISystemClock.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/ISystemClock.cs deleted file mode 100644 index 30e33045162f..000000000000 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/ISystemClock.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal; - -/// -/// Abstracts the system clock to facilitate testing. -/// -internal interface ISystemClock -{ - /// - /// Retrieves the current system time in UTC. - /// - DateTimeOffset UtcNow { get; } -} - -internal sealed class SystemClock : ISystemClock -{ - public DateTimeOffset UtcNow => DateTimeOffset.UtcNow; -} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs index 9fa0fd358452..268ac3d5814a 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs @@ -255,7 +255,7 @@ internal bool TryReturnStream(QuicStreamContext stream) heartbeatFeature.OnHeartbeat(static state => ((QuicConnectionContext)state).RemoveExpiredStreams(), this); // Set ticks for the first time. Ticks are then updated in heartbeat. - var now = _context.Options.SystemClock.UtcNow.Ticks; + var now = _context.Options.TimeProvider.GetUtcNow().Ticks; Volatile.Write(ref _heartbeatTicks, now); _streamPoolHeartbeatInitialized = true; @@ -284,7 +284,7 @@ private void RemoveExpiredStreams() lock (_poolLock) { // Update ticks on heartbeat. A precise value isn't necessary. - var now = _context.Options.SystemClock.UtcNow.Ticks; + var now = _context.Options.TimeProvider.GetUtcNow().Ticks; Volatile.Write(ref _heartbeatTicks, now); StreamPool.RemoveExpired(now); diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs index 3a455d7dc68d..e2f58f124b0a 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.Versioning; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic; @@ -80,5 +79,5 @@ private static void ValidateErrorCode(long errorCode) } } - internal ISystemClock SystemClock = new SystemClock(); + internal TimeProvider TimeProvider = TimeProvider.System; } diff --git a/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs b/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs index ef38b4848852..01ff7af3ffa8 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs @@ -506,8 +506,8 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() using var httpEventSource = new HttpEventSourceListener(LoggerFactory); var now = new DateTimeOffset(2021, 7, 6, 12, 0, 0, TimeSpan.Zero); - var testSystemClock = new TestSystemClock { UtcNow = now }; - await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, testSystemClock); + var testTimeProvider = new TestTimeProvider(now); + await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, testTimeProvider); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); await using var clientConnection = await QuicConnection.ConnectAsync(options); @@ -529,7 +529,7 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks); now = now.AddMilliseconds(100); - testSystemClock.UtcNow = now; + testTimeProvider.SetUtcNow(now); testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); @@ -544,13 +544,13 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() Assert.Same(stream1, stream2); now = now.AddTicks(QuicConnectionContext.StreamPoolExpiryTicks); - testSystemClock.UtcNow = now; + testTimeProvider.SetUtcNow(now); testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); now = now.AddTicks(1); - testSystemClock.UtcNow = now; + testTimeProvider.SetUtcNow(now); testHeartbeatFeature.RaiseHeartbeat(); // Removed. Assert.Equal(0, quicConnectionContext.StreamPool.Count); @@ -720,9 +720,24 @@ private record RequestState( public int ActiveConcurrentConnections { get; set; } }; - private class TestSystemClock : ISystemClock + private class TestTimeProvider : TimeProvider { - public DateTimeOffset UtcNow { get; set; } + private DateTimeOffset _now; + + public TestTimeProvider(DateTimeOffset now) + { + _now = now; + } + + public override DateTimeOffset GetUtcNow() + { + return _now; + } + + public void SetUtcNow(DateTimeOffset now) + { + _now = now; + } } private class TestHeartbeatFeature : IConnectionHeartbeatFeature diff --git a/src/Servers/Kestrel/Transport.Quic/test/QuicTestHelpers.cs b/src/Servers/Kestrel/Transport.Quic/test/QuicTestHelpers.cs index 4fa223873d5a..2eab51cca0b5 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/QuicTestHelpers.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/QuicTestHelpers.cs @@ -29,16 +29,16 @@ internal static class QuicTestHelpers public static QuicTransportFactory CreateTransportFactory( ILoggerFactory loggerFactory = null, - ISystemClock systemClock = null, + TimeProvider timeProvider = null, long defaultCloseErrorCode = 0) { var quicTransportOptions = new QuicTransportOptions(); quicTransportOptions.MaxBidirectionalStreamCount = 200; quicTransportOptions.MaxUnidirectionalStreamCount = 200; quicTransportOptions.DefaultCloseErrorCode = defaultCloseErrorCode; - if (systemClock != null) + if (timeProvider != null) { - quicTransportOptions.SystemClock = systemClock; + quicTransportOptions.TimeProvider = timeProvider; } return new QuicTransportFactory(loggerFactory ?? NullLoggerFactory.Instance, Options.Create(quicTransportOptions)); @@ -46,14 +46,14 @@ public static QuicTransportFactory CreateTransportFactory( public static async Task CreateConnectionListenerFactory( ILoggerFactory loggerFactory = null, - ISystemClock systemClock = null, + TimeProvider timeProvider = null, bool clientCertificateRequired = false, long defaultCloseErrorCode = 0, int port = 0) { var transportFactory = CreateTransportFactory( loggerFactory, - systemClock, + timeProvider, defaultCloseErrorCode: defaultCloseErrorCode); var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), port); @@ -65,10 +65,10 @@ public static async Task CreateConnectionListenerFactory public static async Task CreateConnectionListenerFactory( TlsConnectionCallbackOptions tlsConnectionOptions, ILoggerFactory loggerFactory = null, - ISystemClock systemClock = null, + TimeProvider timeProvider = null, int port = 0) { - var transportFactory = CreateTransportFactory(loggerFactory, systemClock); + var transportFactory = CreateTransportFactory(loggerFactory, timeProvider); var endpoint = new IPEndPoint(IPAddress.Loopback, port); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs index 010d7385417e..39071cc92ddd 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs @@ -69,7 +69,7 @@ public virtual void GlobalSetup() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), dateHeaderValueManager: new DateHeaderValueManager(), - systemClock: new MockSystemClock()); + timeProvider: TimeProvider.System); serviceContext.DateHeaderValueManager.OnHeartbeat(default); var connectionContext = TestContextFactory.CreateHttpConnectionContext( diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http3/Http3ConnectionBenchmarkBase.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http3/Http3ConnectionBenchmarkBase.cs index ac508ddcc447..1a7c7e1fe9b8 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http3/Http3ConnectionBenchmarkBase.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http3/Http3ConnectionBenchmarkBase.cs @@ -52,15 +52,15 @@ public virtual void GlobalSetup() _httpRequestHeaders[InternalHeaderNames.Scheme] = new StringValues("http"); _httpRequestHeaders[InternalHeaderNames.Authority] = new StringValues("localhost:80"); + var mockTimeProvider = new MockTimeProvider(); + var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), dateHeaderValueManager: new DateHeaderValueManager(), - systemClock: new MockSystemClock()); + timeProvider: mockTimeProvider); serviceContext.DateHeaderValueManager.OnHeartbeat(default); - var mockSystemClock = new Microsoft.AspNetCore.Testing.MockSystemClock(); - - _http3 = new Http3InMemory(serviceContext, mockSystemClock, new DefaultTimeoutHandler(), NullLoggerFactory.Instance); + _http3 = new Http3InMemory(serviceContext, mockTimeProvider, new DefaultTimeoutHandler(), NullLoggerFactory.Instance); _http3.InitializeConnectionAsync(ProcessRequest).GetAwaiter().GetResult(); } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj b/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj index 073496464d85..192cd25e51ca 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockSystemClock.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockSystemClock.cs deleted file mode 100644 index 58ed52a202e9..000000000000 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockSystemClock.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; - -namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks; - -internal sealed class MockSystemClock : ISystemClock -{ - public DateTimeOffset UtcNow { get; } - public long UtcNowTicks { get; } - public DateTimeOffset UtcNowUnsynchronized { get; } -} diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index f4ecc6a6a598..e4ef05ee8ef5 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -37,13 +37,13 @@ internal class Http3InMemory protected static readonly byte[] _helloWorldBytes = Encoding.ASCII.GetBytes("hello, world"); protected static readonly byte[] _maxData = Encoding.ASCII.GetBytes(new string('a', 16 * 1024)); - public Http3InMemory(ServiceContext serviceContext, MockSystemClock mockSystemClock, ITimeoutHandler timeoutHandler, ILoggerFactory loggerFactory) + public Http3InMemory(ServiceContext serviceContext, MockTimeProvider mockTimeProvider, ITimeoutHandler timeoutHandler, ILoggerFactory loggerFactory) { _serviceContext = serviceContext; _timeoutControl = new TimeoutControl(new TimeoutControlConnectionInvoker(this, timeoutHandler)); _timeoutControl.Debugger = new TestDebugger(); - _mockSystemClock = mockSystemClock; + _mockTimeProvider = mockTimeProvider; _serverReceivedSettings = Channel.CreateUnbounded>(); Logger = loggerFactory.CreateLogger(); @@ -73,7 +73,7 @@ public void OnTimeout(TimeoutReason reason) } internal ServiceContext _serviceContext; - private MockSystemClock _mockSystemClock; + private MockTimeProvider _mockTimeProvider; internal HttpConnection _httpConnection; internal readonly TimeoutControl _timeoutControl; internal readonly MemoryPool _memoryPool = PinnedBlockMemoryPoolFactory.Create(); @@ -199,26 +199,26 @@ internal void VerifyGoAway(Http3FrameWithPayload frame, long expectedLastStreamI } } - public void AdvanceClock(TimeSpan timeSpan) + public void AdvanceTime(TimeSpan timeSpan) { - Logger.LogDebug($"Advancing clock {timeSpan}."); + Logger.LogDebug($"Advancing timeProvider {timeSpan}."); - var clock = _mockSystemClock; - var endTime = clock.UtcNow + timeSpan; + var timeProvider = _mockTimeProvider; + var endTime = timeProvider.GetUtcNow() + timeSpan; - while (clock.UtcNow + Heartbeat.Interval < endTime) + while (timeProvider.GetUtcNow() + Heartbeat.Interval < endTime) { - clock.UtcNow += Heartbeat.Interval; - _timeoutControl.Tick(clock.UtcNow); + timeProvider.Advance(Heartbeat.Interval); + _timeoutControl.Tick(timeProvider.GetUtcNow()); } - clock.UtcNow = endTime; - _timeoutControl.Tick(clock.UtcNow); + timeProvider.SetUtcNow(endTime); + _timeoutControl.Tick(timeProvider.GetUtcNow()); } public void TriggerTick(DateTimeOffset now) { - _mockSystemClock.UtcNow = now; + _mockTimeProvider.SetUtcNow(now); Connection?.Tick(now); } diff --git a/src/Servers/Kestrel/shared/test/MockSystemClock.cs b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs similarity index 57% rename from src/Servers/Kestrel/shared/test/MockSystemClock.cs rename to src/Servers/Kestrel/shared/test/MockTimeProvider.cs index 71a231637a79..25d343823d0a 100644 --- a/src/Servers/Kestrel/shared/test/MockSystemClock.cs +++ b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs @@ -1,42 +1,44 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Threading; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; - namespace Microsoft.AspNetCore.Testing; -public class MockSystemClock : ISystemClock +public class MockTimeProvider : TimeProvider { private long _utcNowTicks; - public MockSystemClock() + public MockTimeProvider() { // Use a random DateTimeOffset to ensure tests that incorrectly use the current DateTimeOffset fail always instead of only rarely. // Pick a date between the min DateTimeOffset and a day before the max DateTimeOffset so there's room to advance the clock. _utcNowTicks = NextLong(DateTimeOffset.MinValue.Ticks, DateTimeOffset.MaxValue.Ticks - TimeSpan.FromDays(1).Ticks); } - public DateTimeOffset UtcNow + public MockTimeProvider(DateTimeOffset now) { - get - { - UtcNowCalled++; - return new DateTimeOffset(Interlocked.Read(ref _utcNowTicks), TimeSpan.Zero); - } - set - { - Interlocked.Exchange(ref _utcNowTicks, value.Ticks); - } + _utcNowTicks = now.UtcTicks; } - public long UtcNowTicks => UtcNow.Ticks; + public override DateTimeOffset GetUtcNow() + { + UtcNowCalled++; + return new DateTimeOffset(Interlocked.Read(ref _utcNowTicks), TimeSpan.Zero); + } - public DateTimeOffset UtcNowUnsynchronized => UtcNow; + public void SetUtcNow(DateTimeOffset now) + { + Interlocked.Exchange(ref _utcNowTicks, now.Ticks); + } + + public override long GetTimestamp() => GetUtcNow().Ticks; public int UtcNowCalled { get; private set; } + public void Advance(TimeSpan timeSpan) + { + Interlocked.Add(ref _utcNowTicks, timeSpan.Ticks); + } + private long NextLong(long minValue, long maxValue) { return (long)(Random.Shared.NextDouble() * (maxValue - minValue) + minValue); diff --git a/src/Servers/Kestrel/shared/test/TestContextFactory.cs b/src/Servers/Kestrel/shared/test/TestContextFactory.cs index 6a9caffff83a..49ad4f2c4415 100644 --- a/src/Servers/Kestrel/shared/test/TestContextFactory.cs +++ b/src/Servers/Kestrel/shared/test/TestContextFactory.cs @@ -1,13 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Buffers; -using System.Collections.Generic; using System.IO.Pipelines; using System.Net; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -29,7 +25,7 @@ public static ServiceContext CreateServiceContext( KestrelServerOptions serverOptions, IHttpParser httpParser = null, PipeScheduler scheduler = null, - ISystemClock systemClock = null, + TimeProvider timeProvider = null, DateHeaderValueManager dateHeaderValueManager = null, ConnectionManager connectionManager = null, Heartbeat heartbeat = null) @@ -39,7 +35,7 @@ public static ServiceContext CreateServiceContext( Log = new KestrelTrace(NullLoggerFactory.Instance), Scheduler = scheduler, HttpParser = httpParser, - SystemClock = systemClock, + TimeProvider = timeProvider, DateHeaderValueManager = dateHeaderValueManager, ConnectionManager = connectionManager, Heartbeat = heartbeat, diff --git a/src/Servers/Kestrel/shared/test/TestServiceContext.cs b/src/Servers/Kestrel/shared/test/TestServiceContext.cs index 4893853164c8..d2cb2ba267dc 100644 --- a/src/Servers/Kestrel/shared/test/TestServiceContext.cs +++ b/src/Servers/Kestrel/shared/test/TestServiceContext.cs @@ -42,13 +42,13 @@ public void InitializeHeartbeat() DateHeaderValueManager = new DateHeaderValueManager(); Heartbeat = new Heartbeat( new IHeartbeatHandler[] { DateHeaderValueManager, heartbeatManager }, - new SystemClock(), + TimeProvider.System, DebuggerWrapper.Singleton, Log, Heartbeat.Interval); - MockSystemClock = null; - SystemClock = heartbeatManager; + MockTimeProvider = null; + TimeProvider = heartbeatManager; } private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace, bool disableHttp1LineFeedTerminators, KestrelMetrics metrics) @@ -56,8 +56,8 @@ private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace, LoggerFactory = loggerFactory; Log = kestrelTrace; Scheduler = PipeScheduler.ThreadPool; - MockSystemClock = new MockSystemClock(); - SystemClock = MockSystemClock; + MockTimeProvider = new MockTimeProvider(); + TimeProvider = MockTimeProvider; DateHeaderValueManager = new DateHeaderValueManager(); ConnectionManager = new ConnectionManager(Log, ResourceCounter.Unlimited); HttpParser = new HttpParser(Log.IsEnabled(LogLevel.Information), disableHttp1LineFeedTerminators); @@ -66,13 +66,13 @@ private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace, AddServerHeader = false }; - DateHeaderValueManager.OnHeartbeat(SystemClock.UtcNow); + DateHeaderValueManager.OnHeartbeat(TimeProvider.GetUtcNow()); Metrics = metrics; } public ILoggerFactory LoggerFactory { get; set; } - public MockSystemClock MockSystemClock { get; set; } + public MockTimeProvider MockTimeProvider { get; set; } public Func> MemoryPoolFactory { get; set; } = System.Buffers.PinnedBlockMemoryPoolFactory.Create; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs index 61f5bda90ef4..7e5cec61fa81 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2ConnectionTests.cs @@ -634,7 +634,7 @@ public async Task StreamPool_UnendedStreamErrorsOnStart_NotReturnedToPool() // TriggerTick will trigger the stream to be returned to the pool so we can assert it TriggerTick(); - AdvanceClock(TimeSpan.FromTicks(Constants.RequestBodyDrainTimeout.Ticks + 1)); + AdvanceTime(TimeSpan.FromTicks(Constants.RequestBodyDrainTimeout.Ticks + 1)); // TriggerTick will trigger the stream to attempt to be returned to the pool TriggerTick(); @@ -670,12 +670,12 @@ await ExpectAsync(Http2FrameType.HEADERS, _connection.StreamPool.TryPeek(out var pooledStream); - AdvanceClock(TimeSpan.FromSeconds(1)); + AdvanceTime(TimeSpan.FromSeconds(1)); // Stream has not expired and is still in pool Assert.Equal(1, _connection.StreamPool.Count); - AdvanceClock(TimeSpan.FromSeconds(6)); + AdvanceTime(TimeSpan.FromSeconds(6)); // Stream has expired and has been removed from pool Assert.Equal(0, _connection.StreamPool.Count); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs index 47d9ed74d51e..467f1761d4ef 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs @@ -28,7 +28,7 @@ public async Task KeepAlivePingTimeout_InfiniteTimeSpan_NoGoAway() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockSystemClock.UtcNow; + DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); // Heartbeat TriggerTick(now); @@ -58,7 +58,7 @@ public async Task IntervalExceeded_WithoutActivity_PingSent() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockSystemClock.UtcNow; + DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); // Heartbeat TriggerTick(now); @@ -81,7 +81,7 @@ public async Task IntervalExceeded_WithActivity_NoPingSent() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockSystemClock.UtcNow; + DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); // Heartbeat TriggerTick(now); @@ -123,7 +123,7 @@ public async Task IntervalExceeded_MultipleTimes_PingsNotSentWhileAwaitingOnAck( await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockSystemClock.UtcNow; + DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); TriggerTick(now); TriggerTick(now + TimeSpan.FromSeconds(1.1 * 2)); @@ -147,7 +147,7 @@ public async Task IntervalExceeded_MultipleTimes_PingSentAfterAck() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockSystemClock.UtcNow; + DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); // Heartbeats TriggerTick(now); @@ -187,7 +187,7 @@ public async Task TimeoutExceeded_NoAck_GoAway() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockSystemClock.UtcNow; + DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); // Heartbeat TriggerTick(now); @@ -219,7 +219,7 @@ public async Task TimeoutExceeded_NonPingActivity_NoGoAway() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockSystemClock.UtcNow; + DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); // Heartbeat TriggerTick(now); @@ -251,7 +251,7 @@ public async Task IntervalExceeded_StreamStarted_NoPingSent() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockSystemClock.UtcNow; + DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); // Heartbeat TriggerTick(now); @@ -286,7 +286,7 @@ await InitializeConnectionAsync(async c => await c.Request.Body.FlushAsync(); }, expectedWindowUpdate: false).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockSystemClock.UtcNow; + DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); // Heartbeat TriggerTick(now); @@ -341,7 +341,7 @@ await InitializeConnectionAsync(async c => await c.Request.Body.FlushAsync(); }, expectedWindowUpdate: false).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockSystemClock.UtcNow; + DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); // Heartbeat TriggerTick(now); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 3e980aa0ba1e..f011c923e2a9 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -472,7 +472,7 @@ protected void CreateConnection() _mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny())) .Callback(r => httpConnection.OnTimeout(r)); - _timeoutControl.Initialize(_serviceContext.SystemClock.UtcNow.Ticks); + _timeoutControl.Initialize(_serviceContext.TimeProvider.GetUtcNow().Ticks); } private class LifetimeHandlerInterceptor : IHttp2StreamLifetimeHandler @@ -1340,20 +1340,20 @@ protected void VerifyDecodedRequestHeaders(IEnumerable h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); @@ -42,11 +42,11 @@ public async Task HEADERS_NotReceivedInitially_WithinKeepAliveTimeout_ClosesConn await InitializeConnectionAsync(_noopApplication); - AdvanceClock(limits.KeepAliveTimeout + Heartbeat.Interval); + AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); @@ -62,7 +62,7 @@ public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_Cl await InitializeConnectionAsync(_noopApplication); - AdvanceClock(limits.KeepAliveTimeout + Heartbeat.Interval); + AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval); // keep-alive timeout set but not fired. _mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Once); @@ -90,11 +90,11 @@ await ExpectAsync(Http2FrameType.HEADERS, await setTimeoutTcs.Task.DefaultTimeout(); - AdvanceClock(limits.KeepAliveTimeout + Heartbeat.Interval); + AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); @@ -178,13 +178,13 @@ public async Task HEADERS_ReceivedWithoutAllCONTINUATIONs_WithinRequestHeadersTi await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.NONE); - AdvanceClock(limits.RequestHeadersTimeout + Heartbeat.Interval); + AdvanceTime(limits.RequestHeadersTimeout + Heartbeat.Interval); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.NONE); - AdvanceClock(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.RequestHeaders), Times.Once); @@ -212,13 +212,13 @@ public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); - AdvanceClock(TimeSpan.FromSeconds(_bytesReceived / limits.MinResponseDataRate.BytesPerSecond) + + AdvanceTime(TimeSpan.FromSeconds(_bytesReceived / limits.MinResponseDataRate.BytesPerSecond) + limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); _mockConnectionContext.Verify(c => c.Abort(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromSeconds(1)); + AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); @@ -242,7 +242,7 @@ public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterCooldow // Remove callback that completes _pair.Application.Output on abort. _mockConnectionContext.Reset(); - var mockSystemClock = _serviceContext.MockSystemClock; + var mockTimeProvider = _serviceContext.MockTimeProvider; var headers = new[] { @@ -268,7 +268,7 @@ async Task AdvanceClockAndSendFrames() while (!closed) { // Just past the timeout - mockSystemClock.UtcNow += Constants.RequestBodyDrainTimeout + TimeSpan.FromTicks(1); + mockTimeProvider.Advance(Constants.RequestBodyDrainTimeout + TimeSpan.FromTicks(1)); // Send an extra frame to make it fail switch (finalFrameType) @@ -355,12 +355,12 @@ await ExpectAsync(Http2FrameType.HEADERS, TriggerTick(); // Don't read data frame to induce "socket" backpressure. - AdvanceClock(TimeSpan.FromSeconds((_bytesReceived + _helloWorldBytes.Length) / limits.MinResponseDataRate.BytesPerSecond) + + AdvanceTime(TimeSpan.FromSeconds((_bytesReceived + _helloWorldBytes.Length) / limits.MinResponseDataRate.BytesPerSecond) + limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromSeconds(1)); + AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); @@ -410,11 +410,11 @@ await ExpectAsync(Http2FrameType.HEADERS, limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5); // Don't read data frame to induce "socket" backpressure. - AdvanceClock(timeToWriteMaxData); + AdvanceTime(timeToWriteMaxData); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromSeconds(1)); + AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); @@ -462,12 +462,12 @@ await ExpectAsync(Http2FrameType.DATA, TriggerTick(); // Don't send WINDOW_UPDATE to induce flow-control backpressure - AdvanceClock(TimeSpan.FromSeconds(_bytesReceived / limits.MinResponseDataRate.BytesPerSecond) + + AdvanceTime(TimeSpan.FromSeconds(_bytesReceived / limits.MinResponseDataRate.BytesPerSecond) + limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromSeconds(1)); + AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); @@ -516,11 +516,11 @@ await ExpectAsync(Http2FrameType.DATA, limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5); // Don't send WINDOW_UPDATE to induce flow-control backpressure - AdvanceClock(timeToWriteMaxData); + AdvanceTime(timeToWriteMaxData); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromSeconds(1)); + AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); @@ -581,11 +581,11 @@ await ExpectAsync(Http2FrameType.DATA, limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5); // Don't send WINDOW_UPDATE to induce flow-control backpressure - AdvanceClock(timeToWriteMaxData); + AdvanceTime(timeToWriteMaxData); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromSeconds(1)); + AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); @@ -627,11 +627,11 @@ await ExpectAsync(Http2FrameType.DATA, withStreamId: 1); // Don't send any more data and advance just to and then past the grace period. - AdvanceClock(limits.MinRequestBodyDataRate.GracePeriod); + AdvanceTime(limits.MinRequestBodyDataRate.GracePeriod); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -677,11 +677,11 @@ await ExpectAsync(Http2FrameType.DATA, var timeToReadMaxData = TimeSpan.FromSeconds(_maxData.Length / limits.MinRequestBodyDataRate.BytesPerSecond) - TimeSpan.FromSeconds(.5); // Don't send any more data and advance just to and then past the rate timeout. - AdvanceClock(timeToReadMaxData); + AdvanceTime(timeToReadMaxData); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromSeconds(1)); + AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -743,11 +743,11 @@ await ExpectAsync(Http2FrameType.DATA, timeToReadMaxData -= TimeSpan.FromSeconds(.5); // Don't send any more data and advance just to and then past the rate timeout. - AdvanceClock(timeToReadMaxData); + AdvanceTime(timeToReadMaxData); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromSeconds(1)); + AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -810,11 +810,11 @@ await ExpectAsync(Http2FrameType.DATA, var timeToReadMaxData = TimeSpan.FromSeconds(_maxData.Length / limits.MinRequestBodyDataRate.BytesPerSecond) - TimeSpan.FromSeconds(.5); // Don't send any more data and advance just to and then past the rate timeout. - AdvanceClock(timeToReadMaxData); + AdvanceTime(timeToReadMaxData); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromSeconds(1)); + AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -861,11 +861,11 @@ await ExpectAsync(Http2FrameType.DATA, withStreamId: 1); // Don't send any more data and advance just to and then past the grace period. - AdvanceClock(limits.MinRequestBodyDataRate.GracePeriod); + AdvanceTime(limits.MinRequestBodyDataRate.GracePeriod); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); @@ -929,7 +929,7 @@ await ExpectAsync(Http2FrameType.DATA, withStreamId: 3); // No matter how much time elapses there is no read timeout because the connection window is too small. - AdvanceClock(TimeSpan.FromDays(1)); + AdvanceTime(TimeSpan.FromDays(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); @@ -949,11 +949,11 @@ await ExpectAsync(Http2FrameType.HEADERS, var expectedUpdateSize = ((framesConnectionInWindow / 2) + 1) * _maxData.Length + _helloWorldBytes.Length; Assert.Equal(expectedUpdateSize, updateFrame.WindowUpdateSizeIncrement); - AdvanceClock(limits.MinRequestBodyDataRate.GracePeriod); + AdvanceTime(limits.MinRequestBodyDataRate.GracePeriod); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceClock(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs index 1bf786788b64..cbe0d1ce1d14 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs @@ -338,7 +338,7 @@ await InitializeConnectionAsync(async context => await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers); // Don't send any more data and advance just to and then past the grace period. - AdvanceClock(limits.MinRequestBodyDataRate.GracePeriod + TimeSpan.FromTicks(1)); + AdvanceTime(limits.MinRequestBodyDataRate.GracePeriod + TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs index 25be0a175b51..6300576c7c9c 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs @@ -290,7 +290,7 @@ await Http3Api.WaitForConnectionErrorAsync( [Fact] public async Task ControlStream_ClientToServer_Completes_ConnectionError() { - var now = _serviceContext.MockSystemClock.UtcNow; + var now = _serviceContext.MockTimeProvider.GetUtcNow(); await Http3Api.InitializeConnectionAsync(_noopApplication); @@ -339,7 +339,7 @@ public async Task GOAWAY_TriggersLifetimeNotification_ConnectionClosedRequested( [Fact] public async Task ControlStream_ServerToClient_ErrorInitializing_ConnectionError() { - var now = _serviceContext.MockSystemClock.UtcNow; + var now = _serviceContext.MockTimeProvider.GetUtcNow(); await Http3Api.InitializeConnectionAsync(_noopApplication); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index b5d0f612afd5..9ab1efb1726b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -145,7 +145,7 @@ public override void Initialize(TestContext context, MethodInfo methodInfo, obje Scheduler = PipeScheduler.Inline, }; - Http3Api = new Http3InMemory(_serviceContext, _serviceContext.MockSystemClock, _mockTimeoutHandler.Object, LoggerFactory); + Http3Api = new Http3InMemory(_serviceContext, _serviceContext.MockTimeProvider, _mockTimeoutHandler.Object, LoggerFactory); } public void AssertExpectedErrorMessages(string expectedErrorMessage) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs index fb741c527cb3..afbbb31a42eb 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs @@ -25,7 +25,7 @@ public async Task KeepAliveTimeout_ControlStreamNotReceived_ConnectionClosed() var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout(); await controlStream.ExpectSettingsAsync().DefaultTimeout(); - Http3Api.AdvanceClock(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -41,7 +41,7 @@ public async Task KeepAliveTimeout_RequestNotReceived_ConnectionClosed() var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout(); await controlStream.ExpectSettingsAsync().DefaultTimeout(); - Http3Api.AdvanceClock(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -69,7 +69,7 @@ public async Task KeepAliveTimeout_AfterRequestComplete_ConnectionClosed() await requestStream.ExpectReceiveEndOfStream(); await requestStream.OnDisposedTask.DefaultTimeout(); - Http3Api.AdvanceClock(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -101,11 +101,11 @@ await Http3Api.InitializeConnectionAsync(_ => await requestReceivedTcs.Task; - Http3Api.AdvanceClock(limits.KeepAliveTimeout); - Http3Api.AdvanceClock(limits.KeepAliveTimeout); - Http3Api.AdvanceClock(limits.KeepAliveTimeout); - Http3Api.AdvanceClock(limits.KeepAliveTimeout); - Http3Api.AdvanceClock(limits.KeepAliveTimeout); + Http3Api.AdvanceTime(limits.KeepAliveTimeout); + Http3Api.AdvanceTime(limits.KeepAliveTimeout); + Http3Api.AdvanceTime(limits.KeepAliveTimeout); + Http3Api.AdvanceTime(limits.KeepAliveTimeout); + Http3Api.AdvanceTime(limits.KeepAliveTimeout); requestFinishedTcs.SetResult(); @@ -114,7 +114,7 @@ await Http3Api.InitializeConnectionAsync(_ => await requestStream.ExpectReceiveEndOfStream(); await requestStream.OnDisposedTask.DefaultTimeout(); - Http3Api.AdvanceClock(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -122,7 +122,7 @@ await Http3Api.InitializeConnectionAsync(_ => [Fact] public async Task HEADERS_IncompleteFrameReceivedWithinRequestHeadersTimeout_StreamError() { - var now = _serviceContext.MockSystemClock.UtcNow; + var now = _serviceContext.MockTimeProvider.GetUtcNow(); var limits = _serviceContext.ServerOptions.Limits; var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, null).DefaultTimeout(); @@ -156,7 +156,7 @@ public async Task HEADERS_HeaderFrameReceivedWithinRequestHeadersTimeout_Success { Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = pendingStreamsEnabled; - var now = _serviceContext.MockSystemClock.UtcNow; + var now = _serviceContext.MockTimeProvider.GetUtcNow(); var limits = _serviceContext.ServerOptions.Limits; var headers = new[] { @@ -209,7 +209,7 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str { Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = true; - var now = _serviceContext.MockSystemClock.UtcNow; + var now = _serviceContext.MockTimeProvider.GetUtcNow(); var limits = _serviceContext.ServerOptions.Limits; await Http3Api.InitializeConnectionAsync(_noopApplication).DefaultTimeout(); @@ -235,7 +235,7 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str { Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = false; - var now = _serviceContext.MockSystemClock.UtcNow; + var now = _serviceContext.MockTimeProvider.GetUtcNow(); var limits = _serviceContext.ServerOptions.Limits; var headers = new[] { @@ -272,7 +272,7 @@ await outboundControlStream.WaitForStreamErrorAsync( [Fact] public async Task ControlStream_HeaderReceivedWithinRequestHeadersTimeout_StreamError() { - var now = _serviceContext.MockSystemClock.UtcNow; + var now = _serviceContext.MockTimeProvider.GetUtcNow(); var limits = _serviceContext.ServerOptions.Limits; await Http3Api.InitializeConnectionAsync(_noopApplication).DefaultTimeout(); @@ -297,7 +297,7 @@ public async Task ControlStream_RequestHeadersTimeoutMaxValue_ExpirationIsMaxVal { Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = pendingStreamEnabled; - var now = _serviceContext.MockSystemClock.UtcNow; + var now = _serviceContext.MockTimeProvider.GetUtcNow(); var limits = _serviceContext.ServerOptions.Limits; limits.RequestHeadersTimeout = TimeSpan.MaxValue; @@ -328,13 +328,13 @@ public async Task ControlStream_RequestHeadersTimeoutMaxValue_ExpirationIsMaxVal [Fact] public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGracePeriod() { - var mockSystemClock = _serviceContext.MockSystemClock; + var mockTimeProvider = _serviceContext.MockTimeProvider; var limits = _serviceContext.ServerOptions.Limits; // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockSystemClock.UtcNow.Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -350,11 +350,11 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP await requestStream.ExpectDataAsync(); // Don't send any more data and advance just to and then past the grace period. - Http3Api.AdvanceClock(limits.MinRequestBodyDataRate.GracePeriod); + Http3Api.AdvanceTime(limits.MinRequestBodyDataRate.GracePeriod); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceClock(TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -370,9 +370,9 @@ await Http3Api.WaitForConnectionErrorAsync( [Fact] public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() { - var now = _serviceContext.MockSystemClock.UtcNow; + var now = _serviceContext.MockTimeProvider.GetUtcNow(); var limits = _serviceContext.ServerOptions.Limits; - var mockSystemClock = _serviceContext.MockSystemClock; + var mockTimeProvider = _serviceContext.MockTimeProvider; // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinResponseDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); @@ -438,7 +438,7 @@ public async Task RunApp(HttpContext context) [Fact] public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsConnectionAfterGracePeriod() { - var mockSystemClock = _serviceContext.MockSystemClock; + var mockTimeProvider = _serviceContext.MockTimeProvider; var limits = _serviceContext.ServerOptions.Limits; // Use non-default value to ensure the min request and response rates aren't mixed up. @@ -447,7 +447,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC // Disable response buffering so "socket" backpressure is observed immediately. limits.MaxResponseBufferSize = 0; - Http3Api._timeoutControl.Initialize(mockSystemClock.UtcNow.Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); var app = new EchoAppWithNotification(); var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(app.RunApp, _browserRequestHeaders, endStream: false); @@ -458,15 +458,15 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC await app.WriteStartedTask.DefaultTimeout(); // Complete timing of the request body so we don't induce any unexpected request body rate timeouts. - Http3Api._timeoutControl.Tick(mockSystemClock.UtcNow); + Http3Api._timeoutControl.Tick(mockTimeProvider.GetUtcNow()); // Don't read data frame to induce "socket" backpressure. - Http3Api.AdvanceClock(TimeSpan.FromSeconds((requestStream.BytesReceived + _helloWorldBytes.Length) / limits.MinResponseDataRate.BytesPerSecond) + + Http3Api.AdvanceTime(TimeSpan.FromSeconds((requestStream.BytesReceived + _helloWorldBytes.Length) / limits.MinResponseDataRate.BytesPerSecond) + limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceClock(TimeSpan.FromSeconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); @@ -480,7 +480,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC [Fact] public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsConnectionAfterRateTimeout() { - var mockSystemClock = _serviceContext.MockSystemClock; + var mockTimeProvider = _serviceContext.MockTimeProvider; var limits = _serviceContext.ServerOptions.Limits; // Use non-default value to ensure the min request and response rates aren't mixed up. @@ -489,7 +489,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC // Disable response buffering so "socket" backpressure is observed immediately. limits.MaxResponseBufferSize = 0; - Http3Api._timeoutControl.Initialize(mockSystemClock.UtcNow.Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); var app = new EchoAppWithNotification(); var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(app.RunApp, _browserRequestHeaders, endStream: false); @@ -500,17 +500,17 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC await app.WriteStartedTask.DefaultTimeout(); // Complete timing of the request body so we don't induce any unexpected request body rate timeouts. - Http3Api._timeoutControl.Tick(mockSystemClock.UtcNow); + Http3Api._timeoutControl.Tick(mockTimeProvider.GetUtcNow()); var timeToWriteMaxData = TimeSpan.FromSeconds((requestStream.BytesReceived + _maxData.Length) / limits.MinResponseDataRate.BytesPerSecond) + limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5); // Don't read data frame to induce "socket" backpressure. - Http3Api.AdvanceClock(timeToWriteMaxData); + Http3Api.AdvanceTime(timeToWriteMaxData); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceClock(TimeSpan.FromSeconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); @@ -523,13 +523,13 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC [Fact] public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTimeout() { - var mockSystemClock = _serviceContext.MockSystemClock; + var mockTimeProvider = _serviceContext.MockTimeProvider; var limits = _serviceContext.ServerOptions.Limits; // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockSystemClock.UtcNow.Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -549,11 +549,11 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi var timeToReadMaxData = TimeSpan.FromSeconds(_maxData.Length / limits.MinRequestBodyDataRate.BytesPerSecond) - TimeSpan.FromSeconds(.5); // Don't send any more data and advance just to and then past the rate timeout. - Http3Api.AdvanceClock(timeToReadMaxData); + Http3Api.AdvanceTime(timeToReadMaxData); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceClock(TimeSpan.FromSeconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -569,13 +569,13 @@ await Http3Api.WaitForConnectionErrorAsync( [Fact] public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfterAdditiveRateTimeout() { - var mockSystemClock = _serviceContext.MockSystemClock; + var mockTimeProvider = _serviceContext.MockTimeProvider; var limits = _serviceContext.ServerOptions.Limits; // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockSystemClock.UtcNow.Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -604,11 +604,11 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter timeToReadMaxData -= TimeSpan.FromSeconds(.5); // Don't send any more data and advance just to and then past the rate timeout. - Http3Api.AdvanceClock(timeToReadMaxData); + Http3Api.AdvanceTime(timeToReadMaxData); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceClock(TimeSpan.FromSeconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -624,13 +624,13 @@ await Http3Api.WaitForConnectionErrorAsync( [Fact] public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNonAdditiveRateTimeout() { - var mockSystemClock = _serviceContext.MockSystemClock; + var mockTimeProvider = _serviceContext.MockTimeProvider; var limits = _serviceContext.ServerOptions.Limits; // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockSystemClock.UtcNow.Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -660,11 +660,11 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon var timeToReadMaxData = TimeSpan.FromSeconds(_maxData.Length / limits.MinRequestBodyDataRate.BytesPerSecond) - TimeSpan.FromSeconds(.5); // Don't send any more data and advance just to and then past the rate timeout. - Http3Api.AdvanceClock(timeToReadMaxData); + Http3Api.AdvanceTime(timeToReadMaxData); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceClock(TimeSpan.FromSeconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromSeconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -680,13 +680,13 @@ await Http3Api.WaitForConnectionErrorAsync( [Fact] public async Task DATA_Received_SlowlyWhenRateLimitDisabledPerRequest_DoesNotAbortConnection() { - var mockSystemClock = _serviceContext.MockSystemClock; + var mockTimeProvider = _serviceContext.MockTimeProvider; var limits = _serviceContext.ServerOptions.Limits; // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockSystemClock.UtcNow.Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); await Http3Api.InitializeConnectionAsync(context => { @@ -709,11 +709,11 @@ await Http3Api.InitializeConnectionAsync(context => await requestStream.ExpectDataAsync(); // Don't send any more data and advance just to and then past the grace period. - Http3Api.AdvanceClock(limits.MinRequestBodyDataRate.GracePeriod); + Http3Api.AdvanceTime(limits.MinRequestBodyDataRate.GracePeriod); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceClock(TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs index b994582239c3..d15f2bbcd64a 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs @@ -43,8 +43,8 @@ await connection.Send( await ReceiveResponse(connection, testContext); // Min amount of time between requests that triggers a keep-alive timeout. - testContext.MockSystemClock.UtcNow += _keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); await connection.WaitForConnectionClose(); } @@ -73,8 +73,8 @@ await connection.Send( await ReceiveResponse(connection, testContext); // Max amount of time between requests that doesn't trigger a keep-alive timeout. - testContext.MockSystemClock.UtcNow += _keepAliveTimeout + Heartbeat.Interval; - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); } } } @@ -108,8 +108,8 @@ await connection.Send( "a", ""); - testContext.MockSystemClock.UtcNow += _shortDelay; - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(_shortDelay); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); } await connection.Send( @@ -144,8 +144,8 @@ await connection.Send( for (var totalDelay = TimeSpan.Zero; totalDelay < _longDelay; totalDelay += _shortDelay) { - testContext.MockSystemClock.UtcNow += _shortDelay; - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(_shortDelay); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); } cts.Cancel(); @@ -175,8 +175,8 @@ private async Task ConnectionTimesOutWhenOpenedButNoRequestSent() await connection.TransportConnection.WaitForReadTask; // Min amount of time between requests that triggers a keep-alive timeout. - testContext.MockSystemClock.UtcNow += _keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); await connection.WaitForConnectionClose(); } @@ -211,8 +211,8 @@ await connection.Receive( for (var totalDelay = TimeSpan.Zero; totalDelay < _longDelay; totalDelay += _shortDelay) { - testContext.MockSystemClock.UtcNow += _shortDelay; - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(_shortDelay); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); } cts.Cancel(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs index 0dab106577ae..fe5e1aad77e2 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs @@ -31,22 +31,22 @@ public async Task RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRat context.Features.Get().MinDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: gracePeriod); - // The server must call Request.Body.ReadAsync() *before* the test sets systemClock.UtcNow (which is triggered by the - // server calling appRunningEvent.SetResult(null)). If systemClock.UtcNow is set first, it's possible for the test to fail + // The server must call Request.Body.ReadAsync() *before* the test sets timeProvider.UtcNow (which is triggered by the + // server calling appRunningEvent.SetResult(null)). If timeProvider.UtcNow is set first, it's possible for the test to fail // due to the following race condition: // - // 1. [test] systemClock.UtcNow += gracePeriod + TimeSpan.FromSeconds(1); + // 1. [test] timeProvider.UtcNow += gracePeriod + TimeSpan.FromSeconds(1); // 2. [server] Heartbeat._timer is triggered, which calls HttpConnection.Tick() // 3. [server] HttpConnection.Tick() calls HttpConnection.CheckForReadDataRateTimeout() // 4. [server] HttpConnection.CheckForReadDataRateTimeout() is a no-op, since _readTimingEnabled is false, // since Request.Body.ReadAsync() has not been called yet // 5. [server] HttpConnection.Tick() sets _lastTimestamp = timestamp // 6. [server] Request.Body.ReadAsync() is called - // 6. [test] systemClock.UtcNow is never updated again, so server timestamp is never updated, + // 6. [test] timeProvider.UtcNow is never updated again, so server timestamp is never updated, // so HttpConnection.CheckForReadDataRateTimeout() is always a no-op until test fails // // This is a pretty tight race, since the once-per-second Heartbeat._timer needs to fire between the test updating - // systemClock.UtcNow and the server calling Request.Body.ReadAsync(). But it happened often enough to cause + // timeProvider.UtcNow and the server calling Request.Body.ReadAsync(). But it happened often enough to cause // test flakiness in our CI (https://github.com/aspnet/KestrelHttpServer/issues/2539). // // For verification, I was able to induce the race by adding a sleep in the RequestDelegate: @@ -70,11 +70,11 @@ await connection.Send( await appRunningEvent.Task.DefaultTimeout(); - // Advance the clock gracePeriod + TimeSpan.FromSeconds(1) + // Advance the timeProvider gracePeriod + TimeSpan.FromSeconds(1) for (var i = 0; i < 6; i++) { - serviceContext.MockSystemClock.UtcNow += TimeSpan.FromSeconds(1); - heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow); + serviceContext.MockTimeProvider.Advance(TimeSpan.FromSeconds(1)); + heartbeatManager.OnHeartbeat(serviceContext.TimeProvider.GetUtcNow()); } await connection.Receive( @@ -98,9 +98,9 @@ public async Task RequestTimesOutWhenNotDrainedWithinDrainTimeoutPeriod() serviceContext.InitializeHeartbeat(); // Ensure there's still a constant date header value. - var clock = new MockSystemClock(); + var timeProvider = new MockTimeProvider(); var date = new DateHeaderValueManager(); - date.OnHeartbeat(clock.UtcNow); + date.OnHeartbeat(timeProvider.GetUtcNow()); serviceContext.DateHeaderValueManager = date; var appRunningEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -190,8 +190,8 @@ await connection.Send( // Advance the clock gracePeriod + TimeSpan.FromSeconds(1) for (var i = 0; i < 6; i++) { - serviceContext.MockSystemClock.UtcNow += TimeSpan.FromSeconds(1); - heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow); + serviceContext.MockTimeProvider.Advance(TimeSpan.FromSeconds(1)); + heartbeatManager.OnHeartbeat(serviceContext.TimeProvider.GetUtcNow()); } await exceptionSwallowedTcs.Task.DefaultTimeout(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs index 4adb3e6e1f60..6ac91ea45c3b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs @@ -38,8 +38,8 @@ await connection.Send( headers); // Min amount of time between requests that triggers a request headers timeout. - testContext.MockSystemClock.UtcNow += RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); await ReceiveTimeoutResponse(connection, testContext); } @@ -66,8 +66,8 @@ await connection.Send( ""); // Min amount of time between requests that triggers a request headers timeout. - testContext.MockSystemClock.UtcNow += RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); await connection.Send( "a"); @@ -94,8 +94,8 @@ public async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(string reque await connection.Send(requestLine); // Min amount of time between requests that triggers a request headers timeout. - testContext.MockSystemClock.UtcNow += RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1); - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); await ReceiveTimeoutResponse(connection, testContext); } @@ -121,8 +121,8 @@ public async Task TimeoutNotResetOnEachRequestLineCharacterReceived() { await connection.Send(ch.ToString()); - testContext.MockSystemClock.UtcNow += ShortDelay; - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(ShortDelay); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); } await ReceiveTimeoutResponse(connection, testContext); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index 71b065eede03..948741cbd4b0 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -1664,8 +1664,8 @@ await connection.Send( await appEvent.Task.DefaultTimeout(); - serviceContext.MockSystemClock.UtcNow += TimeSpan.FromSeconds(5); - heartbeatManager.OnHeartbeat(serviceContext.SystemClock.UtcNow); + serviceContext.MockTimeProvider.Advance(TimeSpan.FromSeconds(5)); + heartbeatManager.OnHeartbeat(serviceContext.TimeProvider.GetUtcNow()); delayEvent.SetResult(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs index f156b1f41046..587682b7ccfa 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs @@ -62,17 +62,17 @@ await connection.Send( // Advance the clock to the grace period for (var i = 0; i < 2; i++) { - testContext.MockSystemClock.UtcNow += TimeSpan.FromSeconds(1); - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(TimeSpan.FromSeconds(1)); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); } - testContext.MockSystemClock.UtcNow += Heartbeat.Interval - TimeSpan.FromSeconds(.5); - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(Heartbeat.Interval - TimeSpan.FromSeconds(.5)); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); Assert.Null(transportConnection.AbortReason); - testContext.MockSystemClock.UtcNow += TimeSpan.FromSeconds(1); - heartbeatManager.OnHeartbeat(testContext.SystemClock.UtcNow); + testContext.MockTimeProvider.Advance(TimeSpan.FromSeconds(1)); + heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); Assert.NotNull(transportConnection.AbortReason); Assert.Equal(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied, transportConnection.AbortReason.Message); diff --git a/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj b/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj index 5a39a90d9b4e..e38f1828db2f 100644 --- a/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj +++ b/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj b/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj index 3e8bcdf17ec0..f21ed3ba48fe 100644 --- a/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj @@ -13,7 +13,7 @@ - + From 5db6804f30c884d7c4ca83e11acb553517b802aa Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 4 May 2023 15:45:37 -0700 Subject: [PATCH 02/17] Use Timestamps for stream pool expiration, pr feedback --- .../src/Internal/Http2/Http2Connection.cs | 10 ++--- .../src/Internal/QuicConnectionContext.cs | 10 +++-- .../test/QuicConnectionContextTests.cs | 38 +++++++++---------- .../Kestrel/shared/test/MockTimeProvider.cs | 5 +++ 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 4006062071df..a50de5337762 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -44,7 +44,7 @@ internal sealed partial class Http2Connection : IHttp2StreamLifetimeHandler, IHt private const int InitialStreamPoolSize = 5; private const int MaxStreamPoolSize = 100; - private const long StreamPoolExpiryTicks = TimeSpan.TicksPerSecond * 5; + private const long StreamPoolExpirySeconds = 5; private readonly HttpConnectionContext _context; private readonly Http2FrameWriter _frameWriter; @@ -240,7 +240,7 @@ public async Task ProcessRequestsAsync(IHttpApplication appl if (result.IsCanceled) { // Heartbeat will cancel ReadAsync and trigger expiring unused streams from pool. - StreamPool.RemoveExpired(TimeProvider.GetUtcNow().Ticks); + StreamPool.RemoveExpired(TimeProvider.GetTimestamp()); } try @@ -1255,7 +1255,7 @@ void IHttp2StreamLifetimeHandler.OnStreamCompleted(Http2Stream stream) private void UpdateCompletedStreams() { Http2Stream? firstRequedStream = null; - var now = TimeProvider.GetUtcNow().Ticks; + var now = TimeProvider.GetTimestamp(); while (_completedStreams.TryDequeue(out var stream)) { @@ -1270,7 +1270,7 @@ private void UpdateCompletedStreams() if (stream.DrainExpirationTicks == default) { _serverActiveStreamCount--; - stream.DrainExpirationTicks = now + Constants.RequestBodyDrainTimeout.Ticks; + stream.DrainExpirationTicks = now + (long)(Constants.RequestBodyDrainTimeout.TotalSeconds * TimeProvider.TimestampFrequency); } if (stream.EndStreamReceived || stream.RstStreamReceived || stream.DrainExpirationTicks < now) @@ -1304,7 +1304,7 @@ private void RemoveStream(Http2Stream stream) // Pool and reuse the stream if it finished in a graceful state and there is space in the pool. // This property is used to remove unused streams from the pool - stream.DrainExpirationTicks = TimeProvider.GetUtcNow().Ticks + StreamPoolExpiryTicks; + stream.DrainExpirationTicks = TimeProvider.GetTimestamp() + StreamPoolExpirySeconds * TimeProvider.TimestampFrequency; StreamPool.Push(stream); } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs index 268ac3d5814a..f2db39c37a0d 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs @@ -33,7 +33,7 @@ internal partial class QuicConnectionContext : TransportMultiplexedConnection internal const int InitialStreamPoolSize = 5; internal const int MaxStreamPoolSize = 100; - internal const long StreamPoolExpiryTicks = TimeSpan.TicksPerSecond * 5; + internal const long StreamPoolExpirySeconds = 5; public QuicConnectionContext(QuicConnection connection, QuicTransportContext context) { @@ -240,6 +240,8 @@ internal bool TryReturnStream(QuicStreamContext stream) { lock (_poolLock) { + var timeProvider = _context.Options.TimeProvider; + if (!_streamPoolHeartbeatInitialized) { // Heartbeat feature is added to connection features by Kestrel. @@ -255,7 +257,7 @@ internal bool TryReturnStream(QuicStreamContext stream) heartbeatFeature.OnHeartbeat(static state => ((QuicConnectionContext)state).RemoveExpiredStreams(), this); // Set ticks for the first time. Ticks are then updated in heartbeat. - var now = _context.Options.TimeProvider.GetUtcNow().Ticks; + var now = timeProvider.GetTimestamp(); Volatile.Write(ref _heartbeatTicks, now); _streamPoolHeartbeatInitialized = true; @@ -263,7 +265,7 @@ internal bool TryReturnStream(QuicStreamContext stream) if (stream.CanReuse && StreamPool.Count < MaxStreamPoolSize) { - stream.PoolExpirationTicks = Volatile.Read(ref _heartbeatTicks) + StreamPoolExpiryTicks; + stream.PoolExpirationTicks = Volatile.Read(ref _heartbeatTicks) + StreamPoolExpirySeconds * timeProvider.TimestampFrequency; StreamPool.Push(stream); QuicLog.StreamPooled(_log, stream); @@ -284,7 +286,7 @@ private void RemoveExpiredStreams() lock (_poolLock) { // Update ticks on heartbeat. A precise value isn't necessary. - var now = _context.Options.TimeProvider.GetUtcNow().Ticks; + var now = _context.Options.TimeProvider.GetTimestamp(); Volatile.Write(ref _heartbeatTicks, now); StreamPool.RemoveExpired(now); diff --git a/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs b/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs index 01ff7af3ffa8..3a342672b6d0 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Diagnostics; using System.Net.Http; using System.Net.Quic; using System.Text; @@ -499,14 +500,12 @@ public async Task StreamPool_StreamAbortedOnClientAndServer_NotPooled() [ConditionalFact] [MsQuicSupported] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/37862")] public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() { // Arrange using var httpEventSource = new HttpEventSourceListener(LoggerFactory); - var now = new DateTimeOffset(2021, 7, 6, 12, 0, 0, TimeSpan.Zero); - var testTimeProvider = new TestTimeProvider(now); + var testTimeProvider = new TestTimeProvider(123456789l); await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, testTimeProvider); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); @@ -526,10 +525,9 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() Assert.Equal(1, quicConnectionContext.StreamPool.Count); QuicStreamContext pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); - Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks); + Assert.Equal(testTimeProvider.GetTimestamp() + QuicConnectionContext.StreamPoolExpirySeconds * testTimeProvider.TimestampFrequency, pooledStream.PoolExpirationTicks); - now = now.AddMilliseconds(100); - testTimeProvider.SetUtcNow(now); + testTimeProvider.Advance((long)(0.1 * testTimeProvider.TimestampFrequency)); testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); @@ -539,18 +537,16 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() Assert.Equal(1, quicConnectionContext.StreamPool.Count); pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); - Assert.Equal(now.Ticks + QuicConnectionContext.StreamPoolExpiryTicks, pooledStream.PoolExpirationTicks); + Assert.Equal(testTimeProvider.GetTimestamp() + QuicConnectionContext.StreamPoolExpirySeconds * testTimeProvider.TimestampFrequency, pooledStream.PoolExpirationTicks); Assert.Same(stream1, stream2); - now = now.AddTicks(QuicConnectionContext.StreamPoolExpiryTicks); - testTimeProvider.SetUtcNow(now); + testTimeProvider.Advance(QuicConnectionContext.StreamPoolExpirySeconds * testTimeProvider.TimestampFrequency); testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); - now = now.AddTicks(1); - testTimeProvider.SetUtcNow(now); + testTimeProvider.Advance(1); testHeartbeatFeature.RaiseHeartbeat(); // Removed. Assert.Equal(0, quicConnectionContext.StreamPool.Count); @@ -722,22 +718,24 @@ private record RequestState( private class TestTimeProvider : TimeProvider { - private DateTimeOffset _now; + private long _now; - public TestTimeProvider(DateTimeOffset now) + public TestTimeProvider(long now) { _now = now; } public override DateTimeOffset GetUtcNow() - { - return _now; - } + => throw new NotImplementedException(); - public void SetUtcNow(DateTimeOffset now) - { - _now = now; - } + public override long TimestampFrequency => Stopwatch.Frequency; + + public override long GetTimestamp() => _now; + + public void Advance(long ticks) => _now += ticks; + + public override ITimer CreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) + => throw new NotImplementedException(); } private class TestHeartbeatFeature : IConnectionHeartbeatFeature diff --git a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs index 25d343823d0a..abac93c585b0 100644 --- a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs +++ b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs @@ -32,6 +32,8 @@ public void SetUtcNow(DateTimeOffset now) public override long GetTimestamp() => GetUtcNow().Ticks; + public override long TimestampFrequency => TimeSpan.TicksPerSecond; + public int UtcNowCalled { get; private set; } public void Advance(TimeSpan timeSpan) @@ -43,4 +45,7 @@ private long NextLong(long minValue, long maxValue) { return (long)(Random.Shared.NextDouble() * (maxValue - minValue) + minValue); } + + public override ITimer CreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) + => throw new NotImplementedException(); } From c1c724e52dd7ac9d28f65b28c560a3344d2c235b Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 4 May 2023 16:23:34 -0700 Subject: [PATCH 03/17] Change Http2KeepAlives to use Timestamps --- .../Core/src/Internal/Http2/Http2KeepAlive.cs | 27 +++++++++---------- .../test/QuicConnectionContextTests.cs | 2 +- .../Kestrel/shared/test/MockTimeProvider.cs | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs index 7dab4259ed84..5bd4c0f2a7b3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs @@ -18,8 +18,8 @@ internal sealed class Http2KeepAlive // An empty ping payload internal static readonly ReadOnlySequence PingPayload = new ReadOnlySequence(new byte[8]); - private readonly TimeSpan _keepAliveInterval; - private readonly TimeSpan _keepAliveTimeout; + private readonly long _keepAliveInterval; + private readonly long _keepAliveTimeout; private readonly TimeProvider _timeProvider; private long _lastFrameReceivedTimestamp; private long _pingSentTimestamp; @@ -29,20 +29,20 @@ internal sealed class Http2KeepAlive public Http2KeepAlive(TimeSpan keepAliveInterval, TimeSpan keepAliveTimeout, TimeProvider timeProvider) { - _keepAliveInterval = keepAliveInterval; - _keepAliveTimeout = keepAliveTimeout; + _keepAliveInterval = (long)(keepAliveInterval.TotalSeconds * timeProvider.TimestampFrequency); + _keepAliveTimeout = keepAliveTimeout == TimeSpan.MaxValue ? long.MaxValue + : (long)(keepAliveTimeout.TotalSeconds * timeProvider.TimestampFrequency); _timeProvider = timeProvider; } public KeepAliveState ProcessKeepAlive(bool frameReceived) { - var timestamp = _timeProvider.GetUtcNow().Ticks; + var timestamp = _timeProvider.GetTimestamp(); if (frameReceived) { - // System clock only has 1 second of precision, so the clock could be up to 1 second in the past. - // To err on the side of caution, add a second to the clock when calculating the ping sent time. - _lastFrameReceivedTimestamp = timestamp + TimeSpan.TicksPerSecond; + // To err on the side of caution, add a second to the time when calculating the ping sent time. + _lastFrameReceivedTimestamp = timestamp + _timeProvider.TimestampFrequency; // Any frame received after the keep alive interval is exceeded resets the state back to none. if (_state == KeepAliveState.PingSent) @@ -57,23 +57,22 @@ public KeepAliveState ProcessKeepAlive(bool frameReceived) { case KeepAliveState.None: // Check whether keep alive interval has passed since last frame received - if (timestamp > (_lastFrameReceivedTimestamp + _keepAliveInterval.Ticks)) + if (timestamp > (_lastFrameReceivedTimestamp + _keepAliveInterval)) { // Ping will be sent immeditely after this method finishes. // Set the status directly to ping sent and set the timestamp _state = KeepAliveState.PingSent; - // System clock only has 1 second of precision, so the clock could be up to 1 second in the past. - // To err on the side of caution, add a second to the clock when calculating the ping sent time. - _pingSentTimestamp = _timeProvider.GetUtcNow().Ticks + TimeSpan.TicksPerSecond; + // To err on the side of caution, add a second to the time when calculating the ping sent time. + _pingSentTimestamp = timestamp + _timeProvider.TimestampFrequency; // Indicate that the ping needs to be sent. This is only returned once return KeepAliveState.SendPing; } break; case KeepAliveState.PingSent: - if (_keepAliveTimeout != TimeSpan.MaxValue) + if (_keepAliveTimeout != long.MaxValue) { - if (timestamp > (_pingSentTimestamp + _keepAliveTimeout.Ticks)) + if (timestamp > (_pingSentTimestamp + _keepAliveTimeout)) { _state = KeepAliveState.Timeout; } diff --git a/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs b/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs index 3a342672b6d0..3cc65608b473 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs @@ -505,7 +505,7 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() // Arrange using var httpEventSource = new HttpEventSourceListener(LoggerFactory); - var testTimeProvider = new TestTimeProvider(123456789l); + var testTimeProvider = new TestTimeProvider(123456789L); await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, testTimeProvider); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); diff --git a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs index abac93c585b0..69c6e1d540ba 100644 --- a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs +++ b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs @@ -30,7 +30,7 @@ public void SetUtcNow(DateTimeOffset now) Interlocked.Exchange(ref _utcNowTicks, now.Ticks); } - public override long GetTimestamp() => GetUtcNow().Ticks; + public override long GetTimestamp() => Interlocked.Read(ref _utcNowTicks); public override long TimestampFrequency => TimeSpan.TicksPerSecond; From 7c93c51638b51978c4970ca7335933de9551a18f Mon Sep 17 00:00:00 2001 From: Chris R Date: Mon, 8 May 2023 12:00:13 -0700 Subject: [PATCH 04/17] Convert TimeoutControl to timestamps --- .../src/Internal/Http2/Http2Connection.cs | 2 +- .../Core/src/Internal/Http2/Http2KeepAlive.cs | 4 +- .../Core/src/Internal/HttpConnection.cs | 8 +- .../Infrastructure/ITimeoutControl.cs | 2 +- .../Infrastructure/TimeSpanExtensions.cs | 12 ++ .../Internal/Infrastructure/TimeoutControl.cs | 34 +++-- .../Kestrel/Core/test/StartLineTests.cs | 2 +- .../Kestrel/Core/test/TimeoutControlTests.cs | 130 +++++++++--------- .../Http1ConnectionBenchmark.cs | 2 +- ...Http1ConnectionParsingOverheadBenchmark.cs | 2 +- .../Http1LargeWritingBenchmark.cs | 2 +- .../Microbenchmarks/Http1ReadingBenchmark.cs | 2 +- .../Microbenchmarks/Http1WritingBenchmark.cs | 2 +- .../Mocks/MockTimeoutControl.cs | 2 +- .../RequestParsingBenchmark.cs | 2 +- .../shared/test/Http3/Http3InMemory.cs | 14 +- .../Kestrel/shared/test/MockTimeProvider.cs | 7 +- .../Http2/Http2TestBase.cs | 6 +- .../Http3/Http3TimeoutTests.cs | 18 +-- 19 files changed, 136 insertions(+), 117 deletions(-) create mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index a50de5337762..7341c7d18f0b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -1270,7 +1270,7 @@ private void UpdateCompletedStreams() if (stream.DrainExpirationTicks == default) { _serverActiveStreamCount--; - stream.DrainExpirationTicks = now + (long)(Constants.RequestBodyDrainTimeout.TotalSeconds * TimeProvider.TimestampFrequency); + stream.DrainExpirationTicks = now + Constants.RequestBodyDrainTimeout.ToTicks(TimeProvider.TimestampFrequency); } if (stream.EndStreamReceived || stream.RstStreamReceived || stream.DrainExpirationTicks < now) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs index 5bd4c0f2a7b3..7cfdcd55cfbe 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs @@ -29,9 +29,9 @@ internal sealed class Http2KeepAlive public Http2KeepAlive(TimeSpan keepAliveInterval, TimeSpan keepAliveTimeout, TimeProvider timeProvider) { - _keepAliveInterval = (long)(keepAliveInterval.TotalSeconds * timeProvider.TimestampFrequency); + _keepAliveInterval = keepAliveInterval.ToTicks(timeProvider.TimestampFrequency); _keepAliveTimeout = keepAliveTimeout == TimeSpan.MaxValue ? long.MaxValue - : (long)(keepAliveTimeout.TotalSeconds * timeProvider.TimestampFrequency); + : keepAliveTimeout.ToTicks(timeProvider.TimestampFrequency); _timeProvider = timeProvider; } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 29bd16c41cb3..c7b78886a936 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -36,7 +36,7 @@ public HttpConnection(BaseHttpConnectionContext context) _context = context; _timeProvider = _context.ServiceContext.TimeProvider; - _timeoutControl = new TimeoutControl(this); + _timeoutControl = new TimeoutControl(this, _timeProvider.TimestampFrequency); // Tests override the timeout control sometimes _context.TimeoutControl ??= _timeoutControl; @@ -49,7 +49,7 @@ public async Task ProcessRequestsAsync(IHttpApplication http try { // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. - _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); + _timeoutControl.Initialize(_timeProvider.GetTimestamp()); IRequestProcessor? requestProcessor = null; @@ -236,9 +236,9 @@ private void Tick() return; } - // It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat. var now = _timeProvider.GetUtcNow(); - _timeoutControl.Tick(now); + var ticks = _timeProvider.GetTimestamp(); + _timeoutControl.Tick(ticks); _requestProcessor!.Tick(now); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs index aba73fe6d688..0df1623c7ef9 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs @@ -14,7 +14,7 @@ internal interface ITimeoutControl void CancelTimeout(); void InitializeHttp2(InputFlowControl connectionInputFlowControl); - void Tick(DateTimeOffset now); + void Tick(long now); void StartRequestBody(MinDataRate minRate); void StopRequestBody(); diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs new file mode 100644 index 000000000000..d5fbad90dbf0 --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System; + +internal static class TimeSpanExtensions +{ + public static long ToTicks(this TimeSpan timeSpan, long tickFrequency) + { + return (long)(timeSpan.TotalSeconds * tickFrequency); + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs index 7ad002a1fd6e..39b700e11d88 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs @@ -11,11 +11,14 @@ internal sealed class TimeoutControl : ITimeoutControl, IConnectionTimeoutFeatur { private readonly ITimeoutHandler _timeoutHandler; + private readonly long _heartbeatIntervalTicks; + private readonly long _tickFrequency; private long _lastTimestamp; private long _timeoutTimestamp = long.MaxValue; private readonly object _readTimingLock = new object(); private MinDataRate? _minReadRate; + private long _minReadRateGracePeriodTicks; private bool _readTimingEnabled; private bool _readTimingPauseRequested; private long _readTimingElapsedTicks; @@ -29,9 +32,11 @@ internal sealed class TimeoutControl : ITimeoutControl, IConnectionTimeoutFeatur private int _concurrentAwaitingWrites; private long _writeTimingTimeoutTimestamp; - public TimeoutControl(ITimeoutHandler timeoutHandler) + public TimeoutControl(ITimeoutHandler timeoutHandler, long tickFrequency) { _timeoutHandler = timeoutHandler; + _tickFrequency = tickFrequency; + _heartbeatIntervalTicks = Heartbeat.Interval.ToTicks(_tickFrequency); } public TimeoutReason TimerReason { get; private set; } @@ -43,9 +48,9 @@ internal void Initialize(long nowTicks) _lastTimestamp = nowTicks; } - public void Tick(DateTimeOffset now) + public void Tick(long now) { - var timestamp = now.Ticks; + var timestamp = now; CheckForTimeout(timestamp); CheckForReadDataRateTimeout(timestamp); @@ -108,13 +113,13 @@ private void CheckForReadDataRateTimeout(long timestamp) // Assume overly long tick intervals are the result of server resource starvation. // Don't count extra time between ticks against the rate limit. - _readTimingElapsedTicks += Math.Min(timestamp - _lastTimestamp, Heartbeat.Interval.Ticks); + _readTimingElapsedTicks += Math.Min(timestamp - _lastTimestamp, _heartbeatIntervalTicks); Debug.Assert(_minReadRate != null); - if (_minReadRate.BytesPerSecond > 0 && _readTimingElapsedTicks > _minReadRate.GracePeriod.Ticks) + if (_minReadRate.BytesPerSecond > 0 && _readTimingElapsedTicks > _minReadRateGracePeriodTicks) { - var elapsedSeconds = (double)_readTimingElapsedTicks / TimeSpan.TicksPerSecond; + var elapsedSeconds = (double)_readTimingElapsedTicks / _tickFrequency; var rate = _readTimingBytesRead / elapsedSeconds; timeout = rate < _minReadRate.BytesPerSecond && !Debugger.IsAttached; @@ -145,7 +150,7 @@ private void CheckForWriteDataRateTimeout(long timestamp) { // Assume overly long tick intervals are the result of server resource starvation. // Don't count extra time between ticks against the rate limit. - var extraTimeForTick = timestamp - _lastTimestamp - Heartbeat.Interval.Ticks; + var extraTimeForTick = timestamp - _lastTimestamp - _heartbeatIntervalTicks; if (extraTimeForTick > 0) { @@ -186,7 +191,7 @@ private void AssignTimeout(long ticks, TimeoutReason timeoutReason) TimerReason = timeoutReason; // Add Heartbeat.Interval since this can be called right before the next heartbeat. - Interlocked.Exchange(ref _timeoutTimestamp, Interlocked.Read(ref _lastTimestamp) + ticks + Heartbeat.Interval.Ticks); + Interlocked.Exchange(ref _timeoutTimestamp, Interlocked.Read(ref _lastTimestamp) + ticks + _heartbeatIntervalTicks); } public void InitializeHttp2(InputFlowControl connectionInputFlowControl) @@ -202,6 +207,7 @@ public void StartRequestBody(MinDataRate minRate) Debug.Assert(_concurrentIncompleteRequestBodies == 0 || minRate == _minReadRate, "Multiple simultaneous read data rates are not supported."); _minReadRate = minRate; + _minReadRateGracePeriodTicks = minRate.GracePeriod.ToTicks(_tickFrequency); _concurrentIncompleteRequestBodies++; if (_concurrentIncompleteRequestBodies == 1) @@ -282,14 +288,14 @@ public void BytesWrittenToBuffer(MinDataRate minRate, long count) lock (_writeTimingLock) { // Add Heartbeat.Interval since this can be called right before the next heartbeat. - var currentTimeUpperBound = Interlocked.Read(ref _lastTimestamp) + Heartbeat.Interval.Ticks; - var ticksToCompleteWriteAtMinRate = TimeSpan.FromSeconds(count / minRate.BytesPerSecond).Ticks; + var currentTimeUpperBound = Interlocked.Read(ref _lastTimestamp) + _heartbeatIntervalTicks; + var ticksToCompleteWriteAtMinRate = TimeSpan.FromSeconds(count / minRate.BytesPerSecond).ToTicks(_tickFrequency); // If ticksToCompleteWriteAtMinRate is less than the configured grace period, // allow that write to take up to the grace period to complete. Only add the grace period // to the current time and not to any accumulated timeout. var singleWriteTimeoutTimestamp = currentTimeUpperBound + Math.Max( - minRate.GracePeriod.Ticks, + minRate.GracePeriod.ToTicks(_tickFrequency), ticksToCompleteWriteAtMinRate); // Don't penalize a connection for completing previous writes more quickly than required. @@ -316,7 +322,7 @@ void IConnectionTimeoutFeature.SetTimeout(TimeSpan timeSpan) throw new InvalidOperationException(CoreStrings.ConcurrentTimeoutsNotSupported); } - SetTimeout(timeSpan.Ticks, TimeoutReason.TimeoutFeature); + SetTimeout(timeSpan.ToTicks(_tickFrequency), TimeoutReason.TimeoutFeature); } void IConnectionTimeoutFeature.ResetTimeout(TimeSpan timeSpan) @@ -326,13 +332,13 @@ void IConnectionTimeoutFeature.ResetTimeout(TimeSpan timeSpan) throw new ArgumentException(CoreStrings.PositiveFiniteTimeSpanRequired, nameof(timeSpan)); } - ResetTimeout(timeSpan.Ticks, TimeoutReason.TimeoutFeature); + ResetTimeout(timeSpan.ToTicks(_tickFrequency), TimeoutReason.TimeoutFeature); } public long GetResponseDrainDeadline(long ticks, MinDataRate minRate) { // On grace period overflow, use max value. - var gracePeriod = ticks + minRate.GracePeriod.Ticks; + var gracePeriod = ticks + minRate.GracePeriod.ToTicks(_tickFrequency); gracePeriod = gracePeriod >= 0 ? gracePeriod : long.MaxValue; return Math.Max(_writeTimingTimeoutTimestamp, gracePeriod); diff --git a/src/Servers/Kestrel/Core/test/StartLineTests.cs b/src/Servers/Kestrel/Core/test/StartLineTests.cs index 701561f367df..d87269d7cb62 100644 --- a/src/Servers/Kestrel/Core/test/StartLineTests.cs +++ b/src/Servers/Kestrel/Core/test/StartLineTests.cs @@ -532,7 +532,7 @@ public StartLineTests() serviceContext: serviceContext, connectionContext: Mock.Of(), transport: Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null), + timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), memoryPool: MemoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs index ba9e283fd0b6..6e02880e6d14 100644 --- a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs +++ b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs @@ -1,12 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Testing; using Moq; -using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; @@ -19,8 +17,8 @@ public class TimeoutControlTests public TimeoutControlTests() { _mockTimeoutHandler = new Mock(); - _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object); _timeProvider = new MockTimeProvider(); + _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object, _timeProvider.TimestampFrequency); } [Fact] @@ -30,10 +28,10 @@ public void DoesNotTimeOutWhenDebuggerIsAttached() mockDebugger.SetupGet(g => g.IsAttached).Returns(true); _timeoutControl.Debugger = mockDebugger.Object; - var now = DateTimeOffset.Now; - _timeoutControl.Initialize(now.Ticks); + var now = _timeProvider.GetTimestamp(); + _timeoutControl.Initialize(now); _timeoutControl.SetTimeout(1, TimeoutReason.RequestHeaders); - _timeoutControl.Tick(now.AddTicks(2).Add(Heartbeat.Interval)); + _timeoutControl.Tick(now + 2 + Heartbeat.Interval.ToTicks(_timeProvider.TimestampFrequency)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); } @@ -65,14 +63,14 @@ public void RequestBodyMinimumDataRateNotEnforcedDuringGracePeriod() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - var now = DateTimeOffset.UtcNow; - _timeoutControl.Initialize(now.Ticks); + var now = _timeProvider.GetTimestamp(); + _timeoutControl.Initialize(now); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); // Tick during grace period w/ low data rate - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(10); _timeoutControl.Tick(now); @@ -80,9 +78,9 @@ public void RequestBodyMinimumDataRateNotEnforcedDuringGracePeriod() _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Tick after grace period w/ low data rate - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.Tick(now); - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(10); _timeoutControl.Tick(now); @@ -97,21 +95,21 @@ public void RequestBodyDataRateIsAveragedOverTimeSpentReadingRequestBody() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: gracePeriod); // Initialize timestamp - var now = DateTimeOffset.UtcNow; - _timeoutControl.Initialize(now.Ticks); + var now = _timeProvider.GetTimestamp(); + _timeoutControl.Initialize(now); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); // Set base data rate to 200 bytes/second - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.Tick(now); - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(400); _timeoutControl.Tick(now); // Data rate: 200 bytes/second - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(200); _timeoutControl.Tick(now); @@ -119,7 +117,7 @@ public void RequestBodyDataRateIsAveragedOverTimeSpentReadingRequestBody() _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Data rate: 150 bytes/second - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(0); _timeoutControl.Tick(now); @@ -127,7 +125,7 @@ public void RequestBodyDataRateIsAveragedOverTimeSpentReadingRequestBody() _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Data rate: 120 bytes/second - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(0); _timeoutControl.Tick(now); @@ -135,7 +133,7 @@ public void RequestBodyDataRateIsAveragedOverTimeSpentReadingRequestBody() _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Data rate: 100 bytes/second - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(0); _timeoutControl.Tick(now); @@ -143,7 +141,7 @@ public void RequestBodyDataRateIsAveragedOverTimeSpentReadingRequestBody() _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Data rate: ~85 bytes/second - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(0); _timeoutControl.Tick(now); @@ -157,19 +155,19 @@ public void RequestBodyDataRateNotComputedOnPausedTime() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); + _timeoutControl.Initialize(_timeProvider.GetTimestamp()); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); // Tick at 3s, expected counted time is 3s, expected data rate is 200 bytes/second _timeProvider.Advance(TimeSpan.FromSeconds(1)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); _timeProvider.Advance(TimeSpan.FromSeconds(1)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); _timeProvider.Advance(TimeSpan.FromSeconds(1)); _timeoutControl.BytesRead(600); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Pause at 3.5s _timeProvider.Advance(TimeSpan.FromSeconds(0.5)); @@ -177,11 +175,11 @@ public void RequestBodyDataRateNotComputedOnPausedTime() // Tick at 4s, expected counted time is 4s (first tick after pause goes through), expected data rate is 150 bytes/second _timeProvider.Advance(TimeSpan.FromSeconds(0.5)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Tick at 6s, expected counted time is 4s, expected data rate is 150 bytes/second _timeProvider.Advance(TimeSpan.FromSeconds(2)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); @@ -192,16 +190,16 @@ public void RequestBodyDataRateNotComputedOnPausedTime() // Tick at 9s, expected counted time is 6s, expected data rate is 100 bytes/second _timeProvider.Advance(TimeSpan.FromSeconds(1.0)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); _timeProvider.Advance(TimeSpan.FromSeconds(.5)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Tick at 10s, expected counted time is 7s, expected data rate drops below 100 bytes/second _timeProvider.Advance(TimeSpan.FromSeconds(1)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -213,16 +211,16 @@ public void ReadTimingNotPausedWhenResumeCalledBeforeNextTick() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); + _timeoutControl.Initialize(_timeProvider.GetTimestamp()); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); // Tick at 2s, expected counted time is 2s, expected data rate is 100 bytes/second _timeProvider.Advance(TimeSpan.FromSeconds(1)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); _timeProvider.Advance(TimeSpan.FromSeconds(1)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); _timeoutControl.BytesRead(200); // Not timed out @@ -239,14 +237,14 @@ public void ReadTimingNotPausedWhenResumeCalledBeforeNextTick() // Tick at 3s, expected counted time is 3s, expected data rate is 100 bytes/second _timeProvider.Advance(TimeSpan.FromSeconds(0.5)); _timeoutControl.BytesRead(100); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Tick at 4s, expected counted time is 4s, expected data rate drops below 100 bytes/second _timeProvider.Advance(TimeSpan.FromSeconds(1)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -261,24 +259,24 @@ public void ReadTimingNotEnforcedWhenTimeoutIsSet() var startTime = _timeProvider.GetUtcNow(); // Initialize timestamp - _timeoutControl.Initialize(startTime.Ticks); + _timeoutControl.Initialize(_timeProvider.GetTimestamp()); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); - _timeoutControl.SetTimeout(timeout.Ticks, TimeoutReason.RequestBodyDrain); + _timeoutControl.SetTimeout(timeout.ToTicks(_timeProvider.TimestampFrequency), TimeoutReason.RequestBodyDrain); // Tick beyond grace period with low data rate _timeProvider.Advance(TimeSpan.FromSeconds(3)); _timeoutControl.BytesRead(1); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Tick just past timeout period, adjusted by Heartbeat.Interval _timeProvider.SetUtcNow(startTime + timeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.RequestBodyDrain), Times.Once); @@ -292,18 +290,18 @@ public void ReadTimingNotEnforcedWhenLowConnectionInputFlowControlAvailability() var flowControl = new InputFlowControl(initialWindowSize: 2, minWindowSizeIncrement: 1); // Initialize timestamp - var now = DateTimeOffset.UtcNow; - _timeoutControl.Initialize(now.Ticks); + var now = _timeProvider.GetTimestamp(); + _timeoutControl.Initialize(now); _timeoutControl.InitializeHttp2(flowControl); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); // Tick past grace period - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(100); _timeoutControl.Tick(now); - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(100); _timeoutControl.Tick(now); @@ -311,7 +309,7 @@ public void ReadTimingNotEnforcedWhenLowConnectionInputFlowControlAvailability() flowControl.TryAdvance(2); // Read 0 bytes in 1 second - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.Tick(now); // Not timed out @@ -325,7 +323,7 @@ public void ReadTimingNotEnforcedWhenLowConnectionInputFlowControlAvailability() _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Read 0 bytes in 1 second - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.Tick(now); // Timed out @@ -338,22 +336,22 @@ public void ReadTimingOnlyCountsUpToOneHeartbeatIntervalPerTick() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - var now = DateTimeOffset.UtcNow; - _timeoutControl.Initialize(now.Ticks); + var now = _timeProvider.GetTimestamp(); + _timeoutControl.Initialize(now); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); // Tick past grace period - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(100); _timeoutControl.Tick(now); - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(100); _timeoutControl.Tick(now); // Read 100 bytes in 2 seconds with a single tick - now += TimeSpan.FromSeconds(2); + now += _timeProvider.TimestampFrequency * 2; _timeoutControl.BytesRead(100); _timeoutControl.Tick(now); @@ -361,10 +359,10 @@ public void ReadTimingOnlyCountsUpToOneHeartbeatIntervalPerTick() _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Read 100 bytes in 2 seconds in two ticks - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.BytesRead(100); _timeoutControl.Tick(now); - now += TimeSpan.FromSeconds(1); + now += _timeProvider.TimestampFrequency; _timeoutControl.Tick(now); // Timed out @@ -377,7 +375,7 @@ public void WriteTimingAbortsConnectionWhenWriteDoesNotCompleteWithMinimumDataRa var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); + _timeoutControl.Initialize(_timeProvider.GetTimestamp()); // Should complete within 4 seconds, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 400); @@ -395,8 +393,7 @@ public void WriteTimingAbortsConnectionWhenSmallWriteDoesNotCompleteWithinGraceP var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(5)); // Initialize timestamp - var startTime = _timeProvider.GetUtcNow(); - _timeoutControl.Initialize(startTime.Ticks); + _timeoutControl.Initialize(_timeProvider.GetTimestamp()); // Should complete within 1 second, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 100); @@ -420,7 +417,7 @@ public void WriteTimingTimeoutPushedOnConcurrentWrite() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); + _timeoutControl.Initialize(_timeProvider.GetTimestamp()); // Should complete within 5 seconds, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 500); @@ -453,8 +450,7 @@ public void WriteTimingAbortsConnectionWhenRepeatedSmallWritesDoNotCompleteWithM var writeSize = 100; // Initialize timestamp - var startTime = _timeProvider.GetUtcNow(); - _timeoutControl.Initialize(startTime.Ticks); + _timeoutControl.Initialize(_timeProvider.GetTimestamp()); // 5 consecutive 100 byte writes. for (var i = 0; i < numWrites - 1; i++) @@ -474,7 +470,7 @@ public void WriteTimingAbortsConnectionWhenRepeatedSmallWritesDoNotCompleteWithM // On more tick forward triggers the timeout. _timeProvider.Advance(TimeSpan.FromTicks(1)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); } @@ -485,7 +481,7 @@ public void WriteTimingOnlyCountsUpToOneHeartbeatIntervalPerTick() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); + _timeoutControl.Initialize(_timeProvider.GetTimestamp()); // Should complete within 4 seconds, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 400); @@ -493,7 +489,7 @@ public void WriteTimingOnlyCountsUpToOneHeartbeatIntervalPerTick() // Tick just past 4s plus Heartbeat.Interval at once _timeProvider.Advance(TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1)); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Never); @@ -510,7 +506,7 @@ private void TickBodyWithMinimumDataRate(int bytesPerSecond) var minRate = new MinDataRate(bytesPerSecond, gracePeriod); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetUtcNow().Ticks); + _timeoutControl.Initialize(_timeProvider.GetTimestamp()); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); @@ -520,20 +516,20 @@ private void TickBodyWithMinimumDataRate(int bytesPerSecond) // Tick after grace period w/ low data rate _timeProvider.Advance(TimeSpan.FromSeconds(1)); _timeoutControl.BytesRead(1); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); } private void AdvanceClock(TimeSpan timeSpan) { - var endTime = _timeProvider.GetUtcNow() + timeSpan; + var endTime = _timeProvider.GetTimestamp() + timeSpan.ToTicks(_timeProvider.TimestampFrequency); - while (_timeProvider.GetUtcNow() + Heartbeat.Interval < endTime) + while (_timeProvider.GetTimestamp() + Heartbeat.Interval.ToTicks(_timeProvider.TimestampFrequency) < endTime) { _timeProvider.Advance(Heartbeat.Interval); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); } - _timeProvider.SetUtcNow(endTime); - _timeoutControl.Tick(_timeProvider.GetUtcNow()); + _timeProvider.SetTimestamp(endTime); + _timeoutControl.Tick(_timeProvider.GetTimestamp()); } } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs index 65f7472b68ec..20f997bb3430 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs @@ -39,7 +39,7 @@ public void Setup() serviceContext: serviceContext, connectionContext: null, transport: pair.Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null), + timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), memoryPool: memoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs index dfac1481c43a..6114620b1343 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs @@ -35,7 +35,7 @@ public void Setup() serviceContext: serviceContext, connectionContext: null, transport: pair.Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null), + timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), memoryPool: memoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs index 14f4f7837482..dc9e95c28d0e 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs @@ -77,7 +77,7 @@ private TestHttp1Connection MakeHttp1Connection() serviceContext: serviceContext, connectionContext: null, transport: pair.Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null), + timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), memoryPool: _memoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs index 39a5efb02896..2ca4cfab1db4 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs @@ -106,7 +106,7 @@ private TestHttp1Connection MakeHttp1Connection() serviceContext: serviceContext, connectionContext: null, transport: pair.Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null), + timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), memoryPool: _memoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs index 58f2a8822ad9..eecabcd2501c 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs @@ -111,7 +111,7 @@ private TestHttp1Connection MakeHttp1Connection() serviceContext: serviceContext, connectionContext: null, transport: pair.Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null), + timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), memoryPool: _memoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs index eb32c081a3af..30a3bf22e7c2 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs @@ -65,7 +65,7 @@ public void StopTimingWrite() { } - public void Tick(DateTimeOffset now) + public void Tick(long now) { } } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs index 648ccce3a470..190a802f06fd 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs @@ -39,7 +39,7 @@ public void Setup() transport: pair.Transport, memoryPool: _memoryPool, connectionFeatures: new FeatureCollection(), - timeoutControl: new TimeoutControl(timeoutHandler: null)); + timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency)); var http1Connection = new Http1Connection(connectionContext); diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index e4ef05ee8ef5..2fef1145dda6 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -40,7 +40,7 @@ internal class Http3InMemory public Http3InMemory(ServiceContext serviceContext, MockTimeProvider mockTimeProvider, ITimeoutHandler timeoutHandler, ILoggerFactory loggerFactory) { _serviceContext = serviceContext; - _timeoutControl = new TimeoutControl(new TimeoutControlConnectionInvoker(this, timeoutHandler)); + _timeoutControl = new TimeoutControl(new TimeoutControlConnectionInvoker(this, timeoutHandler), mockTimeProvider.TimestampFrequency); _timeoutControl.Debugger = new TestDebugger(); _mockTimeProvider = mockTimeProvider; @@ -201,19 +201,19 @@ internal void VerifyGoAway(Http3FrameWithPayload frame, long expectedLastStreamI public void AdvanceTime(TimeSpan timeSpan) { - Logger.LogDebug($"Advancing timeProvider {timeSpan}."); + Logger.LogDebug("Advancing timeProvider {timeSpan}.", timeSpan); var timeProvider = _mockTimeProvider; - var endTime = timeProvider.GetUtcNow() + timeSpan; + var endTime = timeProvider.GetTimestamp() + timeSpan.ToTicks(timeProvider.TimestampFrequency); - while (timeProvider.GetUtcNow() + Heartbeat.Interval < endTime) + while (timeProvider.GetTimestamp() + Heartbeat.Interval.ToTicks(timeProvider.TimestampFrequency) < endTime) { timeProvider.Advance(Heartbeat.Interval); - _timeoutControl.Tick(timeProvider.GetUtcNow()); + _timeoutControl.Tick(timeProvider.GetTimestamp()); } - timeProvider.SetUtcNow(endTime); - _timeoutControl.Tick(timeProvider.GetUtcNow()); + timeProvider.SetTimestamp(endTime); + _timeoutControl.Tick(timeProvider.GetTimestamp()); } public void TriggerTick(DateTimeOffset now) diff --git a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs index 69c6e1d540ba..d4855ac4367a 100644 --- a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs +++ b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs @@ -32,13 +32,18 @@ public void SetUtcNow(DateTimeOffset now) public override long GetTimestamp() => Interlocked.Read(ref _utcNowTicks); + public void SetTimestamp(long now) + { + Interlocked.Exchange(ref _utcNowTicks, now); + } + public override long TimestampFrequency => TimeSpan.TicksPerSecond; public int UtcNowCalled { get; private set; } public void Advance(TimeSpan timeSpan) { - Interlocked.Add(ref _utcNowTicks, timeSpan.Ticks); + Interlocked.Add(ref _utcNowTicks, timeSpan.ToTicks(TimestampFrequency)); } private long NextLong(long minValue, long maxValue) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index f011c923e2a9..3060f1f816f6 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -162,7 +162,7 @@ public Http2TestBase() _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize); _hpackEncoder = new DynamicHPackEncoder(); - _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object); + _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object, new MockTimeProvider().TimestampFrequency); _mockTimeoutControl = new Mock(_timeoutControl) { CallBase = true }; _timeoutControl.Debugger = Mock.Of(); @@ -1342,7 +1342,7 @@ protected void TriggerTick(DateTimeOffset? nowOrNull = null) { var now = nowOrNull ?? _serviceContext.MockTimeProvider.GetUtcNow(); _serviceContext.MockTimeProvider.SetUtcNow(now); - _timeoutControl.Tick(now); + _timeoutControl.Tick(now.Ticks); ((IRequestProcessor)_connection)?.Tick(now); } @@ -1477,7 +1477,7 @@ public virtual void BytesWrittenToBuffer(MinDataRate minRate, long size) _realTimeoutControl.BytesWrittenToBuffer(minRate, size); } - public virtual void Tick(DateTimeOffset now) + public virtual void Tick(long now) { _realTimeoutControl.Tick(now); } diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs index afbbb31a42eb..d0a8e9b02a59 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs @@ -334,7 +334,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -447,7 +447,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC // Disable response buffering so "socket" backpressure is observed immediately. limits.MaxResponseBufferSize = 0; - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); var app = new EchoAppWithNotification(); var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(app.RunApp, _browserRequestHeaders, endStream: false); @@ -458,7 +458,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC await app.WriteStartedTask.DefaultTimeout(); // Complete timing of the request body so we don't induce any unexpected request body rate timeouts. - Http3Api._timeoutControl.Tick(mockTimeProvider.GetUtcNow()); + Http3Api._timeoutControl.Tick(mockTimeProvider.GetTimestamp()); // Don't read data frame to induce "socket" backpressure. Http3Api.AdvanceTime(TimeSpan.FromSeconds((requestStream.BytesReceived + _helloWorldBytes.Length) / limits.MinResponseDataRate.BytesPerSecond) + @@ -489,7 +489,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC // Disable response buffering so "socket" backpressure is observed immediately. limits.MaxResponseBufferSize = 0; - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); var app = new EchoAppWithNotification(); var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(app.RunApp, _browserRequestHeaders, endStream: false); @@ -500,7 +500,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC await app.WriteStartedTask.DefaultTimeout(); // Complete timing of the request body so we don't induce any unexpected request body rate timeouts. - Http3Api._timeoutControl.Tick(mockTimeProvider.GetUtcNow()); + Http3Api._timeoutControl.Tick(mockTimeProvider.GetTimestamp()); var timeToWriteMaxData = TimeSpan.FromSeconds((requestStream.BytesReceived + _maxData.Length) / limits.MinResponseDataRate.BytesPerSecond) + limits.MinResponseDataRate.GracePeriod + Heartbeat.Interval - TimeSpan.FromSeconds(.5); @@ -529,7 +529,7 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -575,7 +575,7 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -630,7 +630,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -686,7 +686,7 @@ public async Task DATA_Received_SlowlyWhenRateLimitDisabledPerRequest_DoesNotAbo // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetUtcNow().Ticks); + Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); await Http3Api.InitializeConnectionAsync(context => { From edd0c9b94ce2b70ab51e97aae3b779a3cb1914a7 Mon Sep 17 00:00:00 2001 From: Chris R Date: Wed, 10 May 2023 16:42:34 -0700 Subject: [PATCH 05/17] Refactor to use timestamps everywhere --- .../Internal/Http/DateHeaderValueManager.cs | 15 +++-- .../Core/src/Internal/Http/Http1Connection.cs | 7 ++- .../src/Internal/Http/Http1MessageBody.cs | 3 +- .../src/Internal/Http2/Http2Connection.cs | 10 +-- .../src/Internal/Http3/Http3Connection.cs | 39 ++++++------ .../Core/src/Internal/Http3/Http3Stream.cs | 1 - .../Core/src/Internal/HttpConnection.cs | 5 +- .../Core/src/Internal/IRequestProcessor.cs | 2 +- .../Infrastructure/ConnectionManager.cs | 15 ++++- .../src/Internal/Infrastructure/Heartbeat.cs | 10 ++- .../Infrastructure/HeartbeatManager.cs | 33 ---------- .../Infrastructure/IHeartbeatHandler.cs | 2 +- .../Infrastructure/TimeSpanExtensions.cs | 8 +++ .../Internal/Infrastructure/TimeoutControl.cs | 4 +- .../Core/src/Internal/KestrelServerImpl.cs | 7 +-- .../Core/test/DateHeaderValueManagerTests.cs | 23 +++---- .../Kestrel/Core/test/HeartbeatTests.cs | 17 ++--- .../Core/test/Http1/Http1ConnectionTests.cs | 4 +- .../Kestrel/Core/test/KestrelServerTests.cs | 7 ++- .../Kestrel/Core/test/StartLineTests.cs | 1 + .../Kestrel/Core/test/TimeoutControlTests.cs | 16 +++-- .../HeaderCollectionBenchmark.cs | 4 +- .../Http1LargeWritingBenchmark.cs | 4 +- .../Microbenchmarks/Http1ReadingBenchmark.cs | 4 +- .../Microbenchmarks/Http1WritingBenchmark.cs | 4 +- .../Http2/Http2ConnectionBenchmarkBase.cs | 6 +- .../Http2/Http2FrameWriterBenchmark.cs | 2 +- .../Http3/Http3ConnectionBenchmarkBase.cs | 4 +- .../HttpProtocolFeatureCollection.cs | 2 +- .../RequestParsingBenchmark.cs | 2 +- .../ResponseHeaderCollectionBenchmark.cs | 4 +- .../ResponseHeadersWritingBenchmark.cs | 5 +- .../shared/test/Http3/Http3InMemory.cs | 20 +++--- .../Kestrel/shared/test/MockTimeProvider.cs | 29 ++++++--- .../Kestrel/shared/test/TestServiceContext.cs | 11 ++-- .../test/FunctionalTests/ResponseTests.cs | 12 ++-- .../Http2/Http2TestBase.cs | 22 +++++-- .../Http2/Http2TimeoutTests.cs | 16 ++--- .../Http2/Http2WebSocketTests.cs | 2 +- .../Http3/Http3TimeoutTests.cs | 62 ++++++++++--------- .../KeepAliveTimeoutTests.cs | 22 +++---- .../RequestBodyTimeoutTests.cs | 10 ++- .../RequestHeadersTimeoutTests.cs | 19 +++--- .../InMemory.FunctionalTests/RequestTests.cs | 5 +- .../ResponseDrainingTests.cs | 8 +-- 45 files changed, 262 insertions(+), 246 deletions(-) delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/DateHeaderValueManager.cs b/src/Servers/Kestrel/Core/src/Internal/Http/DateHeaderValueManager.cs index 31ad48241a1c..f3d9eb3b7d95 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/DateHeaderValueManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/DateHeaderValueManager.cs @@ -15,8 +15,15 @@ internal sealed class DateHeaderValueManager : IHeartbeatHandler // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static private static ReadOnlySpan DatePreambleBytes => "\r\nDate: "u8; + public TimeProvider _timeProvider; + private DateHeaderValues? _dateValues; + public DateHeaderValueManager(TimeProvider timeProvider) + { + _timeProvider = timeProvider; + } + /// /// Returns a value representing the current server date/time for use in the HTTP "Date" response header /// in accordance with http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 @@ -25,17 +32,17 @@ internal sealed class DateHeaderValueManager : IHeartbeatHandler public DateHeaderValues GetDateHeaderValues() => _dateValues!; // Called by the Timer (background) thread - public void OnHeartbeat(DateTimeOffset now) + public void OnHeartbeat() { - SetDateValues(now); + SetDateValues(); } /// /// Sets date values from a provided ticks value /// - /// A DateTimeOffset value - private void SetDateValues(DateTimeOffset value) + private void SetDateValues() { + var value = _timeProvider.GetUtcNow(); var dateValue = HeaderUtilities.FormatDate(value); var dateBytes = new byte[DatePreambleBytes.Length + dateValue.Length]; DatePreambleBytes.CopyTo(dateBytes); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 4e7b87c3f9ff..66ea4bd9830f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -55,8 +55,9 @@ public Http1Connection(HttpConnectionContext context) _context = context; _parser = ServiceContext.HttpParser; - _keepAliveTicks = ServerOptions.Limits.KeepAliveTimeout.Ticks; - _requestHeadersTimeoutTicks = ServerOptions.Limits.RequestHeadersTimeout.Ticks; + var frequency = context.ServiceContext.TimeProvider.TimestampFrequency; + _keepAliveTicks = ServerOptions.Limits.KeepAliveTimeout.ToTicks(frequency); + _requestHeadersTimeoutTicks = ServerOptions.Limits.RequestHeadersTimeout.ToTicks(frequency); _http1Output = new Http1OutputProducer( _context.Transport.Output, @@ -790,5 +791,5 @@ protected override Task TryProduceInvalidRequestResponse() return base.TryProduceInvalidRequestResponse(); } - void IRequestProcessor.Tick(DateTimeOffset now) { } + void IRequestProcessor.Tick(long now) { } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index a8c2916c0247..f82cf0bb29b2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -83,7 +83,8 @@ protected async Task OnConsumeAsyncAwaited() { Log.RequestBodyNotEntirelyRead(_context.ConnectionIdFeature, _context.TraceIdentifier); - _context.TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutReason.RequestBodyDrain); + _context.TimeoutControl.SetTimeout( + Constants.RequestBodyDrainTimeout.ToTicks(_context.ServiceContext.TimeProvider.TimestampFrequency), TimeoutReason.RequestBodyDrain); try { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 7341c7d18f0b..1ca89cf89a78 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -209,7 +209,7 @@ public async Task ProcessRequestsAsync(IHttpApplication appl ValidateTlsRequirements(); TimeoutControl.InitializeHttp2(_inputFlowControl); - TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive); + TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.ToTicks(TimeProvider.TimestampFrequency), TimeoutReason.KeepAlive); if (!await TryReadPrefaceAsync()) { @@ -729,7 +729,7 @@ private Task ProcessHeadersFrameAsync(IHttpApplication appli if (!_incomingFrame.HeadersEndHeaders) { - TimeoutControl.SetTimeout(Limits.RequestHeadersTimeout.Ticks, TimeoutReason.RequestHeaders); + TimeoutControl.SetTimeout(Limits.RequestHeadersTimeout.ToTicks(TimeProvider.TimestampFrequency), TimeoutReason.RequestHeaders); } // Start a new stream @@ -944,7 +944,7 @@ private Task ProcessPingFrameAsync(in ReadOnlySequence payload) // Incoming ping resets connection keep alive timeout if (TimeoutControl.TimerReason == TimeoutReason.KeepAlive) { - TimeoutControl.ResetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive); + TimeoutControl.ResetTimeout(Limits.KeepAliveTimeout.ToTicks(TimeProvider.TimestampFrequency), TimeoutReason.KeepAlive); } if (_incomingFrame.PingAck) @@ -1241,7 +1241,7 @@ private void AbortStream(int streamId, IOException error) } } - void IRequestProcessor.Tick(DateTimeOffset now) + void IRequestProcessor.Tick(long now) { Input.CancelPendingRead(); } @@ -1363,7 +1363,7 @@ private void UpdateConnectionState() { if (TimeoutControl.TimerReason == TimeoutReason.None) { - TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive); + TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.ToTicks(TimeProvider.TimestampFrequency), TimeoutReason.KeepAlive); } // If we're awaiting headers, either a new stream will be started, or there will be a connection diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index d6e43c6644f8..0706b27bf569 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -190,7 +190,7 @@ public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode) } } - public void Tick(DateTimeOffset now) + public void Tick(long now) { if (_aborted) { @@ -203,10 +203,8 @@ public void Tick(DateTimeOffset now) UpdateStreamTimeouts(now); } - private void ValidateOpenControlStreams(DateTimeOffset now) + private void ValidateOpenControlStreams(long now) { - var ticks = now.Ticks; - // This method validates that a connnection's control streams are open. // // They're checked on a delayed timer because when a connection is aborted or timed out, notifications are sent to open streams @@ -216,10 +214,10 @@ private void ValidateOpenControlStreams(DateTimeOffset now) // // Realistically, control streams are never closed except when the connection is. A small delay in aborting the connection in the // unlikely situation where a control stream is incorrectly closed should be fine. - ValidateOpenControlStream(OutboundControlStream, this, ticks); - ValidateOpenControlStream(ControlStream, this, ticks); - ValidateOpenControlStream(EncoderStream, this, ticks); - ValidateOpenControlStream(DecoderStream, this, ticks); + ValidateOpenControlStream(OutboundControlStream, this, now); + ValidateOpenControlStream(ControlStream, this, now); + ValidateOpenControlStream(EncoderStream, this, now); + ValidateOpenControlStream(DecoderStream, this, now); static void ValidateOpenControlStream(Http3ControlStream? stream, Http3Connection connection, long ticks) { @@ -242,15 +240,16 @@ static void ValidateOpenControlStream(Http3ControlStream? stream, Http3Connectio } } - private void UpdateStreamTimeouts(DateTimeOffset now) + private void UpdateStreamTimeouts(long now) { // This method checks for timeouts: // 1. When a stream first starts and waits to receive headers. // Uses RequestHeadersTimeout. // 2. When a stream finished and is waiting for underlying transport to drain. // Uses MinResponseDataRate. - - var ticks = now.Ticks; + var serviceContext = _context.ServiceContext; + var requestHeadersTimeout = serviceContext.ServerOptions.Limits.RequestHeadersTimeout.ToTicks( + serviceContext.TimeProvider.TimestampFrequency); lock (_unidentifiedStreams) { @@ -259,11 +258,11 @@ private void UpdateStreamTimeouts(DateTimeOffset now) if (stream.StreamTimeoutTicks == default) { // On expiration overflow, use max value. - var expirationTicks = ticks + _context.ServiceContext.ServerOptions.Limits.RequestHeadersTimeout.Ticks; + var expirationTicks = now + requestHeadersTimeout; stream.StreamTimeoutTicks = expirationTicks >= 0 ? expirationTicks : long.MaxValue; } - if (stream.StreamTimeoutTicks < ticks) + if (stream.StreamTimeoutTicks < now) { stream.Abort(new("Stream timed out before its type was determined.")); } @@ -279,11 +278,11 @@ private void UpdateStreamTimeouts(DateTimeOffset now) if (stream.StreamTimeoutTicks == default) { // On expiration overflow, use max value. - var expirationTicks = ticks + _context.ServiceContext.ServerOptions.Limits.RequestHeadersTimeout.Ticks; + var expirationTicks = now + requestHeadersTimeout; stream.StreamTimeoutTicks = expirationTicks >= 0 ? expirationTicks : long.MaxValue; } - if (stream.StreamTimeoutTicks < ticks) + if (stream.StreamTimeoutTicks < now) { if (stream.IsRequestStream) { @@ -305,10 +304,10 @@ private void UpdateStreamTimeouts(DateTimeOffset now) if (stream.StreamTimeoutTicks == default) { - stream.StreamTimeoutTicks = TimeoutControl.GetResponseDrainDeadline(ticks, minDataRate); + stream.StreamTimeoutTicks = TimeoutControl.GetResponseDrainDeadline(now, minDataRate); } - if (stream.StreamTimeoutTicks < ticks) + if (stream.StreamTimeoutTicks < now) { // Cancel connection to be consistent with other data rate limits. Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, stream.TraceIdentifier); @@ -348,7 +347,8 @@ public async Task ProcessRequestsAsync(IHttpApplication appl outboundControlStreamTask = ProcessOutboundControlStreamAsync(outboundControlStream); // Close the connection if we don't receive any request streams - TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive); + TimeoutControl.SetTimeout( + Limits.KeepAliveTimeout.ToTicks(_context.ServiceContext.TimeProvider.TimestampFrequency), TimeoutReason.KeepAlive); while (_stoppedAcceptingStreams == 0) { @@ -820,7 +820,8 @@ void IHttp3StreamLifetimeHandler.OnStreamCompleted(IHttp3Stream stream) if (_activeRequestCount == 0) { - TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive); + TimeoutControl.SetTimeout( + Limits.KeepAliveTimeout.ToTicks(_context.ServiceContext.TimeProvider.TimestampFrequency), TimeoutReason.KeepAlive); } } _streams.Remove(stream.StreamId); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index aed761bd6b05..1bbb40dde58a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -70,7 +70,6 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS public QPackDecoder QPackDecoder { get; private set; } = default!; public PipeReader Input => _context.Transport.Input; - public TimeProvider TimeProvider => _context.ServiceContext.TimeProvider; public KestrelServerLimits Limits => _context.ServiceContext.ServerOptions.Limits; public long StreamId => _streamIdFeature.StreamId; public long StreamTimeoutTicks { get; set; } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index c7b78886a936..8856db78bdb8 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -236,9 +236,8 @@ private void Tick() return; } - var now = _timeProvider.GetUtcNow(); - var ticks = _timeProvider.GetTimestamp(); - _timeoutControl.Tick(ticks); + var now = _timeProvider.GetTimestamp(); + _timeoutControl.Tick(now); _requestProcessor!.Tick(now); } diff --git a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs index 217b5d501cab..d904077c6aee 100644 --- a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs +++ b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs @@ -13,6 +13,6 @@ internal interface IRequestProcessor void HandleRequestHeadersTimeout(); void HandleReadDataRateTimeout(); void OnInputOrOutputCompleted(); - void Tick(DateTimeOffset now); + void Tick(long now); void Abort(ConnectionAbortedException ex); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs index 9b27d4d8bcdc..fd43496090b6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ConnectionManager.cs @@ -5,8 +5,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -internal sealed class ConnectionManager +internal sealed class ConnectionManager : IHeartbeatHandler { + private readonly Action _walkCallback; + private long _lastConnectionId = long.MinValue; private readonly ConcurrentDictionary _connectionReferences = new ConcurrentDictionary(); @@ -21,6 +23,7 @@ public ConnectionManager(KestrelTrace trace, ResourceCounter upgradedConnections { UpgradedConnectionCount = upgradedConnections; _trace = trace; + _walkCallback = WalkCallback; } public long GetNewConnectionId() => Interlocked.Increment(ref _lastConnectionId); @@ -30,6 +33,16 @@ public ConnectionManager(KestrelTrace trace, ResourceCounter upgradedConnections /// public ResourceCounter UpgradedConnectionCount { get; } + public void OnHeartbeat() + { + Walk(_walkCallback); + } + + private void WalkCallback(KestrelConnection connection) + { + connection.TickHeartbeat(); + } + public void AddConnection(long id, ConnectionReference connectionReference) { if (!_connectionReferences.TryAdd(id, connectionReference)) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs index 61829a93f499..b42f66b6a55d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/Heartbeat.cs @@ -42,24 +42,22 @@ public void Start() internal void OnHeartbeat() { - var now = _timeProvider.GetUtcNow(); + var now = _timeProvider.GetTimestamp(); try { foreach (var callback in _callbacks) { - callback.OnHeartbeat(now); + callback.OnHeartbeat(); } if (!_debugger.IsAttached) { - var after = _timeProvider.GetUtcNow(); - - var duration = TimeSpan.FromTicks(after.Ticks - now.Ticks); + var duration = _timeProvider.GetElapsedTime(now); if (duration > _interval) { - _trace.HeartbeatSlow(duration, _interval, now); + _trace.HeartbeatSlow(duration, _interval, _timeProvider.GetUtcNow()); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs deleted file mode 100644 index a502926f9efc..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/HeartbeatManager.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; - -internal sealed class HeartbeatManager : TimeProvider, IHeartbeatHandler -{ - private readonly ConnectionManager _connectionManager; - private readonly Action _walkCallback; - private long _nowTicks; - - public HeartbeatManager(ConnectionManager connectionManager) - { - _connectionManager = connectionManager; - _walkCallback = WalkCallback; - } - - public override DateTimeOffset GetUtcNow() => new(GetTimestamp(), TimeSpan.Zero); - - public override long GetTimestamp() => Volatile.Read(ref _nowTicks); - - public void OnHeartbeat(DateTimeOffset now) - { - Volatile.Write(ref _nowTicks, now.Ticks); - - _connectionManager.Walk(_walkCallback); - } - - private void WalkCallback(KestrelConnection connection) - { - connection.TickHeartbeat(); - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IHeartbeatHandler.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IHeartbeatHandler.cs index 1ef557ab73fb..74e8e723a036 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IHeartbeatHandler.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IHeartbeatHandler.cs @@ -5,5 +5,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; internal interface IHeartbeatHandler { - void OnHeartbeat(DateTimeOffset now); + void OnHeartbeat(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs index d5fbad90dbf0..ef45738686d3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs @@ -7,6 +7,14 @@ internal static class TimeSpanExtensions { public static long ToTicks(this TimeSpan timeSpan, long tickFrequency) { + if (timeSpan == TimeSpan.MaxValue) + { + return long.MaxValue; + } + if (timeSpan == TimeSpan.MinValue) + { + return long.MinValue; + } return (long)(timeSpan.TotalSeconds * tickFrequency); } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs index 39b700e11d88..3d1c8c4d5d41 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs @@ -43,9 +43,9 @@ public TimeoutControl(ITimeoutHandler timeoutHandler, long tickFrequency) internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton; - internal void Initialize(long nowTicks) + internal void Initialize(long timestamp) { - _lastTimestamp = nowTicks; + Interlocked.Exchange(ref _lastTimestamp, timestamp); } public void Tick(long now) diff --git a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs index 75171aaf698a..4da16fc2aa1a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs @@ -83,11 +83,10 @@ private static ServiceContext CreateServiceContext(IOptions(trace.IsEnabled(LogLevel.Information), serverOptions.DisableHttp1LineFeedTerminators), - TimeProvider = heartbeatManager, + TimeProvider = TimeProvider.System, DateHeaderValueManager = dateHeaderValueManager, ConnectionManager = connectionManager, Heartbeat = heartbeat, diff --git a/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs b/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs index 53b66dd37c12..3b9652b42bb2 100644 --- a/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs +++ b/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs @@ -22,10 +22,11 @@ public class DateHeaderValueManagerTests [Fact] public void GetDateHeaderValue_ReturnsDateValueInRFC1123Format() { - var now = DateTimeOffset.UtcNow; + var timeProvider = new MockTimeProvider(); + var now = timeProvider.GetUtcNow(); - var dateHeaderValueManager = new DateHeaderValueManager(); - dateHeaderValueManager.OnHeartbeat(now); + var dateHeaderValueManager = new DateHeaderValueManager(timeProvider); + dateHeaderValueManager.OnHeartbeat(); Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); } @@ -37,8 +38,8 @@ public void GetDateHeaderValue_ReturnsCachedValueBetweenTimerTicks() var future = now.AddSeconds(10); var timeProvider = new MockTimeProvider(now); - var dateHeaderValueManager = new DateHeaderValueManager(); - dateHeaderValueManager.OnHeartbeat(now); + var dateHeaderValueManager = new DateHeaderValueManager(timeProvider); + dateHeaderValueManager.OnHeartbeat(); var testKestrelTrace = new KestrelTrace(NullLoggerFactory.Instance); @@ -49,7 +50,7 @@ public void GetDateHeaderValue_ReturnsCachedValueBetweenTimerTicks() Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); } - Assert.Equal(0, timeProvider.UtcNowCalled); + Assert.Equal(1, timeProvider.UtcNowCalled); } [Fact] @@ -59,8 +60,8 @@ public void GetDateHeaderValue_ReturnsUpdatedValueAfterHeartbeat() var future = now.AddSeconds(10); var timeProvider = new MockTimeProvider(now); - var dateHeaderValueManager = new DateHeaderValueManager(); - dateHeaderValueManager.OnHeartbeat(now); + var dateHeaderValueManager = new DateHeaderValueManager(timeProvider); + dateHeaderValueManager.OnHeartbeat(); var testKestrelTrace = new KestrelTrace(NullLoggerFactory.Instance); @@ -78,7 +79,7 @@ public void GetDateHeaderValue_ReturnsUpdatedValueAfterHeartbeat() heartbeat.OnHeartbeat(); Assert.Equal(future.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); - Assert.Equal(4, timeProvider.UtcNowCalled); + Assert.Equal(3, timeProvider.UtcNowCalled); } } @@ -89,8 +90,8 @@ public void GetDateHeaderValue_ReturnsLastDateValueAfterHeartbeatDisposed() var future = now.AddSeconds(10); var timeProvider = new MockTimeProvider(now); - var dateHeaderValueManager = new DateHeaderValueManager(); - dateHeaderValueManager.OnHeartbeat(now); + var dateHeaderValueManager = new DateHeaderValueManager(timeProvider); + dateHeaderValueManager.OnHeartbeat(); var testKestrelTrace = new KestrelTrace(NullLoggerFactory.Instance); diff --git a/src/Servers/Kestrel/Core/test/HeartbeatTests.cs b/src/Servers/Kestrel/Core/test/HeartbeatTests.cs index 1daa4f0b60b5..2a89865ecbbe 100644 --- a/src/Servers/Kestrel/Core/test/HeartbeatTests.cs +++ b/src/Servers/Kestrel/Core/test/HeartbeatTests.cs @@ -33,10 +33,11 @@ public async void HeartbeatLoopRunsWithSpecifiedInterval() var debugger = new Mock(); var kestrelTrace = new KestrelTrace(LoggerFactory); var now = timeProvider.GetUtcNow(); + var timestamp = timeProvider.GetTimestamp(); var splits = new List(); Stopwatch sw = null; - heartbeatHandler.Setup(h => h.OnHeartbeat(now)).Callback(() => + heartbeatHandler.Setup(h => h.OnHeartbeat()).Callback(() => { heartbeatCallCount++; if (sw == null) @@ -106,9 +107,10 @@ public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsWarning() var handlerMre = new ManualResetEventSlim(); var handlerStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var now = timeProvider.GetUtcNow(); + var timestamp = timeProvider.GetTimestamp(); var heartbeatDuration = TimeSpan.FromSeconds(2); - heartbeatHandler.Setup(h => h.OnHeartbeat(now)).Callback(() => + heartbeatHandler.Setup(h => h.OnHeartbeat()).Callback(() => { handlerStartedTcs.SetResult(); handlerMre.Wait(); @@ -131,10 +133,10 @@ public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsWarning() await blockedHeartbeatTask.DefaultTimeout(); - heartbeatHandler.Verify(h => h.OnHeartbeat(now), Times.Once()); + heartbeatHandler.Verify(h => h.OnHeartbeat(), Times.Once()); var warningMessage = TestSink.Writes.Single(message => message.LogLevel == LogLevel.Warning).Message; - Assert.Equal($"As of \"{now.ToString(CultureInfo.InvariantCulture)}\", the heartbeat has been running for " + Assert.Equal($"As of \"{timeProvider.GetUtcNow().ToString(CultureInfo.InvariantCulture)}\", the heartbeat has been running for " + $"\"{heartbeatDuration.ToString("c", CultureInfo.InvariantCulture)}\" which is longer than " + $"\"{Heartbeat.Interval.ToString("c", CultureInfo.InvariantCulture)}\". " + "This could be caused by thread pool starvation.", warningMessage); @@ -150,8 +152,9 @@ public async Task HeartbeatTakingLongerThanIntervalIsNotLoggedIfDebuggerAttached var handlerMre = new ManualResetEventSlim(); var handlerStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var now = timeProvider.GetUtcNow(); + var timestamp = timeProvider.GetTimestamp(); - heartbeatHandler.Setup(h => h.OnHeartbeat(now)).Callback(() => + heartbeatHandler.Setup(h => h.OnHeartbeat()).Callback(() => { handlerStartedTcs.SetResult(); handlerMre.Wait(); @@ -175,7 +178,7 @@ public async Task HeartbeatTakingLongerThanIntervalIsNotLoggedIfDebuggerAttached await blockedHeartbeatTask.DefaultTimeout(); - heartbeatHandler.Verify(h => h.OnHeartbeat(now), Times.Once()); + heartbeatHandler.Verify(h => h.OnHeartbeat(), Times.Once()); Assert.Empty(TestSink.Writes.Where(w => w.EventId.Name == "HeartbeatSlow")); } @@ -188,7 +191,7 @@ public void ExceptionFromHeartbeatHandlerIsLoggedAsError() var kestrelTrace = new KestrelTrace(LoggerFactory); var ex = new Exception(); - heartbeatHandler.Setup(h => h.OnHeartbeat(timeProvider.GetUtcNow())).Throws(ex); + heartbeatHandler.Setup(h => h.OnHeartbeat()).Throws(ex); using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, timeProvider, DebuggerWrapper.Singleton, kestrelTrace, Heartbeat.Interval)) { diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs index cd1cc5b208bd..a4fe350f4de1 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs @@ -450,7 +450,7 @@ public async Task ParseRequestStartsRequestHeadersTimeoutOnFirstByteAvailable() ParseRequest((await _transport.Input.ReadAsync()).Buffer, out _consumed, out _examined); _transport.Input.AdvanceTo(_consumed, _examined); - var expectedRequestHeadersTimeout = _serviceContext.ServerOptions.Limits.RequestHeadersTimeout.Ticks; + var expectedRequestHeadersTimeout = _serviceContext.ServerOptions.Limits.RequestHeadersTimeout.ToTicks(_serviceContext.TimeProvider.TimestampFrequency); _timeoutControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutReason.RequestHeaders)); } @@ -570,7 +570,7 @@ public async Task ProcessRequestsAsyncEnablesKeepAliveTimeout() { var requestProcessingTask = _http1Connection.ProcessRequestsAsync(null); - var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.Ticks; + var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.ToTicks(_serviceContext.TimeProvider.TimestampFrequency); _timeoutControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutReason.KeepAlive)); _http1Connection.StopProcessingNextRequest(); diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 0119f60b3c1c..39eea7cbb2cc 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -711,6 +711,7 @@ public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() [Fact] public void StartingServerInitializesHeartbeat() { + var timeProvider = new MockTimeProvider(); var testContext = new TestServiceContext { ServerOptions = @@ -720,12 +721,14 @@ public void StartingServerInitializesHeartbeat() new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) } }, - DateHeaderValueManager = new DateHeaderValueManager() + MockTimeProvider = timeProvider, + TimeProvider = timeProvider, + DateHeaderValueManager = new DateHeaderValueManager(timeProvider) }; testContext.Heartbeat = new Heartbeat( new IHeartbeatHandler[] { testContext.DateHeaderValueManager }, - testContext.MockTimeProvider, + timeProvider, DebuggerWrapper.Singleton, testContext.Log, Heartbeat.Interval); diff --git a/src/Servers/Kestrel/Core/test/StartLineTests.cs b/src/Servers/Kestrel/Core/test/StartLineTests.cs index d87269d7cb62..ca998499d2bd 100644 --- a/src/Servers/Kestrel/Core/test/StartLineTests.cs +++ b/src/Servers/Kestrel/Core/test/StartLineTests.cs @@ -526,6 +526,7 @@ public StartLineTests() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), + timeProvider: new MockTimeProvider(), httpParser: new HttpParser()); var connectionContext = TestContextFactory.CreateHttpConnectionContext( diff --git a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs index 6e02880e6d14..5630973b425e 100644 --- a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs +++ b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs @@ -256,8 +256,6 @@ public void ReadTimingNotEnforcedWhenTimeoutIsSet() var timeout = TimeSpan.FromSeconds(5); var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); - var startTime = _timeProvider.GetUtcNow(); - // Initialize timestamp _timeoutControl.Initialize(_timeProvider.GetTimestamp()); @@ -275,7 +273,7 @@ public void ReadTimingNotEnforcedWhenTimeoutIsSet() _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Tick just past timeout period, adjusted by Heartbeat.Interval - _timeProvider.SetUtcNow(startTime + timeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + _timeProvider.Advance(TimeSpan.FromSeconds(2) + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Timed out @@ -382,7 +380,7 @@ public void WriteTimingAbortsConnectionWhenWriteDoesNotCompleteWithMinimumDataRa _timeoutControl.StartTimingWrite(); // Tick just past 4s plus Heartbeat.Interval - AdvanceClock(TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1)); + AdvanceClock(TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); } @@ -400,7 +398,7 @@ public void WriteTimingAbortsConnectionWhenSmallWriteDoesNotCompleteWithinGraceP _timeoutControl.StartTimingWrite(); // Tick just past 1s plus Heartbeat.Interval - AdvanceClock(TimeSpan.FromSeconds(1) + Heartbeat.Interval + TimeSpan.FromTicks(1)); + AdvanceClock(TimeSpan.FromSeconds(1) + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); // Still within grace period, not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); @@ -428,7 +426,7 @@ public void WriteTimingTimeoutPushedOnConcurrentWrite() _timeoutControl.StartTimingWrite(); // Tick just past 5s plus Heartbeat.Interval, when the first write should have completed - AdvanceClock(TimeSpan.FromSeconds(5) + Heartbeat.Interval + TimeSpan.FromTicks(1)); + AdvanceClock(TimeSpan.FromSeconds(5) + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); // Not timed out because the timeout was pushed by the second write _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); @@ -469,7 +467,7 @@ public void WriteTimingAbortsConnectionWhenRepeatedSmallWritesDoNotCompleteWithM _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // On more tick forward triggers the timeout. - _timeProvider.Advance(TimeSpan.FromTicks(1)); + _timeProvider.Advance(TimeSpan.FromMilliseconds(1)); _timeoutControl.Tick(_timeProvider.GetTimestamp()); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); @@ -488,13 +486,13 @@ public void WriteTimingOnlyCountsUpToOneHeartbeatIntervalPerTick() _timeoutControl.StartTimingWrite(); // Tick just past 4s plus Heartbeat.Interval at once - _timeProvider.Advance(TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1)); + _timeProvider.Advance(TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); _timeoutControl.Tick(_timeProvider.GetTimestamp()); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Never); // The last Tick only accounted for one heartbeat interval. Try again with a tick per interval. - AdvanceClock(TimeSpan.FromSeconds(4) + TimeSpan.FromTicks(1)); + AdvanceClock(TimeSpan.FromSeconds(4) + TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/HeaderCollectionBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/HeaderCollectionBenchmark.cs index 67f1a4e90ed5..5f31f7fcfa68 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/HeaderCollectionBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/HeaderCollectionBenchmark.cs @@ -21,7 +21,7 @@ public class HeaderCollectionBenchmark private const int InnerLoopCount = 1024 * 1024; private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel"); - private static readonly DateHeaderValueManager _dateHeaderValueManager = new DateHeaderValueManager(); + private static readonly DateHeaderValueManager _dateHeaderValueManager = new DateHeaderValueManager(TimeProvider.System); private HttpResponseHeaders _responseHeadersDirect; private HttpResponse _response; @@ -343,7 +343,7 @@ public void Setup() var http1Connection = new Http1Connection(connectionContext); http1Connection.Reset(); - serviceContext.DateHeaderValueManager.OnHeartbeat(DateTimeOffset.UtcNow); + serviceContext.DateHeaderValueManager.OnHeartbeat(); _responseHeadersDirect = (HttpResponseHeaders)http1Connection.ResponseHeaders; var context = new DefaultHttpContext(http1Connection); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs index dc9e95c28d0e..90bfc3371eeb 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs @@ -71,7 +71,7 @@ private TestHttp1Connection MakeHttp1Connection() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), httpParser: new HttpParser(), - dateHeaderValueManager: new DateHeaderValueManager()); + dateHeaderValueManager: new DateHeaderValueManager(new MockTimeProvider())); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, @@ -85,7 +85,7 @@ private TestHttp1Connection MakeHttp1Connection() http1Connection.Reset(); http1Connection.InitializeBodyControl(MessageBody.ZeroContentLengthKeepAlive); - serviceContext.DateHeaderValueManager.OnHeartbeat(DateTimeOffset.UtcNow); + serviceContext.DateHeaderValueManager.OnHeartbeat(); return http1Connection; } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs index 2ca4cfab1db4..6339e780c724 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs @@ -100,7 +100,7 @@ private TestHttp1Connection MakeHttp1Connection() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), httpParser: new HttpParser(), - dateHeaderValueManager: new DateHeaderValueManager()); + dateHeaderValueManager: new DateHeaderValueManager(new MockTimeProvider())); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, @@ -114,7 +114,7 @@ private TestHttp1Connection MakeHttp1Connection() http1Connection.Reset(); http1Connection.InitializeBodyControl(new Http1ContentLengthMessageBody(http1Connection, contentLength: 100, keepAlive: true)); - serviceContext.DateHeaderValueManager.OnHeartbeat(DateTimeOffset.UtcNow); + serviceContext.DateHeaderValueManager.OnHeartbeat(); return http1Connection; } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs index eecabcd2501c..5c731ee0b4f1 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs @@ -105,7 +105,7 @@ private TestHttp1Connection MakeHttp1Connection() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), httpParser: new HttpParser(), - dateHeaderValueManager: new DateHeaderValueManager()); + dateHeaderValueManager: new DateHeaderValueManager(new MockTimeProvider())); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, @@ -119,7 +119,7 @@ private TestHttp1Connection MakeHttp1Connection() http1Connection.Reset(); http1Connection.InitializeBodyControl(MessageBody.ZeroContentLengthKeepAlive); - serviceContext.DateHeaderValueManager.OnHeartbeat(DateTimeOffset.UtcNow); + serviceContext.DateHeaderValueManager.OnHeartbeat(); return http1Connection; } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs index 39071cc92ddd..69a6fe425773 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs @@ -68,9 +68,9 @@ public virtual void GlobalSetup() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), - dateHeaderValueManager: new DateHeaderValueManager(), - timeProvider: TimeProvider.System); - serviceContext.DateHeaderValueManager.OnHeartbeat(default); + dateHeaderValueManager: new DateHeaderValueManager(new MockTimeProvider()), + timeProvider: new MockTimeProvider()); + serviceContext.DateHeaderValueManager.OnHeartbeat(); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs index a80186a0af60..5d010762e1a9 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs @@ -32,7 +32,7 @@ public void GlobalSetup() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), httpParser: new HttpParser(), - dateHeaderValueManager: new DateHeaderValueManager()); + dateHeaderValueManager: new DateHeaderValueManager(TimeProvider.System)); _frameWriter = new Http2FrameWriter( new NullPipeWriter(), diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http3/Http3ConnectionBenchmarkBase.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http3/Http3ConnectionBenchmarkBase.cs index 1a7c7e1fe9b8..ae144e61081b 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http3/Http3ConnectionBenchmarkBase.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http3/Http3ConnectionBenchmarkBase.cs @@ -56,9 +56,9 @@ public virtual void GlobalSetup() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), - dateHeaderValueManager: new DateHeaderValueManager(), + dateHeaderValueManager: new DateHeaderValueManager(mockTimeProvider), timeProvider: mockTimeProvider); - serviceContext.DateHeaderValueManager.OnHeartbeat(default); + serviceContext.DateHeaderValueManager.OnHeartbeat(); _http3 = new Http3InMemory(serviceContext, mockTimeProvider, new DefaultTimeoutHandler(), NullLoggerFactory.Instance); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/HttpProtocolFeatureCollection.cs b/src/Servers/Kestrel/perf/Microbenchmarks/HttpProtocolFeatureCollection.cs index 9155abd1ccef..e749b89c1a97 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/HttpProtocolFeatureCollection.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/HttpProtocolFeatureCollection.cs @@ -233,7 +233,7 @@ public HttpProtocolFeatureCollection() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), httpParser: new HttpParser(), - dateHeaderValueManager: new DateHeaderValueManager()); + dateHeaderValueManager: new DateHeaderValueManager(TimeProvider.System)); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs index 190a802f06fd..fb77308d59da 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs @@ -31,7 +31,7 @@ public void Setup() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), httpParser: new HttpParser(), - dateHeaderValueManager: new DateHeaderValueManager()); + dateHeaderValueManager: new DateHeaderValueManager(new MockTimeProvider())); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs index 2a61fe881060..6f2f6c9b2817 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs @@ -22,7 +22,7 @@ public class ResponseHeaderCollectionBenchmark private const int InnerLoopCount = 128; private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel"); - private static readonly DateHeaderValueManager _dateHeaderValueManager = new DateHeaderValueManager(); + private static readonly DateHeaderValueManager _dateHeaderValueManager = new DateHeaderValueManager(new MockTimeProvider()); private HttpResponseHeaders _responseHeadersDirect; private HttpResponse _response; @@ -191,7 +191,7 @@ public void Setup() var http1Connection = new Http1Connection(connectionContext); http1Connection.Reset(); - serviceContext.DateHeaderValueManager.OnHeartbeat(DateTimeOffset.UtcNow); + serviceContext.DateHeaderValueManager.OnHeartbeat(); _responseHeadersDirect = (HttpResponseHeaders)http1Connection.ResponseHeaders; var context = new DefaultHttpContext(http1Connection); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeadersWritingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeadersWritingBenchmark.cs index ab1bfbc1a084..f3d8177f4d5b 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeadersWritingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeadersWritingBenchmark.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Testing; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks; @@ -178,8 +179,8 @@ public void GlobalSetup() { _responseHeaders = new HttpResponseHeaders(); _responseHeadersDict = _responseHeaders; - _dateHeaderValueManager = new DateHeaderValueManager(); - _dateHeaderValueManager.OnHeartbeat(DateTimeOffset.Now); + _dateHeaderValueManager = new DateHeaderValueManager(new MockTimeProvider()); + _dateHeaderValueManager.OnHeartbeat(); _writer = new Writer(); } diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index 2fef1145dda6..4a37920542d9 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.Globalization; using System.IO.Pipelines; -using System.Linq; using System.Net.Http; using System.Net.Http.QPack; using System.Text; @@ -17,12 +16,9 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; -using Microsoft.AspNetCore.Server.Kestrel.Core.WebTransport; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using static System.IO.Pipelines.DuplexPipe; @@ -205,20 +201,26 @@ public void AdvanceTime(TimeSpan timeSpan) var timeProvider = _mockTimeProvider; var endTime = timeProvider.GetTimestamp() + timeSpan.ToTicks(timeProvider.TimestampFrequency); + var interval = Heartbeat.Interval.ToTicks(timeProvider.TimestampFrequency); - while (timeProvider.GetTimestamp() + Heartbeat.Interval.ToTicks(timeProvider.TimestampFrequency) < endTime) + while (timeProvider.GetTimestamp() + interval < endTime) { - timeProvider.Advance(Heartbeat.Interval); - _timeoutControl.Tick(timeProvider.GetTimestamp()); + TriggerTick(timeProvider.GetTimestamp() + interval); } - timeProvider.SetTimestamp(endTime); - _timeoutControl.Tick(timeProvider.GetTimestamp()); + TriggerTick(endTime); } public void TriggerTick(DateTimeOffset now) { _mockTimeProvider.SetUtcNow(now); + Connection?.Tick(_mockTimeProvider.GetTimestamp()); + } + + public void TriggerTick(long now) + { + _mockTimeProvider.SetTimestamp(now); + _timeoutControl.Tick(now); Connection?.Tick(now); } diff --git a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs index d4855ac4367a..bc4eed17b1b1 100644 --- a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs +++ b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs @@ -1,49 +1,58 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; + namespace Microsoft.AspNetCore.Testing; public class MockTimeProvider : TimeProvider { - private long _utcNowTicks; + private readonly long _timestampFrequency = + // 10_000_000; // Windows + // 100_000_000; // 8 zeros, in between + // 1_000_000_000; // Linux + Stopwatch.Frequency; + private long _ticks; public MockTimeProvider() { // Use a random DateTimeOffset to ensure tests that incorrectly use the current DateTimeOffset fail always instead of only rarely. - // Pick a date between the min DateTimeOffset and a day before the max DateTimeOffset so there's room to advance the clock. - _utcNowTicks = NextLong(DateTimeOffset.MinValue.Ticks, DateTimeOffset.MaxValue.Ticks - TimeSpan.FromDays(1).Ticks); + // Pick a date between the min DateTimeOffset and a year before the max DateTimeOffset so there's room to advance the clock. + _ticks = NextLong(0, + (DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch + TimeSpan.FromDays(365 * 10)).ToTicks(_timestampFrequency)); } public MockTimeProvider(DateTimeOffset now) { - _utcNowTicks = now.UtcTicks; + _ticks = (now - DateTimeOffset.UnixEpoch).ToTicks(_timestampFrequency); } public override DateTimeOffset GetUtcNow() { UtcNowCalled++; - return new DateTimeOffset(Interlocked.Read(ref _utcNowTicks), TimeSpan.Zero); + var seconds = Interlocked.Read(ref _ticks) / (double)_timestampFrequency; + return DateTimeOffset.UnixEpoch + TimeSpan.FromSeconds(seconds); } public void SetUtcNow(DateTimeOffset now) { - Interlocked.Exchange(ref _utcNowTicks, now.Ticks); + Interlocked.Exchange(ref _ticks, (now - DateTimeOffset.UnixEpoch).ToTicks(_timestampFrequency)); } - public override long GetTimestamp() => Interlocked.Read(ref _utcNowTicks); + public override long GetTimestamp() => Interlocked.Read(ref _ticks); public void SetTimestamp(long now) { - Interlocked.Exchange(ref _utcNowTicks, now); + Interlocked.Exchange(ref _ticks, now); } - public override long TimestampFrequency => TimeSpan.TicksPerSecond; + public override long TimestampFrequency => _timestampFrequency; public int UtcNowCalled { get; private set; } public void Advance(TimeSpan timeSpan) { - Interlocked.Add(ref _utcNowTicks, timeSpan.ToTicks(TimestampFrequency)); + Interlocked.Add(ref _ticks, timeSpan.ToTicks(TimestampFrequency)); } private long NextLong(long minValue, long maxValue) diff --git a/src/Servers/Kestrel/shared/test/TestServiceContext.cs b/src/Servers/Kestrel/shared/test/TestServiceContext.cs index d2cb2ba267dc..49712725a9a9 100644 --- a/src/Servers/Kestrel/shared/test/TestServiceContext.cs +++ b/src/Servers/Kestrel/shared/test/TestServiceContext.cs @@ -38,17 +38,16 @@ private static KestrelTrace CreateLoggingTrace(ILoggerFactory loggerFactory) public void InitializeHeartbeat() { - var heartbeatManager = new HeartbeatManager(ConnectionManager); - DateHeaderValueManager = new DateHeaderValueManager(); + DateHeaderValueManager = new DateHeaderValueManager(TimeProvider.System); Heartbeat = new Heartbeat( - new IHeartbeatHandler[] { DateHeaderValueManager, heartbeatManager }, + new IHeartbeatHandler[] { DateHeaderValueManager, ConnectionManager }, TimeProvider.System, DebuggerWrapper.Singleton, Log, Heartbeat.Interval); MockTimeProvider = null; - TimeProvider = heartbeatManager; + TimeProvider = TimeProvider.System; } private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace, bool disableHttp1LineFeedTerminators, KestrelMetrics metrics) @@ -58,7 +57,7 @@ private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace, Scheduler = PipeScheduler.ThreadPool; MockTimeProvider = new MockTimeProvider(); TimeProvider = MockTimeProvider; - DateHeaderValueManager = new DateHeaderValueManager(); + DateHeaderValueManager = new DateHeaderValueManager(MockTimeProvider); ConnectionManager = new ConnectionManager(Log, ResourceCounter.Unlimited); HttpParser = new HttpParser(Log.IsEnabled(LogLevel.Information), disableHttp1LineFeedTerminators); ServerOptions = new KestrelServerOptions @@ -66,7 +65,7 @@ private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace, AddServerHeader = false }; - DateHeaderValueManager.OnHeartbeat(TimeProvider.GetUtcNow()); + DateHeaderValueManager.OnHeartbeat(); Metrics = metrics; } diff --git a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs index 17eb640826d9..50c67b81a968 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/ResponseTests.cs @@ -768,8 +768,8 @@ public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLarg }; testContext.InitializeHeartbeat(); - var dateHeaderValueManager = new DateHeaderValueManager(); - dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + var dateHeaderValueManager = new DateHeaderValueManager(TimeProvider.System); + dateHeaderValueManager.OnHeartbeat(); testContext.DateHeaderValueManager = dateHeaderValueManager; var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); @@ -845,8 +845,8 @@ public async Task ConnectionNotClosedWhenClientSatisfiesMinimumDataRateGivenLarg }; testContext.InitializeHeartbeat(); - var dateHeaderValueManager = new DateHeaderValueManager(); - dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + var dateHeaderValueManager = new DateHeaderValueManager(TimeProvider.System); + dateHeaderValueManager.OnHeartbeat(); testContext.DateHeaderValueManager = dateHeaderValueManager; var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); @@ -929,8 +929,8 @@ public async Task ClientCanReceiveFullConnectionCloseResponseWithoutErrorAtALowD }; testContext.InitializeHeartbeat(); - var dateHeaderValueManager = new DateHeaderValueManager(); - dateHeaderValueManager.OnHeartbeat(DateTimeOffset.MinValue); + var dateHeaderValueManager = new DateHeaderValueManager(TimeProvider.System); + dateHeaderValueManager.OnHeartbeat(); testContext.DateHeaderValueManager = dateHeaderValueManager; var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 3060f1f816f6..e433e52ee4df 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -387,9 +387,12 @@ public override void Initialize(TestContext context, MethodInfo methodInfo, obje { base.Initialize(context, methodInfo, testMethodArguments, testOutputHelper); + var timeProvider = new MockTimeProvider(); _serviceContext = new TestServiceContext(LoggerFactory) { Scheduler = PipeScheduler.Inline, + MockTimeProvider = timeProvider, + TimeProvider = timeProvider, }; TestSink.MessageLogged += context => @@ -472,7 +475,7 @@ protected void CreateConnection() _mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny())) .Callback(r => httpConnection.OnTimeout(r)); - _timeoutControl.Initialize(_serviceContext.TimeProvider.GetUtcNow().Ticks); + _timeoutControl.Initialize(_serviceContext.TimeProvider.GetTimestamp()); } private class LifetimeHandlerInterceptor : IHttp2StreamLifetimeHandler @@ -1342,18 +1345,27 @@ protected void TriggerTick(DateTimeOffset? nowOrNull = null) { var now = nowOrNull ?? _serviceContext.MockTimeProvider.GetUtcNow(); _serviceContext.MockTimeProvider.SetUtcNow(now); - _timeoutControl.Tick(now.Ticks); + var timestamp = _serviceContext.MockTimeProvider.GetTimestamp(); + _timeoutControl.Tick(timestamp); + ((IRequestProcessor)_connection)?.Tick(timestamp); + } + + protected void TriggerTick(long now) + { + _serviceContext.MockTimeProvider.SetTimestamp(now); + _timeoutControl.Tick(now); ((IRequestProcessor)_connection)?.Tick(now); } protected void AdvanceTime(TimeSpan timeSpan) { var timeProvider = _serviceContext.MockTimeProvider; - var endTime = timeProvider.GetUtcNow() + timeSpan; + var endTime = timeProvider.GetTimestamp() + timeSpan.ToTicks(timeProvider.TimestampFrequency); + var interval = Heartbeat.Interval.ToTicks(timeProvider.TimestampFrequency); - while (timeProvider.GetUtcNow() + Heartbeat.Interval < endTime) + while (timeProvider.GetTimestamp() + interval < endTime) { - TriggerTick(timeProvider.GetUtcNow() + Heartbeat.Interval); + TriggerTick(timeProvider.GetTimestamp() + interval); } TriggerTick(endTime); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs index e35ded71dedb..06d2089e02d8 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs @@ -26,7 +26,7 @@ public async Task Preamble_NotReceivedInitially_WithinKeepAliveTimeout_ClosesCon _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); @@ -46,7 +46,7 @@ public async Task HEADERS_NotReceivedInitially_WithinKeepAliveTimeout_ClosesConn _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); @@ -94,7 +94,7 @@ await ExpectAsync(Http2FrameType.HEADERS, _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); @@ -184,7 +184,7 @@ public async Task HEADERS_ReceivedWithoutAllCONTINUATIONs_WithinRequestHeadersTi await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.NONE); - AdvanceTime(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.RequestHeaders), Times.Once); @@ -268,7 +268,7 @@ async Task AdvanceClockAndSendFrames() while (!closed) { // Just past the timeout - mockTimeProvider.Advance(Constants.RequestBodyDrainTimeout + TimeSpan.FromTicks(1)); + mockTimeProvider.Advance(Constants.RequestBodyDrainTimeout + TimeSpan.FromMilliseconds(1)); // Send an extra frame to make it fail switch (finalFrameType) @@ -631,7 +631,7 @@ await ExpectAsync(Http2FrameType.DATA, _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -865,7 +865,7 @@ await ExpectAsync(Http2FrameType.DATA, _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); @@ -953,7 +953,7 @@ await ExpectAsync(Http2FrameType.HEADERS, _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromTicks(1)); + AdvanceTime(TimeSpan.FromMicroseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs index cbe0d1ce1d14..0d9ab3acb6d2 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs @@ -338,7 +338,7 @@ await InitializeConnectionAsync(async context => await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers); // Don't send any more data and advance just to and then past the grace period. - AdvanceTime(limits.MinRequestBodyDataRate.GracePeriod + TimeSpan.FromTicks(1)); + AdvanceTime(limits.MinRequestBodyDataRate.GracePeriod + TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs index d0a8e9b02a59..f616a0c4f0f8 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Net.Http; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http; @@ -25,7 +26,7 @@ public async Task KeepAliveTimeout_ControlStreamNotReceived_ConnectionClosed() var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout(); await controlStream.ExpectSettingsAsync().DefaultTimeout(); - Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromMilliseconds(1)); await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -41,7 +42,7 @@ public async Task KeepAliveTimeout_RequestNotReceived_ConnectionClosed() var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout(); await controlStream.ExpectSettingsAsync().DefaultTimeout(); - Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromMilliseconds(1)); await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -69,7 +70,7 @@ public async Task KeepAliveTimeout_AfterRequestComplete_ConnectionClosed() await requestStream.ExpectReceiveEndOfStream(); await requestStream.OnDisposedTask.DefaultTimeout(); - Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -114,7 +115,7 @@ await Http3Api.InitializeConnectionAsync(_ => await requestStream.ExpectReceiveEndOfStream(); await requestStream.OnDisposedTask.DefaultTimeout(); - Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -122,7 +123,8 @@ await Http3Api.InitializeConnectionAsync(_ => [Fact] public async Task HEADERS_IncompleteFrameReceivedWithinRequestHeadersTimeout_StreamError() { - var now = _serviceContext.MockTimeProvider.GetUtcNow(); + var timeProvider = _serviceContext.MockTimeProvider; + var now = timeProvider.GetTimestamp(); var limits = _serviceContext.ServerOptions.Limits; var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, null).DefaultTimeout(); @@ -137,11 +139,11 @@ public async Task HEADERS_IncompleteFrameReceivedWithinRequestHeadersTimeout_Str var serverRequestStream = Http3Api.Connection._streams[requestStream.StreamId]; Http3Api.TriggerTick(now); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout); + Http3Api.AdvanceTime(limits.RequestHeadersTimeout); - Assert.Equal((now + limits.RequestHeadersTimeout).Ticks, serverRequestStream.StreamTimeoutTicks); + Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverRequestStream.StreamTimeoutTicks); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); await requestStream.WaitForStreamErrorAsync( Http3ErrorCode.RequestRejected, @@ -156,7 +158,7 @@ public async Task HEADERS_HeaderFrameReceivedWithinRequestHeadersTimeout_Success { Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = pendingStreamsEnabled; - var now = _serviceContext.MockTimeProvider.GetUtcNow(); + var now = _serviceContext.MockTimeProvider.GetTimestamp(); var limits = _serviceContext.ServerOptions.Limits; var headers = new[] { @@ -187,15 +189,15 @@ public async Task HEADERS_HeaderFrameReceivedWithinRequestHeadersTimeout_Success } Http3Api.TriggerTick(now); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout); + Http3Api.AdvanceTime(limits.RequestHeadersTimeout); - Assert.Equal((now + limits.RequestHeadersTimeout).Ticks, serverRequestStream.StreamTimeoutTicks); + Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(_serviceContext.TimeProvider.TimestampFrequency), serverRequestStream.StreamTimeoutTicks); await requestStream.SendHeadersAsync(headers).DefaultTimeout(); await requestStream.OnHeaderReceivedTask.DefaultTimeout(); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); await requestStream.SendDataAsync(Memory.Empty, endStream: true); @@ -209,7 +211,8 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str { Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = true; - var now = _serviceContext.MockTimeProvider.GetUtcNow(); + var timeProvider = _serviceContext.MockTimeProvider; + var now = timeProvider.GetTimestamp(); var limits = _serviceContext.ServerOptions.Limits; await Http3Api.InitializeConnectionAsync(_noopApplication).DefaultTimeout(); @@ -223,11 +226,11 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str var serverInboundControlStream = Http3Api.Connection._unidentifiedStreams[outboundControlStream.StreamId]; Http3Api.TriggerTick(now); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout); + Http3Api.AdvanceTime(limits.RequestHeadersTimeout); - Assert.Equal((now + limits.RequestHeadersTimeout).Ticks, serverInboundControlStream.StreamTimeoutTicks); + Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTicks); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); } [Fact] @@ -235,7 +238,9 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str { Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = false; - var now = _serviceContext.MockTimeProvider.GetUtcNow(); + var timeProvider = _serviceContext.MockTimeProvider; + var now = timeProvider.GetTimestamp(); + Http3Api._timeoutControl.Initialize(now); var limits = _serviceContext.ServerOptions.Limits; var headers = new[] { @@ -257,11 +262,11 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str var serverInboundControlStream = Http3Api.Connection._streams[outboundControlStream.StreamId]; Http3Api.TriggerTick(now); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout); + Http3Api.AdvanceTime(limits.RequestHeadersTimeout); - Assert.Equal((now + limits.RequestHeadersTimeout).Ticks, serverInboundControlStream.StreamTimeoutTicks); + Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTicks); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); await outboundControlStream.WaitForStreamErrorAsync( Http3ErrorCode.StreamCreationError, @@ -281,13 +286,13 @@ public async Task ControlStream_HeaderReceivedWithinRequestHeadersTimeout_Stream await controlStream.ExpectSettingsAsync().DefaultTimeout(); Http3Api.TriggerTick(now); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); + Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromMilliseconds(1)); var outboundControlStream = await Http3Api.CreateControlStream(id: 0); await outboundControlStream.OnStreamCreatedTask.DefaultTimeout(); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); + Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromMilliseconds(1)); } [Theory] @@ -297,7 +302,8 @@ public async Task ControlStream_RequestHeadersTimeoutMaxValue_ExpirationIsMaxVal { Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = pendingStreamEnabled; - var now = _serviceContext.MockTimeProvider.GetUtcNow(); + var timeProvider = _serviceContext.MockTimeProvider; + var now = timeProvider.GetTimestamp(); var limits = _serviceContext.ServerOptions.Limits; limits.RequestHeadersTimeout = TimeSpan.MaxValue; @@ -322,7 +328,7 @@ public async Task ControlStream_RequestHeadersTimeoutMaxValue_ExpirationIsMaxVal Http3Api.TriggerTick(now); - Assert.Equal(TimeSpan.MaxValue.Ticks, serverInboundControlStream.StreamTimeoutTicks); + Assert.Equal(TimeSpan.MaxValue.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTicks); } [Fact] @@ -354,7 +360,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -395,10 +401,10 @@ public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() Http3Api.TriggerTick(now); Assert.Null(requestStream.StreamContext._error); - Http3Api.TriggerTick(now + TimeSpan.FromTicks(1)); + Http3Api.TriggerTick(now + TimeSpan.FromMilliseconds(1)); Assert.Null(requestStream.StreamContext._error); - Http3Api.TriggerTick(now + limits.MinResponseDataRate.GracePeriod + TimeSpan.FromTicks(1)); + Http3Api.TriggerTick(now + limits.MinResponseDataRate.GracePeriod + TimeSpan.FromMilliseconds(1)); requestStream.StartStreamDisposeTcs.TrySetResult(); @@ -713,7 +719,7 @@ await Http3Api.InitializeConnectionAsync(context => _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs index d15f2bbcd64a..290d7fa9b12c 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs @@ -27,7 +27,6 @@ public class KeepAliveTimeoutTests : LoggedTest public async Task ConnectionClosedWhenKeepAliveTimeoutExpires() { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); await using (var server = CreateServer(testContext)) { @@ -43,8 +42,8 @@ await connection.Send( await ReceiveResponse(connection, testContext); // Min amount of time between requests that triggers a keep-alive timeout. - testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + testContext.ConnectionManager.OnHeartbeat(); await connection.WaitForConnectionClose(); } @@ -55,7 +54,6 @@ await connection.Send( public async Task ConnectionKeptAliveBetweenRequests() { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); await using (var server = CreateServer(testContext)) { @@ -74,7 +72,7 @@ await connection.Send( // Max amount of time between requests that doesn't trigger a keep-alive timeout. testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.ConnectionManager.OnHeartbeat(); } } } @@ -84,7 +82,6 @@ await connection.Send( public async Task ConnectionNotTimedOutWhileRequestBeingSent() { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); await using (var server = CreateServer(testContext)) { @@ -109,7 +106,7 @@ await connection.Send( ""); testContext.MockTimeProvider.Advance(_shortDelay); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.ConnectionManager.OnHeartbeat(); } await connection.Send( @@ -125,7 +122,6 @@ await connection.Send( private async Task ConnectionNotTimedOutWhileAppIsRunning() { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var cts = new CancellationTokenSource(); await using (var server = CreateServer(testContext, longRunningCt: cts.Token)) @@ -145,7 +141,7 @@ await connection.Send( for (var totalDelay = TimeSpan.Zero; totalDelay < _longDelay; totalDelay += _shortDelay) { testContext.MockTimeProvider.Advance(_shortDelay); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.ConnectionManager.OnHeartbeat(); } cts.Cancel(); @@ -166,7 +162,6 @@ await connection.Send( private async Task ConnectionTimesOutWhenOpenedButNoRequestSent() { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); await using (var server = CreateServer(testContext)) { @@ -175,8 +170,8 @@ private async Task ConnectionTimesOutWhenOpenedButNoRequestSent() await connection.TransportConnection.WaitForReadTask; // Min amount of time between requests that triggers a keep-alive timeout. - testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + testContext.ConnectionManager.OnHeartbeat(); await connection.WaitForConnectionClose(); } @@ -187,7 +182,6 @@ private async Task ConnectionTimesOutWhenOpenedButNoRequestSent() private async Task KeepAliveTimeoutDoesNotApplyToUpgradedConnections() { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var cts = new CancellationTokenSource(); await using (var server = CreateServer(testContext, upgradeCt: cts.Token)) @@ -212,7 +206,7 @@ await connection.Receive( for (var totalDelay = TimeSpan.Zero; totalDelay < _longDelay; totalDelay += _shortDelay) { testContext.MockTimeProvider.Advance(_shortDelay); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.ConnectionManager.OnHeartbeat(); } cts.Cancel(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs index fe5e1aad77e2..8d791988fd16 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestBodyTimeoutTests.cs @@ -22,7 +22,6 @@ public async Task RequestTimesOutWhenRequestBodyNotReceivedAtSpecifiedMinimumRat { var gracePeriod = TimeSpan.FromSeconds(5); var serviceContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(serviceContext.ConnectionManager); var appRunningEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -74,7 +73,7 @@ await connection.Send( for (var i = 0; i < 6; i++) { serviceContext.MockTimeProvider.Advance(TimeSpan.FromSeconds(1)); - heartbeatManager.OnHeartbeat(serviceContext.TimeProvider.GetUtcNow()); + serviceContext.ConnectionManager.OnHeartbeat(); } await connection.Receive( @@ -99,8 +98,8 @@ public async Task RequestTimesOutWhenNotDrainedWithinDrainTimeoutPeriod() // Ensure there's still a constant date header value. var timeProvider = new MockTimeProvider(); - var date = new DateHeaderValueManager(); - date.OnHeartbeat(timeProvider.GetUtcNow()); + var date = new DateHeaderValueManager(timeProvider); + date.OnHeartbeat(); serviceContext.DateHeaderValueManager = date; var appRunningEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -143,7 +142,6 @@ public async Task ConnectionClosedEvenIfAppSwallowsException() { var gracePeriod = TimeSpan.FromSeconds(5); var serviceContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(serviceContext.ConnectionManager); var appRunningTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var exceptionSwallowedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -191,7 +189,7 @@ await connection.Send( for (var i = 0; i < 6; i++) { serviceContext.MockTimeProvider.Advance(TimeSpan.FromSeconds(1)); - heartbeatManager.OnHeartbeat(serviceContext.TimeProvider.GetUtcNow()); + serviceContext.ConnectionManager.OnHeartbeat(); } await exceptionSwallowedTcs.Task.DefaultTimeout(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs index 6ac91ea45c3b..e3f6ff729b6a 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs @@ -5,6 +5,7 @@ using System.IO.Pipelines; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.Testing; @@ -25,7 +26,6 @@ public class RequestHeadersTimeoutTests : LoggedTest public async Task ConnectionAbortedWhenRequestHeadersNotReceivedInTime(string headers) { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); await using (var server = CreateServer(testContext)) { @@ -38,8 +38,8 @@ await connection.Send( headers); // Min amount of time between requests that triggers a request headers timeout. - testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + testContext.ConnectionManager.OnHeartbeat(); await ReceiveTimeoutResponse(connection, testContext); } @@ -50,7 +50,6 @@ await connection.Send( public async Task RequestHeadersTimeoutCanceledAfterHeadersReceived() { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); await using (var server = CreateServer(testContext)) { @@ -66,8 +65,8 @@ await connection.Send( ""); // Min amount of time between requests that triggers a request headers timeout. - testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + testContext.ConnectionManager.OnHeartbeat(); await connection.Send( "a"); @@ -83,7 +82,6 @@ await connection.Send( public async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(string requestLine) { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); await using (var server = CreateServer(testContext)) { @@ -94,8 +92,8 @@ public async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(string reque await connection.Send(requestLine); // Min amount of time between requests that triggers a request headers timeout. - testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + testContext.ConnectionManager.OnHeartbeat(); await ReceiveTimeoutResponse(connection, testContext); } @@ -106,7 +104,6 @@ public async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(string reque public async Task TimeoutNotResetOnEachRequestLineCharacterReceived() { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); // Disable response rate, so we can finish the send loop without timing out the response. testContext.ServerOptions.Limits.MinResponseDataRate = null; @@ -122,7 +119,7 @@ public async Task TimeoutNotResetOnEachRequestLineCharacterReceived() await connection.Send(ch.ToString()); testContext.MockTimeProvider.Advance(ShortDelay); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.ConnectionManager.OnHeartbeat(); } await ReceiveTimeoutResponse(connection, testContext); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index 948741cbd4b0..019cff309d88 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -1628,12 +1628,11 @@ public async Task DoesNotEnforceRequestBodyMinimumDataRateOnUpgradedRequest() var appEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var delayEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var serviceContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(serviceContext.ConnectionManager); await using (var server = new TestServer(async context => { context.Features.Get().MinDataRate = - new MinDataRate(bytesPerSecond: double.MaxValue, gracePeriod: Heartbeat.Interval + TimeSpan.FromTicks(1)); + new MinDataRate(bytesPerSecond: double.MaxValue, gracePeriod: Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); using (var stream = await context.Features.Get().UpgradeAsync()) { @@ -1665,7 +1664,7 @@ await connection.Send( await appEvent.Task.DefaultTimeout(); serviceContext.MockTimeProvider.Advance(TimeSpan.FromSeconds(5)); - heartbeatManager.OnHeartbeat(serviceContext.TimeProvider.GetUtcNow()); + serviceContext.ConnectionManager.OnHeartbeat(); delayEvent.SetResult(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs index 587682b7ccfa..c88063eed7bb 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs @@ -5,6 +5,7 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.Testing; @@ -26,7 +27,6 @@ public class ResponseDrainingTests : TestApplicationErrorLoggerLoggedTest public async Task ConnectionClosedWhenResponseNotDrainedAtMinimumDataRate(ListenOptions listenOptions) { var testContext = new TestServiceContext(LoggerFactory); - var heartbeatManager = new HeartbeatManager(testContext.ConnectionManager); var minRate = new MinDataRate(16384, TimeSpan.FromSeconds(2)); await using (var server = new TestServer(context => @@ -63,16 +63,16 @@ await connection.Send( for (var i = 0; i < 2; i++) { testContext.MockTimeProvider.Advance(TimeSpan.FromSeconds(1)); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.ConnectionManager.OnHeartbeat(); } testContext.MockTimeProvider.Advance(Heartbeat.Interval - TimeSpan.FromSeconds(.5)); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.ConnectionManager.OnHeartbeat(); Assert.Null(transportConnection.AbortReason); testContext.MockTimeProvider.Advance(TimeSpan.FromSeconds(1)); - heartbeatManager.OnHeartbeat(testContext.TimeProvider.GetUtcNow()); + testContext.ConnectionManager.OnHeartbeat(); Assert.NotNull(transportConnection.AbortReason); Assert.Equal(CoreStrings.ConnectionTimedBecauseResponseMininumDataRateNotSatisfied, transportConnection.AbortReason.Message); From 963b692daf10b9797fbbad4e323332dc4d254f8d Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 11 May 2023 09:50:57 -0700 Subject: [PATCH 06/17] Cleanup, naming --- .../Core/src/Internal/Http/Http1Connection.cs | 9 +--- .../src/Internal/Http/Http1MessageBody.cs | 3 +- .../src/Internal/Http2/Http2Connection.cs | 20 +++---- .../Core/src/Internal/Http2/Http2Stream.cs | 6 +-- .../src/Internal/Http3/Http3Connection.cs | 54 +++++++++---------- .../src/Internal/Http3/Http3ControlStream.cs | 2 +- .../src/Internal/Http3/Http3PendingStream.cs | 6 +-- .../Core/src/Internal/Http3/Http3Stream.cs | 6 +-- .../Core/src/Internal/Http3/IHttp3Stream.cs | 2 +- .../Infrastructure/ITimeoutControl.cs | 6 +-- .../Internal/Infrastructure/TimeoutControl.cs | 25 +++++---- .../Core/test/Http1/Http1ConnectionTests.cs | 5 +- .../Core/test/PooledStreamStackTests.cs | 2 +- .../Kestrel/Core/test/TimeoutControlTests.cs | 18 +++---- .../src/Internal/QuicConnectionContext.cs | 10 ++-- .../src/Internal/QuicStreamContext.cs | 4 +- .../test/QuicConnectionContextTests.cs | 4 +- .../Mocks/MockTimeoutControl.cs | 4 +- .../Kestrel/shared/PooledStreamStack.cs | 10 ++-- .../shared/test/Http3/Http3InMemory.cs | 8 +-- .../Http2/Http2TestBase.cs | 8 +-- .../Http2/Http2TimeoutTests.cs | 34 ++++++------ .../Http2/Http2WebSocketTests.cs | 2 +- .../Http3/Http3TimeoutTests.cs | 38 ++++++------- .../KeepAliveTimeoutTests.cs | 4 +- .../RequestHeadersTimeoutTests.cs | 6 +-- .../InMemory.FunctionalTests/RequestTests.cs | 2 +- 27 files changed, 144 insertions(+), 154 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 66ea4bd9830f..4af1069dd24a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -28,8 +28,6 @@ internal partial class Http1Connection : HttpProtocol, IRequestProcessor, IHttpO private readonly HttpConnectionContext _context; private readonly IHttpParser _parser; private readonly Http1OutputProducer _http1Output; - protected readonly long _keepAliveTicks; - private readonly long _requestHeadersTimeoutTicks; private volatile bool _requestTimedOut; private uint _requestCount; @@ -55,9 +53,6 @@ public Http1Connection(HttpConnectionContext context) _context = context; _parser = ServiceContext.HttpParser; - var frequency = context.ServiceContext.TimeProvider.TimestampFrequency; - _keepAliveTicks = ServerOptions.Limits.KeepAliveTimeout.ToTicks(frequency); - _requestHeadersTimeoutTicks = ServerOptions.Limits.RequestHeadersTimeout.ToTicks(frequency); _http1Output = new Http1OutputProducer( _context.Transport.Output, @@ -167,7 +162,7 @@ public bool ParseRequest(ref SequenceReader reader) break; } - TimeoutControl.ResetTimeout(_requestHeadersTimeoutTicks, TimeoutReason.RequestHeaders); + TimeoutControl.ResetTimeout(ServerOptions.Limits.RequestHeadersTimeout, TimeoutReason.RequestHeaders); _requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine; goto case RequestProcessingStatus.ParsingRequestLine; @@ -672,7 +667,7 @@ protected override void BeginRequestProcessing() // Reset the features and timeout. Reset(); _requestCount++; - TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutReason.KeepAlive); + TimeoutControl.SetTimeout(ServerOptions.Limits.KeepAliveTimeout, TimeoutReason.KeepAlive); } protected override bool BeginRead(out ValueTask awaitable) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index f82cf0bb29b2..f0d34685e004 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -83,8 +83,7 @@ protected async Task OnConsumeAsyncAwaited() { Log.RequestBodyNotEntirelyRead(_context.ConnectionIdFeature, _context.TraceIdentifier); - _context.TimeoutControl.SetTimeout( - Constants.RequestBodyDrainTimeout.ToTicks(_context.ServiceContext.TimeProvider.TimestampFrequency), TimeoutReason.RequestBodyDrain); + _context.TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout, TimeoutReason.RequestBodyDrain); try { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 1ca89cf89a78..b2501f1f137f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -209,7 +209,7 @@ public async Task ProcessRequestsAsync(IHttpApplication appl ValidateTlsRequirements(); TimeoutControl.InitializeHttp2(_inputFlowControl); - TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.ToTicks(TimeProvider.TimestampFrequency), TimeoutReason.KeepAlive); + TimeoutControl.SetTimeout(Limits.KeepAliveTimeout, TimeoutReason.KeepAlive); if (!await TryReadPrefaceAsync()) { @@ -729,7 +729,7 @@ private Task ProcessHeadersFrameAsync(IHttpApplication appli if (!_incomingFrame.HeadersEndHeaders) { - TimeoutControl.SetTimeout(Limits.RequestHeadersTimeout.ToTicks(TimeProvider.TimestampFrequency), TimeoutReason.RequestHeaders); + TimeoutControl.SetTimeout(Limits.RequestHeadersTimeout, TimeoutReason.RequestHeaders); } // Start a new stream @@ -944,7 +944,7 @@ private Task ProcessPingFrameAsync(in ReadOnlySequence payload) // Incoming ping resets connection keep alive timeout if (TimeoutControl.TimerReason == TimeoutReason.KeepAlive) { - TimeoutControl.ResetTimeout(Limits.KeepAliveTimeout.ToTicks(TimeProvider.TimestampFrequency), TimeoutReason.KeepAlive); + TimeoutControl.ResetTimeout(Limits.KeepAliveTimeout, TimeoutReason.KeepAlive); } if (_incomingFrame.PingAck) @@ -1255,7 +1255,7 @@ void IHttp2StreamLifetimeHandler.OnStreamCompleted(Http2Stream stream) private void UpdateCompletedStreams() { Http2Stream? firstRequedStream = null; - var now = TimeProvider.GetTimestamp(); + var timestamp = TimeProvider.GetTimestamp(); while (_completedStreams.TryDequeue(out var stream)) { @@ -1267,13 +1267,13 @@ private void UpdateCompletedStreams() break; } - if (stream.DrainExpirationTicks == default) + if (stream.DrainExpirationTimestamp == default) { _serverActiveStreamCount--; - stream.DrainExpirationTicks = now + Constants.RequestBodyDrainTimeout.ToTicks(TimeProvider.TimestampFrequency); + stream.DrainExpirationTimestamp = timestamp + Constants.RequestBodyDrainTimeout.ToTicks(TimeProvider.TimestampFrequency); } - if (stream.EndStreamReceived || stream.RstStreamReceived || stream.DrainExpirationTicks < now) + if (stream.EndStreamReceived || stream.RstStreamReceived || stream.DrainExpirationTimestamp < timestamp) { if (stream == _currentHeadersStream) { @@ -1304,7 +1304,7 @@ private void RemoveStream(Http2Stream stream) // Pool and reuse the stream if it finished in a graceful state and there is space in the pool. // This property is used to remove unused streams from the pool - stream.DrainExpirationTicks = TimeProvider.GetTimestamp() + StreamPoolExpirySeconds * TimeProvider.TimestampFrequency; + stream.DrainExpirationTimestamp = TimeProvider.GetTimestamp() + StreamPoolExpirySeconds * TimeProvider.TimestampFrequency; StreamPool.Push(stream); } @@ -1322,7 +1322,7 @@ private void MakeSpaceInDrainQueue() // If we're tracking too many streams, discard the oldest. while (_streams.Count >= maxStreams && _completedStreams.TryDequeue(out var stream)) { - if (stream.DrainExpirationTicks == default) + if (stream.DrainExpirationTimestamp == default) { _serverActiveStreamCount--; } @@ -1363,7 +1363,7 @@ private void UpdateConnectionState() { if (TimeoutControl.TimerReason == TimeoutReason.None) { - TimeoutControl.SetTimeout(Limits.KeepAliveTimeout.ToTicks(TimeProvider.TimestampFrequency), TimeoutReason.KeepAlive); + TimeoutControl.SetTimeout(Limits.KeepAliveTimeout, TimeoutReason.KeepAlive); } // If we're awaiting headers, either a new stream will be started, or there will be a connection diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs index 1ffa495f2f3d..c8cfc664ab6e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs @@ -29,7 +29,7 @@ internal abstract partial class Http2Stream : HttpProtocol, IThreadPoolWorkItem, public Pipe RequestBodyPipe { get; private set; } = default!; - internal long DrainExpirationTicks { get; set; } + internal long DrainExpirationTimestamp { get; set; } private StreamCompletionFlags _completionState; private readonly object _completionLock = new object(); @@ -42,7 +42,7 @@ public void Initialize(Http2StreamContext context) _completionState = StreamCompletionFlags.None; InputRemaining = null; RequestBodyStarted = false; - DrainExpirationTicks = 0; + DrainExpirationTimestamp = 0; TotalParsedHeaderSize = 0; // Allow up to 2x during parsing, enforce the hard limit after when we can preserve the connection. _eagerRequestHeadersParsedLimit = ServerOptions.Limits.MaxRequestHeaderCount * 2; @@ -724,5 +724,5 @@ void IPooledStream.DisposeCore() Dispose(); } - long IPooledStream.PoolExpirationTicks => DrainExpirationTicks; + long IPooledStream.PoolExpirationTimestamp => DrainExpirationTimestamp; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 0706b27bf569..49c1194ea280 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -190,7 +190,7 @@ public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode) } } - public void Tick(long now) + public void Tick(long timestamp) { if (_aborted) { @@ -199,11 +199,11 @@ public void Tick(long now) return; } - ValidateOpenControlStreams(now); - UpdateStreamTimeouts(now); + ValidateOpenControlStreams(timestamp); + UpdateStreamTimeouts(timestamp); } - private void ValidateOpenControlStreams(long now) + private void ValidateOpenControlStreams(long timestamp) { // This method validates that a connnection's control streams are open. // @@ -214,24 +214,24 @@ private void ValidateOpenControlStreams(long now) // // Realistically, control streams are never closed except when the connection is. A small delay in aborting the connection in the // unlikely situation where a control stream is incorrectly closed should be fine. - ValidateOpenControlStream(OutboundControlStream, this, now); - ValidateOpenControlStream(ControlStream, this, now); - ValidateOpenControlStream(EncoderStream, this, now); - ValidateOpenControlStream(DecoderStream, this, now); + ValidateOpenControlStream(OutboundControlStream, this, timestamp); + ValidateOpenControlStream(ControlStream, this, timestamp); + ValidateOpenControlStream(EncoderStream, this, timestamp); + ValidateOpenControlStream(DecoderStream, this, timestamp); - static void ValidateOpenControlStream(Http3ControlStream? stream, Http3Connection connection, long ticks) + static void ValidateOpenControlStream(Http3ControlStream? stream, Http3Connection connection, long timestamp) { if (stream != null) { if (stream.IsCompleted || stream.IsAborted || stream.EndStreamReceived) { // If a control stream is no longer active then set a timeout so that the connection is aborted next tick. - if (stream.StreamTimeoutTicks == default) + if (stream.StreamTimeoutTimestamp == default) { - stream.StreamTimeoutTicks = ticks; + stream.StreamTimeoutTimestamp = timestamp; } - if (stream.StreamTimeoutTicks < ticks) + if (stream.StreamTimeoutTimestamp < timestamp) { connection.OnStreamConnectionError(new Http3ConnectionErrorException("A control stream used by the connection was closed or reset.", Http3ErrorCode.ClosedCriticalStream)); } @@ -240,7 +240,7 @@ static void ValidateOpenControlStream(Http3ControlStream? stream, Http3Connectio } } - private void UpdateStreamTimeouts(long now) + private void UpdateStreamTimeouts(long timestamp) { // This method checks for timeouts: // 1. When a stream first starts and waits to receive headers. @@ -255,14 +255,14 @@ private void UpdateStreamTimeouts(long now) { foreach (var stream in _unidentifiedStreams.Values) { - if (stream.StreamTimeoutTicks == default) + if (stream.StreamTimeoutTimestamp == default) { // On expiration overflow, use max value. - var expirationTicks = now + requestHeadersTimeout; - stream.StreamTimeoutTicks = expirationTicks >= 0 ? expirationTicks : long.MaxValue; + var expiration = timestamp + requestHeadersTimeout; + stream.StreamTimeoutTimestamp = expiration >= 0 ? expiration : long.MaxValue; } - if (stream.StreamTimeoutTicks < now) + if (stream.StreamTimeoutTimestamp < timestamp) { stream.Abort(new("Stream timed out before its type was determined.")); } @@ -275,14 +275,14 @@ private void UpdateStreamTimeouts(long now) { if (stream.IsReceivingHeader) { - if (stream.StreamTimeoutTicks == default) + if (stream.StreamTimeoutTimestamp == default) { // On expiration overflow, use max value. - var expirationTicks = now + requestHeadersTimeout; - stream.StreamTimeoutTicks = expirationTicks >= 0 ? expirationTicks : long.MaxValue; + var expiration = timestamp + requestHeadersTimeout; + stream.StreamTimeoutTimestamp = expiration >= 0 ? expiration : long.MaxValue; } - if (stream.StreamTimeoutTicks < now) + if (stream.StreamTimeoutTimestamp < timestamp) { if (stream.IsRequestStream) { @@ -302,12 +302,12 @@ private void UpdateStreamTimeouts(long now) continue; } - if (stream.StreamTimeoutTicks == default) + if (stream.StreamTimeoutTimestamp == default) { - stream.StreamTimeoutTicks = TimeoutControl.GetResponseDrainDeadline(now, minDataRate); + stream.StreamTimeoutTimestamp = TimeoutControl.GetResponseDrainDeadline(timestamp, minDataRate); } - if (stream.StreamTimeoutTicks < now) + if (stream.StreamTimeoutTimestamp < timestamp) { // Cancel connection to be consistent with other data rate limits. Log.ResponseMinimumDataRateNotSatisfied(_context.ConnectionId, stream.TraceIdentifier); @@ -347,8 +347,7 @@ public async Task ProcessRequestsAsync(IHttpApplication appl outboundControlStreamTask = ProcessOutboundControlStreamAsync(outboundControlStream); // Close the connection if we don't receive any request streams - TimeoutControl.SetTimeout( - Limits.KeepAliveTimeout.ToTicks(_context.ServiceContext.TimeProvider.TimestampFrequency), TimeoutReason.KeepAlive); + TimeoutControl.SetTimeout(Limits.KeepAliveTimeout, TimeoutReason.KeepAlive); while (_stoppedAcceptingStreams == 0) { @@ -820,8 +819,7 @@ void IHttp3StreamLifetimeHandler.OnStreamCompleted(IHttp3Stream stream) if (_activeRequestCount == 0) { - TimeoutControl.SetTimeout( - Limits.KeepAliveTimeout.ToTicks(_context.ServiceContext.TimeProvider.TimestampFrequency), TimeoutReason.KeepAlive); + TimeoutControl.SetTimeout(Limits.KeepAliveTimeout, TimeoutReason.KeepAlive); } } _streams.Remove(stream.StreamId); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs index fa34f9b6829b..599a55f50212 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs @@ -69,7 +69,7 @@ private void OnStreamClosed() public PipeReader Input => _context.Transport.Input; public KestrelTrace Log => _context.ServiceContext.Log; - public long StreamTimeoutTicks { get; set; } + public long StreamTimeoutTimestamp { get; set; } public bool IsReceivingHeader => _headerType == -1; public bool IsDraining => false; public bool IsRequestStream => false; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3PendingStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3PendingStream.cs index af813249434e..b6db0bb810db 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3PendingStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3PendingStream.cs @@ -13,12 +13,12 @@ internal sealed class Http3PendingStream internal readonly Http3StreamContext Context; internal readonly long StreamId; - internal long StreamTimeoutTicks; + internal long StreamTimeoutTimestamp; public Http3PendingStream(Http3StreamContext context, long id) { Context = context; - StreamTimeoutTicks = default; + StreamTimeoutTimestamp = default; StreamId = id; } @@ -95,7 +95,7 @@ public async ValueTask ReadNextStreamHeaderAsync(Http3StreamContext contex } } - StreamTimeoutTicks = default; + StreamTimeoutTimestamp = default; } return -1L; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 1bbb40dde58a..3da9a0e70dcc 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -72,7 +72,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS public PipeReader Input => _context.Transport.Input; public KestrelServerLimits Limits => _context.ServiceContext.ServerOptions.Limits; public long StreamId => _streamIdFeature.StreamId; - public long StreamTimeoutTicks { get; set; } + public long StreamTimeoutTimestamp { get; set; } public bool IsReceivingHeader => _requestHeaderParsingState <= RequestHeaderParsingState.Headers; // Assigned once headers are received public bool IsDraining => _appCompletedTaskSource.GetStatus() != ValueTaskSourceStatus.Pending; // Draining starts once app is complete public bool IsRequestStream => true; @@ -100,7 +100,7 @@ public void Initialize(Http3StreamContext context) _eagerRequestHeadersParsedLimit = ServerOptions.Limits.MaxRequestHeaderCount * 2; _isMethodConnect = false; _completionState = default; - StreamTimeoutTicks = 0; + StreamTimeoutTimestamp = 0; if (_frameWriter == null) { @@ -857,7 +857,7 @@ private async Task ProcessHeadersFrameAsync(IHttpApplication } _requestHeaderParsingState = RequestHeaderParsingState.Body; - StreamTimeoutTicks = default; + StreamTimeoutTimestamp = default; _context.StreamLifetimeHandler.OnStreamHeaderReceived(this); ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/IHttp3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/IHttp3Stream.cs index 379b5e98444a..24a61c4b7885 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/IHttp3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/IHttp3Stream.cs @@ -20,7 +20,7 @@ internal interface IHttp3Stream /// 2. Between when the request delegate is complete and the transport draining. /// Value is driven by . /// - long StreamTimeoutTicks { get; set; } + long StreamTimeoutTimestamp { get; set; } /// /// The stream is receiving the header frame. diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs index 0df1623c7ef9..e2ca4bd81a7e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs @@ -9,8 +9,8 @@ internal interface ITimeoutControl { TimeoutReason TimerReason { get; } - void SetTimeout(long ticks, TimeoutReason timeoutReason); - void ResetTimeout(long ticks, TimeoutReason timeoutReason); + void SetTimeout(TimeSpan timeout, TimeoutReason timeoutReason); + void ResetTimeout(TimeSpan timeout, TimeoutReason timeoutReason); void CancelTimeout(); void InitializeHttp2(InputFlowControl connectionInputFlowControl); @@ -25,5 +25,5 @@ internal interface ITimeoutControl void StartTimingWrite(); void StopTimingWrite(); void BytesWrittenToBuffer(MinDataRate minRate, long count); - long GetResponseDrainDeadline(long ticks, MinDataRate minRate); + long GetResponseDrainDeadline(long timestamp, MinDataRate minRate); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs index 3d1c8c4d5d41..a9cdf041f2b4 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs @@ -48,10 +48,8 @@ internal void Initialize(long timestamp) Interlocked.Exchange(ref _lastTimestamp, timestamp); } - public void Tick(long now) + public void Tick(long timestamp) { - var timestamp = now; - CheckForTimeout(timestamp); CheckForReadDataRateTimeout(timestamp); CheckForWriteDataRateTimeout(timestamp); @@ -167,16 +165,16 @@ private void CheckForWriteDataRateTimeout(long timestamp) } } - public void SetTimeout(long ticks, TimeoutReason timeoutReason) + public void SetTimeout(TimeSpan timeout, TimeoutReason timeoutReason) { Debug.Assert(_timeoutTimestamp == long.MaxValue, "Concurrent timeouts are not supported."); - AssignTimeout(ticks, timeoutReason); + AssignTimeout(timeout, timeoutReason); } - public void ResetTimeout(long ticks, TimeoutReason timeoutReason) + public void ResetTimeout(TimeSpan timeout, TimeoutReason timeoutReason) { - AssignTimeout(ticks, timeoutReason); + AssignTimeout(timeout, timeoutReason); } public void CancelTimeout() @@ -186,12 +184,13 @@ public void CancelTimeout() TimerReason = TimeoutReason.None; } - private void AssignTimeout(long ticks, TimeoutReason timeoutReason) + private void AssignTimeout(TimeSpan timeout, TimeoutReason timeoutReason) { TimerReason = timeoutReason; // Add Heartbeat.Interval since this can be called right before the next heartbeat. - Interlocked.Exchange(ref _timeoutTimestamp, Interlocked.Read(ref _lastTimestamp) + ticks + _heartbeatIntervalTicks); + var timeoutTicks = timeout.ToTicks(_tickFrequency); + Interlocked.Exchange(ref _timeoutTimestamp, Interlocked.Read(ref _lastTimestamp) + timeoutTicks + _heartbeatIntervalTicks); } public void InitializeHttp2(InputFlowControl connectionInputFlowControl) @@ -322,7 +321,7 @@ void IConnectionTimeoutFeature.SetTimeout(TimeSpan timeSpan) throw new InvalidOperationException(CoreStrings.ConcurrentTimeoutsNotSupported); } - SetTimeout(timeSpan.ToTicks(_tickFrequency), TimeoutReason.TimeoutFeature); + SetTimeout(timeSpan, TimeoutReason.TimeoutFeature); } void IConnectionTimeoutFeature.ResetTimeout(TimeSpan timeSpan) @@ -332,13 +331,13 @@ void IConnectionTimeoutFeature.ResetTimeout(TimeSpan timeSpan) throw new ArgumentException(CoreStrings.PositiveFiniteTimeSpanRequired, nameof(timeSpan)); } - ResetTimeout(timeSpan.ToTicks(_tickFrequency), TimeoutReason.TimeoutFeature); + ResetTimeout(timeSpan, TimeoutReason.TimeoutFeature); } - public long GetResponseDrainDeadline(long ticks, MinDataRate minRate) + public long GetResponseDrainDeadline(long timestamp, MinDataRate minRate) { // On grace period overflow, use max value. - var gracePeriod = ticks + minRate.GracePeriod.ToTicks(_tickFrequency); + var gracePeriod = timestamp + minRate.GracePeriod.ToTicks(_tickFrequency); gracePeriod = gracePeriod >= 0 ? gracePeriod : long.MaxValue; return Math.Max(_writeTimingTimeoutTimestamp, gracePeriod); diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs index a4fe350f4de1..0a0be038a8ac 100644 --- a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs +++ b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs @@ -450,8 +450,7 @@ public async Task ParseRequestStartsRequestHeadersTimeoutOnFirstByteAvailable() ParseRequest((await _transport.Input.ReadAsync()).Buffer, out _consumed, out _examined); _transport.Input.AdvanceTo(_consumed, _examined); - var expectedRequestHeadersTimeout = _serviceContext.ServerOptions.Limits.RequestHeadersTimeout.ToTicks(_serviceContext.TimeProvider.TimestampFrequency); - _timeoutControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutReason.RequestHeaders)); + _timeoutControl.Verify(cc => cc.ResetTimeout(_serviceContext.ServerOptions.Limits.RequestHeadersTimeout, TimeoutReason.RequestHeaders)); } [Fact] @@ -570,7 +569,7 @@ public async Task ProcessRequestsAsyncEnablesKeepAliveTimeout() { var requestProcessingTask = _http1Connection.ProcessRequestsAsync(null); - var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.ToTicks(_serviceContext.TimeProvider.TimestampFrequency); + var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout; _timeoutControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutReason.KeepAlive)); _http1Connection.StopProcessingNextRequest(); diff --git a/src/Servers/Kestrel/Core/test/PooledStreamStackTests.cs b/src/Servers/Kestrel/Core/test/PooledStreamStackTests.cs index 8616f35f19d6..0f74c4eecb95 100644 --- a/src/Servers/Kestrel/Core/test/PooledStreamStackTests.cs +++ b/src/Servers/Kestrel/Core/test/PooledStreamStackTests.cs @@ -117,7 +117,7 @@ private static Http2Stream CreateStream(int streamId, long expirati return new Http2Stream(new DummyApplication(), context) { - DrainExpirationTicks = expirationTicks + DrainExpirationTimestamp = expirationTicks }; } } diff --git a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs index 5630973b425e..576b8799d9ca 100644 --- a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs +++ b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs @@ -30,7 +30,7 @@ public void DoesNotTimeOutWhenDebuggerIsAttached() var now = _timeProvider.GetTimestamp(); _timeoutControl.Initialize(now); - _timeoutControl.SetTimeout(1, TimeoutReason.RequestHeaders); + _timeoutControl.SetTimeout(TimeSpan.FromTicks(1), TimeoutReason.RequestHeaders); _timeoutControl.Tick(now + 2 + Heartbeat.Interval.ToTicks(_timeProvider.TimestampFrequency)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); @@ -262,7 +262,7 @@ public void ReadTimingNotEnforcedWhenTimeoutIsSet() _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); - _timeoutControl.SetTimeout(timeout.ToTicks(_timeProvider.TimestampFrequency), TimeoutReason.RequestBodyDrain); + _timeoutControl.SetTimeout(timeout, TimeoutReason.RequestBodyDrain); // Tick beyond grace period with low data rate _timeProvider.Advance(TimeSpan.FromSeconds(3)); @@ -273,7 +273,7 @@ public void ReadTimingNotEnforcedWhenTimeoutIsSet() _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // Tick just past timeout period, adjusted by Heartbeat.Interval - _timeProvider.Advance(TimeSpan.FromSeconds(2) + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + _timeProvider.Advance(TimeSpan.FromSeconds(2) + Heartbeat.Interval + TimeSpan.FromTicks(1)); _timeoutControl.Tick(_timeProvider.GetTimestamp()); // Timed out @@ -380,7 +380,7 @@ public void WriteTimingAbortsConnectionWhenWriteDoesNotCompleteWithMinimumDataRa _timeoutControl.StartTimingWrite(); // Tick just past 4s plus Heartbeat.Interval - AdvanceClock(TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + AdvanceClock(TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); } @@ -398,7 +398,7 @@ public void WriteTimingAbortsConnectionWhenSmallWriteDoesNotCompleteWithinGraceP _timeoutControl.StartTimingWrite(); // Tick just past 1s plus Heartbeat.Interval - AdvanceClock(TimeSpan.FromSeconds(1) + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + AdvanceClock(TimeSpan.FromSeconds(1) + Heartbeat.Interval + TimeSpan.FromTicks(1)); // Still within grace period, not timed out _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); @@ -426,7 +426,7 @@ public void WriteTimingTimeoutPushedOnConcurrentWrite() _timeoutControl.StartTimingWrite(); // Tick just past 5s plus Heartbeat.Interval, when the first write should have completed - AdvanceClock(TimeSpan.FromSeconds(5) + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + AdvanceClock(TimeSpan.FromSeconds(5) + Heartbeat.Interval + TimeSpan.FromTicks(1)); // Not timed out because the timeout was pushed by the second write _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); @@ -467,7 +467,7 @@ public void WriteTimingAbortsConnectionWhenRepeatedSmallWritesDoNotCompleteWithM _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // On more tick forward triggers the timeout. - _timeProvider.Advance(TimeSpan.FromMilliseconds(1)); + _timeProvider.Advance(TimeSpan.FromTicks(1)); _timeoutControl.Tick(_timeProvider.GetTimestamp()); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); @@ -486,13 +486,13 @@ public void WriteTimingOnlyCountsUpToOneHeartbeatIntervalPerTick() _timeoutControl.StartTimingWrite(); // Tick just past 4s plus Heartbeat.Interval at once - _timeProvider.Advance(TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + _timeProvider.Advance(TimeSpan.FromSeconds(4) + Heartbeat.Interval + TimeSpan.FromTicks(1)); _timeoutControl.Tick(_timeProvider.GetTimestamp()); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Never); // The last Tick only accounted for one heartbeat interval. Try again with a tick per interval. - AdvanceClock(TimeSpan.FromSeconds(4) + TimeSpan.FromMilliseconds(1)); + AdvanceClock(TimeSpan.FromSeconds(4) + TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.WriteDataRate), Times.Once); } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs index f2db39c37a0d..ff02fd3421d5 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs @@ -19,7 +19,7 @@ internal partial class QuicConnectionContext : TransportMultiplexedConnection private bool _streamPoolHeartbeatInitialized; // Ticks updated once per-second in heartbeat event. - private long _heartbeatTicks; + private long _heartbeatTimestamp; private readonly object _poolLock = new object(); private readonly object _shutdownLock = new object(); @@ -256,16 +256,16 @@ internal bool TryReturnStream(QuicStreamContext stream) heartbeatFeature.OnHeartbeat(static state => ((QuicConnectionContext)state).RemoveExpiredStreams(), this); - // Set ticks for the first time. Ticks are then updated in heartbeat. + // Set timestamp for the first time. Timestamps are then updated in heartbeat. var now = timeProvider.GetTimestamp(); - Volatile.Write(ref _heartbeatTicks, now); + Volatile.Write(ref _heartbeatTimestamp, now); _streamPoolHeartbeatInitialized = true; } if (stream.CanReuse && StreamPool.Count < MaxStreamPoolSize) { - stream.PoolExpirationTicks = Volatile.Read(ref _heartbeatTicks) + StreamPoolExpirySeconds * timeProvider.TimestampFrequency; + stream.PoolExpirationTimestamp = Volatile.Read(ref _heartbeatTimestamp) + StreamPoolExpirySeconds * timeProvider.TimestampFrequency; StreamPool.Push(stream); QuicLog.StreamPooled(_log, stream); @@ -287,7 +287,7 @@ private void RemoveExpiredStreams() { // Update ticks on heartbeat. A precise value isn't necessary. var now = _context.Options.TimeProvider.GetTimestamp(); - Volatile.Write(ref _heartbeatTicks, now); + Volatile.Write(ref _heartbeatTimestamp, now); StreamPool.RemoveExpired(now); } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs index 5f0a7d31e1ab..d81f0ec4757a 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs @@ -91,7 +91,7 @@ public void Initialize(QuicStream stream) CanWrite = _stream.CanWrite; _error = null; StreamId = _stream.Id; - PoolExpirationTicks = 0; + PoolExpirationTimestamp = 0; Transport = _originalTransport; Application = _originalApplication; @@ -136,7 +136,7 @@ public override string ConnectionId set => _connectionId = value; } - public long PoolExpirationTicks { get; set; } + public long PoolExpirationTimestamp { get; set; } public void Start() { diff --git a/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs b/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs index 3cc65608b473..7e0cb71f5bff 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs @@ -525,7 +525,7 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() Assert.Equal(1, quicConnectionContext.StreamPool.Count); QuicStreamContext pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); - Assert.Equal(testTimeProvider.GetTimestamp() + QuicConnectionContext.StreamPoolExpirySeconds * testTimeProvider.TimestampFrequency, pooledStream.PoolExpirationTicks); + Assert.Equal(testTimeProvider.GetTimestamp() + QuicConnectionContext.StreamPoolExpirySeconds * testTimeProvider.TimestampFrequency, pooledStream.PoolExpirationTimestamp); testTimeProvider.Advance((long)(0.1 * testTimeProvider.TimestampFrequency)); testHeartbeatFeature.RaiseHeartbeat(); @@ -537,7 +537,7 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() Assert.Equal(1, quicConnectionContext.StreamPool.Count); pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); - Assert.Equal(testTimeProvider.GetTimestamp() + QuicConnectionContext.StreamPoolExpirySeconds * testTimeProvider.TimestampFrequency, pooledStream.PoolExpirationTicks); + Assert.Equal(testTimeProvider.GetTimestamp() + QuicConnectionContext.StreamPoolExpirySeconds * testTimeProvider.TimestampFrequency, pooledStream.PoolExpirationTimestamp); Assert.Same(stream1, stream2); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs index 30a3bf22e7c2..f078b856a65f 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs @@ -33,11 +33,11 @@ public void InitializeHttp2(InputFlowControl connectionInputFlowControl) { } - public void ResetTimeout(long ticks, TimeoutReason timeoutReason) + public void ResetTimeout(TimeSpan timeout, TimeoutReason timeoutReason) { } - public void SetTimeout(long ticks, TimeoutReason timeoutReason) + public void SetTimeout(TimeSpan timeout, TimeoutReason timeoutReason) { } diff --git a/src/Servers/Kestrel/shared/PooledStreamStack.cs b/src/Servers/Kestrel/shared/PooledStreamStack.cs index 24f3a42e004d..93b04a4a3a8f 100644 --- a/src/Servers/Kestrel/shared/PooledStreamStack.cs +++ b/src/Servers/Kestrel/shared/PooledStreamStack.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel; internal interface IPooledStream { - long PoolExpirationTicks { get; } + long PoolExpirationTimestamp { get; } void DisposeCore(); } @@ -87,12 +87,12 @@ private void PushWithResize(TValue item) _size++; } - public void RemoveExpired(long now) + public void RemoveExpired(long timestamp) { int size = _size; StreamAsValueType[] array = _array; - var removeCount = CalculateRemoveCount(now, size, array); + var removeCount = CalculateRemoveCount(timestamp, size, array); if (removeCount == 0) { return; @@ -122,12 +122,12 @@ public void RemoveExpired(long now) _size = newSize; } - private static int CalculateRemoveCount(long now, int size, StreamAsValueType[] array) + private static int CalculateRemoveCount(long timestamp, int size, StreamAsValueType[] array) { for (var i = 0; i < size; i++) { TValue stream = array[i]; - if (stream.PoolExpirationTicks >= now) + if (stream.PoolExpirationTimestamp >= timestamp) { // Stream is still valid. All streams after this will have a later expiration. // No reason to keep checking. Return count of streams to remove. diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index 4a37920542d9..fd95802b20a9 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -217,11 +217,11 @@ public void TriggerTick(DateTimeOffset now) Connection?.Tick(_mockTimeProvider.GetTimestamp()); } - public void TriggerTick(long now) + public void TriggerTick(long timestamp) { - _mockTimeProvider.SetTimestamp(now); - _timeoutControl.Tick(now); - Connection?.Tick(now); + _mockTimeProvider.SetTimestamp(timestamp); + _timeoutControl.Tick(timestamp); + Connection?.Tick(timestamp); } public async Task InitializeConnectionAsync(RequestDelegate application) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index e433e52ee4df..9da157b7eaa9 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -1429,14 +1429,14 @@ public MockTimeoutControlBase(ITimeoutControl realTimeoutControl) public virtual TimeoutReason TimerReason => _realTimeoutControl.TimerReason; - public virtual void SetTimeout(long ticks, TimeoutReason timeoutReason) + public virtual void SetTimeout(TimeSpan timeout, TimeoutReason timeoutReason) { - _realTimeoutControl.SetTimeout(ticks, timeoutReason); + _realTimeoutControl.SetTimeout(timeout, timeoutReason); } - public virtual void ResetTimeout(long ticks, TimeoutReason timeoutReason) + public virtual void ResetTimeout(TimeSpan timeout, TimeoutReason timeoutReason) { - _realTimeoutControl.ResetTimeout(ticks, timeoutReason); + _realTimeoutControl.ResetTimeout(timeout, timeoutReason); } public virtual void CancelTimeout() diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs index 06d2089e02d8..96701afc427b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TimeoutTests.cs @@ -26,7 +26,7 @@ public async Task Preamble_NotReceivedInitially_WithinKeepAliveTimeout_ClosesCon _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromMilliseconds(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); @@ -46,7 +46,7 @@ public async Task HEADERS_NotReceivedInitially_WithinKeepAliveTimeout_ClosesConn _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromMilliseconds(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); @@ -65,13 +65,13 @@ public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_Cl AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval); // keep-alive timeout set but not fired. - _mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Once); + _mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Once); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); // The KeepAlive timeout is set when the stream completes processing on a background thread, so we need to hook the // keep-alive set afterwards to make a reliable test. var setTimeoutTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _mockTimeoutControl.Setup(c => c.SetTimeout(It.IsAny(), TimeoutReason.KeepAlive)).Callback((t, r) => + _mockTimeoutControl.Setup(c => c.SetTimeout(It.IsAny(), TimeoutReason.KeepAlive)).Callback((t, r) => { _timeoutControl.SetTimeout(t, r); setTimeoutTcs.SetResult(); @@ -81,7 +81,7 @@ public async Task HEADERS_NotReceivedAfterFirstRequest_WithinKeepAliveTimeout_Cl await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, _browserRequestHeaders); await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS); - _mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.RequestHeaders), Times.Once); + _mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.RequestHeaders), Times.Once); await ExpectAsync(Http2FrameType.HEADERS, withLength: 36, @@ -94,7 +94,7 @@ await ExpectAsync(Http2FrameType.HEADERS, _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromMilliseconds(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.KeepAlive), Times.Once); @@ -113,8 +113,8 @@ public async Task PING_WithinKeepAliveTimeout_ResetKeepAliveTimeout() await InitializeConnectionAsync(_noopApplication); // Connection starts and sets keep alive timeout - _mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Once); - _mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Never); + _mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Once); + _mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Never); await SendPingAsync(Http2PingFrameFlags.NONE); await ExpectAsync(Http2FrameType.PING, @@ -123,7 +123,7 @@ await ExpectAsync(Http2FrameType.PING, withStreamId: 0); // Server resets keep alive timeout - _mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Once); + _mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Once); } [Fact] @@ -136,8 +136,8 @@ public async Task PING_NoKeepAliveTimeout_DoesNotResetKeepAliveTimeout() await InitializeConnectionAsync(_echoApplication); // Connection starts and sets keep alive timeout - _mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Once); - _mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Never); + _mockTimeoutControl.Verify(c => c.SetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Once); + _mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Never); _mockTimeoutControl.Verify(c => c.CancelTimeout(), Times.Never); // Stream will stay open because it is waiting for request body to end @@ -153,7 +153,7 @@ await ExpectAsync(Http2FrameType.PING, withStreamId: 0); // Server doesn't reset keep alive timeout because it isn't running - _mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Never); + _mockTimeoutControl.Verify(c => c.ResetTimeout(It.IsAny(), TimeoutReason.KeepAlive), Times.Never); // End stream await SendDataAsync(1, _helloWorldBytes, endStream: true); @@ -184,7 +184,7 @@ public async Task HEADERS_ReceivedWithoutAllCONTINUATIONs_WithinRequestHeadersTi await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.NONE); - AdvanceTime(TimeSpan.FromMilliseconds(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.RequestHeaders), Times.Once); @@ -268,7 +268,7 @@ async Task AdvanceClockAndSendFrames() while (!closed) { // Just past the timeout - mockTimeProvider.Advance(Constants.RequestBodyDrainTimeout + TimeSpan.FromMilliseconds(1)); + mockTimeProvider.Advance(Constants.RequestBodyDrainTimeout + TimeSpan.FromTicks(1)); // Send an extra frame to make it fail switch (finalFrameType) @@ -631,7 +631,7 @@ await ExpectAsync(Http2FrameType.DATA, _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromMilliseconds(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -865,7 +865,7 @@ await ExpectAsync(Http2FrameType.DATA, _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromMilliseconds(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); @@ -953,7 +953,7 @@ await ExpectAsync(Http2FrameType.HEADERS, _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - AdvanceTime(TimeSpan.FromMicroseconds(1)); + AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs index 0d9ab3acb6d2..cbe0d1ce1d14 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2WebSocketTests.cs @@ -338,7 +338,7 @@ await InitializeConnectionAsync(async context => await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers); // Don't send any more data and advance just to and then past the grace period. - AdvanceTime(limits.MinRequestBodyDataRate.GracePeriod + TimeSpan.FromMilliseconds(1)); + AdvanceTime(limits.MinRequestBodyDataRate.GracePeriod + TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs index f616a0c4f0f8..d03514f3b6bc 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs @@ -26,7 +26,7 @@ public async Task KeepAliveTimeout_ControlStreamNotReceived_ConnectionClosed() var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout(); await controlStream.ExpectSettingsAsync().DefaultTimeout(); - Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromMilliseconds(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -42,7 +42,7 @@ public async Task KeepAliveTimeout_RequestNotReceived_ConnectionClosed() var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout(); await controlStream.ExpectSettingsAsync().DefaultTimeout(); - Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromMilliseconds(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(0, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -70,7 +70,7 @@ public async Task KeepAliveTimeout_AfterRequestComplete_ConnectionClosed() await requestStream.ExpectReceiveEndOfStream(); await requestStream.OnDisposedTask.DefaultTimeout(); - Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -115,7 +115,7 @@ await Http3Api.InitializeConnectionAsync(_ => await requestStream.ExpectReceiveEndOfStream(); await requestStream.OnDisposedTask.DefaultTimeout(); - Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + Http3Api.AdvanceTime(limits.KeepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); await Http3Api.WaitForConnectionStopAsync(4, false, expectedErrorCode: Http3ErrorCode.NoError); } @@ -141,9 +141,9 @@ public async Task HEADERS_IncompleteFrameReceivedWithinRequestHeadersTimeout_Str Http3Api.TriggerTick(now); Http3Api.AdvanceTime(limits.RequestHeadersTimeout); - Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverRequestStream.StreamTimeoutTicks); + Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverRequestStream.StreamTimeoutTimestamp); - Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); await requestStream.WaitForStreamErrorAsync( Http3ErrorCode.RequestRejected, @@ -191,13 +191,13 @@ public async Task HEADERS_HeaderFrameReceivedWithinRequestHeadersTimeout_Success Http3Api.TriggerTick(now); Http3Api.AdvanceTime(limits.RequestHeadersTimeout); - Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(_serviceContext.TimeProvider.TimestampFrequency), serverRequestStream.StreamTimeoutTicks); + Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(_serviceContext.TimeProvider.TimestampFrequency), serverRequestStream.StreamTimeoutTimestamp); await requestStream.SendHeadersAsync(headers).DefaultTimeout(); await requestStream.OnHeaderReceivedTask.DefaultTimeout(); - Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); await requestStream.SendDataAsync(Memory.Empty, endStream: true); @@ -228,9 +228,9 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str Http3Api.TriggerTick(now); Http3Api.AdvanceTime(limits.RequestHeadersTimeout); - Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTicks); + Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTimestamp); - Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); } [Fact] @@ -264,9 +264,9 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str Http3Api.TriggerTick(now); Http3Api.AdvanceTime(limits.RequestHeadersTimeout); - Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTicks); + Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTimestamp); - Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); await outboundControlStream.WaitForStreamErrorAsync( Http3ErrorCode.StreamCreationError, @@ -286,13 +286,13 @@ public async Task ControlStream_HeaderReceivedWithinRequestHeadersTimeout_Stream await controlStream.ExpectSettingsAsync().DefaultTimeout(); Http3Api.TriggerTick(now); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromMilliseconds(1)); + Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); var outboundControlStream = await Http3Api.CreateControlStream(id: 0); await outboundControlStream.OnStreamCreatedTask.DefaultTimeout(); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromMilliseconds(1)); + Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); } [Theory] @@ -328,7 +328,7 @@ public async Task ControlStream_RequestHeadersTimeoutMaxValue_ExpirationIsMaxVal Http3Api.TriggerTick(now); - Assert.Equal(TimeSpan.MaxValue.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTicks); + Assert.Equal(TimeSpan.MaxValue.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTimestamp); } [Fact] @@ -360,7 +360,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(TimeoutReason.ReadDataRate), Times.Once); @@ -401,10 +401,10 @@ public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() Http3Api.TriggerTick(now); Assert.Null(requestStream.StreamContext._error); - Http3Api.TriggerTick(now + TimeSpan.FromMilliseconds(1)); + Http3Api.TriggerTick(now + TimeSpan.FromTicks(1)); Assert.Null(requestStream.StreamContext._error); - Http3Api.TriggerTick(now + limits.MinResponseDataRate.GracePeriod + TimeSpan.FromMilliseconds(1)); + Http3Api.TriggerTick(now + limits.MinResponseDataRate.GracePeriod + TimeSpan.FromTicks(1)); requestStream.StartStreamDisposeTcs.TrySetResult(); @@ -719,7 +719,7 @@ await Http3Api.InitializeConnectionAsync(context => _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); - Http3Api.AdvanceTime(TimeSpan.FromMilliseconds(1)); + Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs index 290d7fa9b12c..57f7e8d8bce1 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KeepAliveTimeoutTests.cs @@ -42,7 +42,7 @@ await connection.Send( await ReceiveResponse(connection, testContext); // Min amount of time between requests that triggers a keep-alive timeout. - testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); testContext.ConnectionManager.OnHeartbeat(); await connection.WaitForConnectionClose(); @@ -170,7 +170,7 @@ private async Task ConnectionTimesOutWhenOpenedButNoRequestSent() await connection.TransportConnection.WaitForReadTask; // Min amount of time between requests that triggers a keep-alive timeout. - testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + testContext.MockTimeProvider.Advance(_keepAliveTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); testContext.ConnectionManager.OnHeartbeat(); await connection.WaitForConnectionClose(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs index e3f6ff729b6a..a379483bcd6d 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestHeadersTimeoutTests.cs @@ -38,7 +38,7 @@ await connection.Send( headers); // Min amount of time between requests that triggers a request headers timeout. - testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); testContext.ConnectionManager.OnHeartbeat(); await ReceiveTimeoutResponse(connection, testContext); @@ -65,7 +65,7 @@ await connection.Send( ""); // Min amount of time between requests that triggers a request headers timeout. - testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); testContext.ConnectionManager.OnHeartbeat(); await connection.Send( @@ -92,7 +92,7 @@ public async Task ConnectionAbortedWhenRequestLineNotReceivedInTime(string reque await connection.Send(requestLine); // Min amount of time between requests that triggers a request headers timeout. - testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + testContext.MockTimeProvider.Advance(RequestHeadersTimeout + Heartbeat.Interval + TimeSpan.FromTicks(1)); testContext.ConnectionManager.OnHeartbeat(); await ReceiveTimeoutResponse(connection, testContext); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index 019cff309d88..51f776e4dbd9 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -1632,7 +1632,7 @@ public async Task DoesNotEnforceRequestBodyMinimumDataRateOnUpgradedRequest() await using (var server = new TestServer(async context => { context.Features.Get().MinDataRate = - new MinDataRate(bytesPerSecond: double.MaxValue, gracePeriod: Heartbeat.Interval + TimeSpan.FromMilliseconds(1)); + new MinDataRate(bytesPerSecond: double.MaxValue, gracePeriod: Heartbeat.Interval + TimeSpan.FromTicks(1)); using (var stream = await context.Features.Get().UpgradeAsync()) { From cbfa3595d2824ee697f9229aea47a23ec155df66 Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 11 May 2023 12:55:44 -0700 Subject: [PATCH 07/17] Test fixes, cleanup --- .../Core/src/Internal/Http/Http1Connection.cs | 2 +- .../Core/src/Internal/Http2/Http2Connection.cs | 2 +- .../Kestrel/Core/src/Internal/HttpConnection.cs | 6 +++--- .../Kestrel/Core/src/Internal/IRequestProcessor.cs | 2 +- .../src/Internal/Infrastructure/ITimeoutControl.cs | 2 +- .../InMemory.FunctionalTests/Http2/Http2TestBase.cs | 12 ++++++------ .../Http3/Http3TimeoutTests.cs | 6 +++--- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs index 4af1069dd24a..869dbf26a31f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1Connection.cs @@ -786,5 +786,5 @@ protected override Task TryProduceInvalidRequestResponse() return base.TryProduceInvalidRequestResponse(); } - void IRequestProcessor.Tick(long now) { } + void IRequestProcessor.Tick(long timestamp) { } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index b2501f1f137f..801f2c6e7bd2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -1241,7 +1241,7 @@ private void AbortStream(int streamId, IOException error) } } - void IRequestProcessor.Tick(long now) + void IRequestProcessor.Tick(long timestamp) { Input.CancelPendingRead(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 8856db78bdb8..2d54344ddca5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -236,9 +236,9 @@ private void Tick() return; } - var now = _timeProvider.GetTimestamp(); - _timeoutControl.Tick(now); - _requestProcessor!.Tick(now); + var timestamp = _timeProvider.GetTimestamp(); + _timeoutControl.Tick(timestamp); + _requestProcessor!.Tick(timestamp); } public void OnTimeout(TimeoutReason reason) diff --git a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs index d904077c6aee..b1b5cfddbb14 100644 --- a/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs +++ b/src/Servers/Kestrel/Core/src/Internal/IRequestProcessor.cs @@ -13,6 +13,6 @@ internal interface IRequestProcessor void HandleRequestHeadersTimeout(); void HandleReadDataRateTimeout(); void OnInputOrOutputCompleted(); - void Tick(long now); + void Tick(long timestamp); void Abort(ConnectionAbortedException ex); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs index e2ca4bd81a7e..24cae41baf96 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/ITimeoutControl.cs @@ -14,7 +14,7 @@ internal interface ITimeoutControl void CancelTimeout(); void InitializeHttp2(InputFlowControl connectionInputFlowControl); - void Tick(long now); + void Tick(long timestamp); void StartRequestBody(MinDataRate minRate); void StopRequestBody(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 9da157b7eaa9..c4374b51ed59 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -1350,11 +1350,11 @@ protected void TriggerTick(DateTimeOffset? nowOrNull = null) ((IRequestProcessor)_connection)?.Tick(timestamp); } - protected void TriggerTick(long now) + protected void TriggerTick(long timestamp) { - _serviceContext.MockTimeProvider.SetTimestamp(now); - _timeoutControl.Tick(now); - ((IRequestProcessor)_connection)?.Tick(now); + _serviceContext.MockTimeProvider.SetTimestamp(timestamp); + _timeoutControl.Tick(timestamp); + ((IRequestProcessor)_connection)?.Tick(timestamp); } protected void AdvanceTime(TimeSpan timeSpan) @@ -1489,9 +1489,9 @@ public virtual void BytesWrittenToBuffer(MinDataRate minRate, long size) _realTimeoutControl.BytesWrittenToBuffer(minRate, size); } - public virtual void Tick(long now) + public virtual void Tick(long timestamp) { - _realTimeoutControl.Tick(now); + _realTimeoutControl.Tick(timestamp); } public long GetResponseDrainDeadline(long ticks, MinDataRate minRate) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs index d03514f3b6bc..6a38f10fc957 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs @@ -376,7 +376,7 @@ await Http3Api.WaitForConnectionErrorAsync( [Fact] public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() { - var now = _serviceContext.MockTimeProvider.GetUtcNow(); + var now = _serviceContext.MockTimeProvider.GetTimestamp(); var limits = _serviceContext.ServerOptions.Limits; var mockTimeProvider = _serviceContext.MockTimeProvider; @@ -401,10 +401,10 @@ public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() Http3Api.TriggerTick(now); Assert.Null(requestStream.StreamContext._error); - Http3Api.TriggerTick(now + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); Assert.Null(requestStream.StreamContext._error); - Http3Api.TriggerTick(now + limits.MinResponseDataRate.GracePeriod + TimeSpan.FromTicks(1)); + Http3Api.AdvanceTime(limits.MinResponseDataRate.GracePeriod); requestStream.StartStreamDisposeTcs.TrySetResult(); From bc90de8a53178bf877afea748095bd97b7e3f0ab Mon Sep 17 00:00:00 2001 From: Chris R Date: Mon, 15 May 2023 11:18:01 -0700 Subject: [PATCH 08/17] Cleanup --- .../src/Internal/Http2/Http2Connection.cs | 6 +- .../Core/src/Internal/Http2/Http2KeepAlive.cs | 4 +- .../src/Internal/Http3/Http3Connection.cs | 2 +- .../Core/src/Internal/HttpConnection.cs | 4 +- .../Internal/Infrastructure/TimeExtensions.cs | 34 ++++++ .../Infrastructure/TimeSpanExtensions.cs | 20 ---- .../Internal/Infrastructure/TimeoutControl.cs | 25 +++-- .../Core/test/DateHeaderValueManagerTests.cs | 24 ++-- .../Kestrel/Core/test/HeartbeatTests.cs | 6 - .../Core/test/PooledStreamStackTests.cs | 26 ++--- .../Kestrel/Core/test/StartLineTests.cs | 5 +- .../Kestrel/Core/test/TimeoutControlTests.cs | 38 +++---- ...Server.Kestrel.Transport.Quic.Tests.csproj | 1 + .../test/QuicConnectionContextTests.cs | 36 ++---- .../Http1ConnectionBenchmark.cs | 2 +- ...Http1ConnectionParsingOverheadBenchmark.cs | 2 +- .../Http1LargeWritingBenchmark.cs | 4 +- .../Microbenchmarks/Http1ReadingBenchmark.cs | 4 +- .../Microbenchmarks/Http1WritingBenchmark.cs | 4 +- .../Http2/Http2ConnectionBenchmarkBase.cs | 4 +- .../Mocks/MockTimeoutControl.cs | 2 +- .../RequestParsingBenchmark.cs | 4 +- .../ResponseHeaderCollectionBenchmark.cs | 2 +- .../ResponseHeadersWritingBenchmark.cs | 2 +- .../shared/test/Http3/Http3InMemory.cs | 25 ++--- .../Kestrel/shared/test/MockTimeProvider.cs | 51 ++++----- .../Http2/Http2KeepAliveTests.cs | 104 +++++++----------- .../Http2/Http2TestBase.cs | 34 +++--- .../Http3/Http3ConnectionTests.cs | 12 +- .../Http3/Http3TimeoutTests.cs | 68 ++++++------ .../ResponseDrainingTests.cs | 4 - 31 files changed, 253 insertions(+), 306 deletions(-) create mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeExtensions.cs delete mode 100644 src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 801f2c6e7bd2..46ac3fc82cbe 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -44,7 +44,7 @@ internal sealed partial class Http2Connection : IHttp2StreamLifetimeHandler, IHt private const int InitialStreamPoolSize = 5; private const int MaxStreamPoolSize = 100; - private const long StreamPoolExpirySeconds = 5; + private readonly TimeSpan StreamPoolExpiry = TimeSpan.FromSeconds(5); private readonly HttpConnectionContext _context; private readonly Http2FrameWriter _frameWriter; @@ -1270,7 +1270,7 @@ private void UpdateCompletedStreams() if (stream.DrainExpirationTimestamp == default) { _serverActiveStreamCount--; - stream.DrainExpirationTimestamp = timestamp + Constants.RequestBodyDrainTimeout.ToTicks(TimeProvider.TimestampFrequency); + stream.DrainExpirationTimestamp = TimeProvider.GetTimestamp(timestamp, Constants.RequestBodyDrainTimeout); } if (stream.EndStreamReceived || stream.RstStreamReceived || stream.DrainExpirationTimestamp < timestamp) @@ -1304,7 +1304,7 @@ private void RemoveStream(Http2Stream stream) // Pool and reuse the stream if it finished in a graceful state and there is space in the pool. // This property is used to remove unused streams from the pool - stream.DrainExpirationTimestamp = TimeProvider.GetTimestamp() + StreamPoolExpirySeconds * TimeProvider.TimestampFrequency; + stream.DrainExpirationTimestamp = TimeProvider.GetTimestamp(StreamPoolExpiry); StreamPool.Push(stream); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs index 7cfdcd55cfbe..166412f394da 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2KeepAlive.cs @@ -29,9 +29,9 @@ internal sealed class Http2KeepAlive public Http2KeepAlive(TimeSpan keepAliveInterval, TimeSpan keepAliveTimeout, TimeProvider timeProvider) { - _keepAliveInterval = keepAliveInterval.ToTicks(timeProvider.TimestampFrequency); + _keepAliveInterval = keepAliveInterval.ToTicks(timeProvider); _keepAliveTimeout = keepAliveTimeout == TimeSpan.MaxValue ? long.MaxValue - : keepAliveTimeout.ToTicks(timeProvider.TimestampFrequency); + : keepAliveTimeout.ToTicks(timeProvider); _timeProvider = timeProvider; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index 49c1194ea280..143adf903c6d 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -249,7 +249,7 @@ private void UpdateStreamTimeouts(long timestamp) // Uses MinResponseDataRate. var serviceContext = _context.ServiceContext; var requestHeadersTimeout = serviceContext.ServerOptions.Limits.RequestHeadersTimeout.ToTicks( - serviceContext.TimeProvider.TimestampFrequency); + serviceContext.TimeProvider); lock (_unidentifiedStreams) { diff --git a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs index 2d54344ddca5..889b311f30c0 100644 --- a/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/HttpConnection.cs @@ -36,7 +36,7 @@ public HttpConnection(BaseHttpConnectionContext context) _context = context; _timeProvider = _context.ServiceContext.TimeProvider; - _timeoutControl = new TimeoutControl(this, _timeProvider.TimestampFrequency); + _timeoutControl = new TimeoutControl(this, _timeProvider); // Tests override the timeout control sometimes _context.TimeoutControl ??= _timeoutControl; @@ -49,7 +49,7 @@ public async Task ProcessRequestsAsync(IHttpApplication http try { // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. - _timeoutControl.Initialize(_timeProvider.GetTimestamp()); + _timeoutControl.Initialize(); IRequestProcessor? requestProcessor = null; diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeExtensions.cs new file mode 100644 index 000000000000..81b170d5b40b --- /dev/null +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeExtensions.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System; + +internal static class TimeExtensions +{ + public static long ToTicks(this TimeSpan timeSpan, TimeProvider timeProvider) + => timeSpan.ToTicks(timeProvider.TimestampFrequency); + + public static long ToTicks(this TimeSpan timeSpan, long tickFrequency) + { + if (timeSpan == TimeSpan.MaxValue) + { + return long.MaxValue; + } + if (timeSpan == TimeSpan.MinValue) + { + return long.MinValue; + } + // The tick frequency should be equal or greater than TicksPerSecond + return timeSpan.Ticks * (tickFrequency / TimeSpan.TicksPerSecond); + } + + public static long GetTimestamp(this TimeProvider timeProvider, TimeSpan timeSpan) + { + return timeProvider.GetTimestamp(timeProvider.GetTimestamp(), timeSpan); + } + + public static long GetTimestamp(this TimeProvider timeProvider, long timeStamp, TimeSpan timeSpan) + { + return timeStamp + timeSpan.ToTicks(timeProvider); + } +} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs deleted file mode 100644 index ef45738686d3..000000000000 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeSpanExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System; - -internal static class TimeSpanExtensions -{ - public static long ToTicks(this TimeSpan timeSpan, long tickFrequency) - { - if (timeSpan == TimeSpan.MaxValue) - { - return long.MaxValue; - } - if (timeSpan == TimeSpan.MinValue) - { - return long.MinValue; - } - return (long)(timeSpan.TotalSeconds * tickFrequency); - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs index a9cdf041f2b4..01b98139065e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Diagnostics; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; @@ -10,9 +11,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; internal sealed class TimeoutControl : ITimeoutControl, IConnectionTimeoutFeature { private readonly ITimeoutHandler _timeoutHandler; + private readonly TimeProvider _timeProvider; private readonly long _heartbeatIntervalTicks; - private readonly long _tickFrequency; private long _lastTimestamp; private long _timeoutTimestamp = long.MaxValue; @@ -32,20 +33,20 @@ internal sealed class TimeoutControl : ITimeoutControl, IConnectionTimeoutFeatur private int _concurrentAwaitingWrites; private long _writeTimingTimeoutTimestamp; - public TimeoutControl(ITimeoutHandler timeoutHandler, long tickFrequency) + public TimeoutControl(ITimeoutHandler timeoutHandler, TimeProvider timeProvider) { _timeoutHandler = timeoutHandler; - _tickFrequency = tickFrequency; - _heartbeatIntervalTicks = Heartbeat.Interval.ToTicks(_tickFrequency); + _timeProvider = timeProvider; + _heartbeatIntervalTicks = Heartbeat.Interval.ToTicks(_timeProvider); } public TimeoutReason TimerReason { get; private set; } internal IDebugger Debugger { get; set; } = DebuggerWrapper.Singleton; - internal void Initialize(long timestamp) + internal void Initialize() { - Interlocked.Exchange(ref _lastTimestamp, timestamp); + Interlocked.Exchange(ref _lastTimestamp, _timeProvider.GetTimestamp()); } public void Tick(long timestamp) @@ -117,7 +118,7 @@ private void CheckForReadDataRateTimeout(long timestamp) if (_minReadRate.BytesPerSecond > 0 && _readTimingElapsedTicks > _minReadRateGracePeriodTicks) { - var elapsedSeconds = (double)_readTimingElapsedTicks / _tickFrequency; + var elapsedSeconds = (double)_readTimingElapsedTicks / _timeProvider.TimestampFrequency; var rate = _readTimingBytesRead / elapsedSeconds; timeout = rate < _minReadRate.BytesPerSecond && !Debugger.IsAttached; @@ -189,7 +190,7 @@ private void AssignTimeout(TimeSpan timeout, TimeoutReason timeoutReason) TimerReason = timeoutReason; // Add Heartbeat.Interval since this can be called right before the next heartbeat. - var timeoutTicks = timeout.ToTicks(_tickFrequency); + var timeoutTicks = timeout.ToTicks(_timeProvider); Interlocked.Exchange(ref _timeoutTimestamp, Interlocked.Read(ref _lastTimestamp) + timeoutTicks + _heartbeatIntervalTicks); } @@ -206,7 +207,7 @@ public void StartRequestBody(MinDataRate minRate) Debug.Assert(_concurrentIncompleteRequestBodies == 0 || minRate == _minReadRate, "Multiple simultaneous read data rates are not supported."); _minReadRate = minRate; - _minReadRateGracePeriodTicks = minRate.GracePeriod.ToTicks(_tickFrequency); + _minReadRateGracePeriodTicks = minRate.GracePeriod.ToTicks(_timeProvider); _concurrentIncompleteRequestBodies++; if (_concurrentIncompleteRequestBodies == 1) @@ -288,13 +289,13 @@ public void BytesWrittenToBuffer(MinDataRate minRate, long count) { // Add Heartbeat.Interval since this can be called right before the next heartbeat. var currentTimeUpperBound = Interlocked.Read(ref _lastTimestamp) + _heartbeatIntervalTicks; - var ticksToCompleteWriteAtMinRate = TimeSpan.FromSeconds(count / minRate.BytesPerSecond).ToTicks(_tickFrequency); + var ticksToCompleteWriteAtMinRate = TimeSpan.FromSeconds(count / minRate.BytesPerSecond).ToTicks(_timeProvider); // If ticksToCompleteWriteAtMinRate is less than the configured grace period, // allow that write to take up to the grace period to complete. Only add the grace period // to the current time and not to any accumulated timeout. var singleWriteTimeoutTimestamp = currentTimeUpperBound + Math.Max( - minRate.GracePeriod.ToTicks(_tickFrequency), + minRate.GracePeriod.ToTicks(_timeProvider), ticksToCompleteWriteAtMinRate); // Don't penalize a connection for completing previous writes more quickly than required. @@ -337,7 +338,7 @@ void IConnectionTimeoutFeature.ResetTimeout(TimeSpan timeSpan) public long GetResponseDrainDeadline(long timestamp, MinDataRate minRate) { // On grace period overflow, use max value. - var gracePeriod = timestamp + minRate.GracePeriod.ToTicks(_tickFrequency); + var gracePeriod = timestamp + minRate.GracePeriod.ToTicks(_timeProvider); gracePeriod = gracePeriod >= 0 ? gracePeriod : long.MaxValue; return Math.Max(_writeTimingTimeoutTimestamp, gracePeriod); diff --git a/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs b/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs index 3b9652b42bb2..4c0ef56a4314 100644 --- a/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs +++ b/src/Servers/Kestrel/Core/test/DateHeaderValueManagerTests.cs @@ -34,9 +34,8 @@ public void GetDateHeaderValue_ReturnsDateValueInRFC1123Format() [Fact] public void GetDateHeaderValue_ReturnsCachedValueBetweenTimerTicks() { - var now = DateTimeOffset.UtcNow; - var future = now.AddSeconds(10); - var timeProvider = new MockTimeProvider(now); + var timeProvider = new MockTimeProvider(); + var now = timeProvider.GetUtcNow(); var dateHeaderValueManager = new DateHeaderValueManager(timeProvider); dateHeaderValueManager.OnHeartbeat(); @@ -46,19 +45,19 @@ public void GetDateHeaderValue_ReturnsCachedValueBetweenTimerTicks() using (var heartbeat = new Heartbeat(new IHeartbeatHandler[] { dateHeaderValueManager }, timeProvider, DebuggerWrapper.Singleton, testKestrelTrace, Heartbeat.Interval)) { Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); - timeProvider.SetUtcNow(future); + timeProvider.Advance(TimeSpan.FromSeconds(10)); Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); } - Assert.Equal(1, timeProvider.UtcNowCalled); + Assert.Equal(2, timeProvider.UtcNowCalled); } [Fact] public void GetDateHeaderValue_ReturnsUpdatedValueAfterHeartbeat() { - var now = DateTimeOffset.UtcNow; + var timeProvider = new MockTimeProvider(); + var now = timeProvider.GetUtcNow(); var future = now.AddSeconds(10); - var timeProvider = new MockTimeProvider(now); var dateHeaderValueManager = new DateHeaderValueManager(timeProvider); dateHeaderValueManager.OnHeartbeat(); @@ -74,21 +73,20 @@ public void GetDateHeaderValue_ReturnsUpdatedValueAfterHeartbeat() Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); // Wait for the next heartbeat before verifying GetDateHeaderValues picks up new time. - timeProvider.SetUtcNow(future); + timeProvider.AdvanceTo(future); heartbeat.OnHeartbeat(); Assert.Equal(future.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); - Assert.Equal(3, timeProvider.UtcNowCalled); + Assert.Equal(4, timeProvider.UtcNowCalled); } } [Fact] public void GetDateHeaderValue_ReturnsLastDateValueAfterHeartbeatDisposed() { - var now = DateTimeOffset.UtcNow; - var future = now.AddSeconds(10); - var timeProvider = new MockTimeProvider(now); + var timeProvider = new MockTimeProvider(); + var now = timeProvider.GetUtcNow(); var dateHeaderValueManager = new DateHeaderValueManager(timeProvider); dateHeaderValueManager.OnHeartbeat(); @@ -101,7 +99,7 @@ public void GetDateHeaderValue_ReturnsLastDateValueAfterHeartbeatDisposed() Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); } - timeProvider.SetUtcNow(future); + timeProvider.Advance(TimeSpan.FromSeconds(10)); Assert.Equal(now.ToString(Rfc1123DateFormat), dateHeaderValueManager.GetDateHeaderValues().String); } } diff --git a/src/Servers/Kestrel/Core/test/HeartbeatTests.cs b/src/Servers/Kestrel/Core/test/HeartbeatTests.cs index 2a89865ecbbe..65d46db9ce29 100644 --- a/src/Servers/Kestrel/Core/test/HeartbeatTests.cs +++ b/src/Servers/Kestrel/Core/test/HeartbeatTests.cs @@ -32,8 +32,6 @@ public async void HeartbeatLoopRunsWithSpecifiedInterval() var heartbeatHandler = new Mock(); var debugger = new Mock(); var kestrelTrace = new KestrelTrace(LoggerFactory); - var now = timeProvider.GetUtcNow(); - var timestamp = timeProvider.GetTimestamp(); var splits = new List(); Stopwatch sw = null; @@ -106,8 +104,6 @@ public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsWarning() var kestrelTrace = new KestrelTrace(LoggerFactory); var handlerMre = new ManualResetEventSlim(); var handlerStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var now = timeProvider.GetUtcNow(); - var timestamp = timeProvider.GetTimestamp(); var heartbeatDuration = TimeSpan.FromSeconds(2); heartbeatHandler.Setup(h => h.OnHeartbeat()).Callback(() => @@ -151,8 +147,6 @@ public async Task HeartbeatTakingLongerThanIntervalIsNotLoggedIfDebuggerAttached var kestrelTrace = new KestrelTrace(LoggerFactory); var handlerMre = new ManualResetEventSlim(); var handlerStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var now = timeProvider.GetUtcNow(); - var timestamp = timeProvider.GetTimestamp(); heartbeatHandler.Setup(h => h.OnHeartbeat()).Callback(() => { diff --git a/src/Servers/Kestrel/Core/test/PooledStreamStackTests.cs b/src/Servers/Kestrel/Core/test/PooledStreamStackTests.cs index 0f74c4eecb95..ba64fb445d82 100644 --- a/src/Servers/Kestrel/Core/test/PooledStreamStackTests.cs +++ b/src/Servers/Kestrel/Core/test/PooledStreamStackTests.cs @@ -25,7 +25,7 @@ public void RemoveExpired_Empty_NoOp() public void RemoveExpired_NoneExpired_NoOp() { var streams = new PooledStreamStack(10); - streams.Push(CreateStream(streamId: 1, expirationTicks: 200)); + streams.Push(CreateStream(streamId: 1, expirationTimestamp: 200)); streams.RemoveExpired(100); @@ -37,7 +37,7 @@ public void RemoveExpired_NoneExpired_NoOp() public void RemoveExpired_OneExpired_ExpiredStreamRemoved() { var streams = new PooledStreamStack(10); - streams.Push(CreateStream(streamId: 1, expirationTicks: 200)); + streams.Push(CreateStream(streamId: 1, expirationTimestamp: 200)); streams.RemoveExpired(300); @@ -49,8 +49,8 @@ public void RemoveExpired_OneExpired_ExpiredStreamRemoved() public void RemoveExpired_MultipleExpired_ExpiredStreamsRemoved() { var streams = new PooledStreamStack(10); - streams.Push(CreateStream(streamId: 1, expirationTicks: 200)); - streams.Push(CreateStream(streamId: 2, expirationTicks: 250)); + streams.Push(CreateStream(streamId: 1, expirationTimestamp: 200)); + streams.Push(CreateStream(streamId: 2, expirationTimestamp: 250)); streams.RemoveExpired(300); @@ -63,8 +63,8 @@ public void RemoveExpired_MultipleExpired_ExpiredStreamsRemoved() public void RemoveExpired_OneExpiredAndOneValid_ExpiredStreamRemoved() { var streams = new PooledStreamStack(10); - streams.Push(CreateStream(streamId: 1, expirationTicks: 200)); - streams.Push(CreateStream(streamId: 2, expirationTicks: 400)); + streams.Push(CreateStream(streamId: 1, expirationTimestamp: 200)); + streams.Push(CreateStream(streamId: 2, expirationTimestamp: 400)); streams.RemoveExpired(300); @@ -77,11 +77,11 @@ public void RemoveExpired_OneExpiredAndOneValid_ExpiredStreamRemoved() public void RemoveExpired_AllExpired_ExpiredStreamRemoved() { var streams = new PooledStreamStack(5); - streams.Push(CreateStream(streamId: 1, expirationTicks: 200)); - streams.Push(CreateStream(streamId: 2, expirationTicks: 200)); - streams.Push(CreateStream(streamId: 3, expirationTicks: 200)); - streams.Push(CreateStream(streamId: 4, expirationTicks: 200)); - streams.Push(CreateStream(streamId: 5, expirationTicks: 200)); + streams.Push(CreateStream(streamId: 1, expirationTimestamp: 200)); + streams.Push(CreateStream(streamId: 2, expirationTimestamp: 200)); + streams.Push(CreateStream(streamId: 3, expirationTimestamp: 200)); + streams.Push(CreateStream(streamId: 4, expirationTimestamp: 200)); + streams.Push(CreateStream(streamId: 5, expirationTimestamp: 200)); streams.RemoveExpired(300); @@ -94,7 +94,7 @@ public void RemoveExpired_AllExpired_ExpiredStreamRemoved() Assert.Equal(default, streams._array[4]); } - private static Http2Stream CreateStream(int streamId, long expirationTicks) + private static Http2Stream CreateStream(int streamId, long expirationTimestamp) { var context = new Http2StreamContext ( @@ -117,7 +117,7 @@ private static Http2Stream CreateStream(int streamId, long expirati return new Http2Stream(new DummyApplication(), context) { - DrainExpirationTimestamp = expirationTicks + DrainExpirationTimestamp = expirationTimestamp }; } } diff --git a/src/Servers/Kestrel/Core/test/StartLineTests.cs b/src/Servers/Kestrel/Core/test/StartLineTests.cs index ca998499d2bd..feb3ea2580e0 100644 --- a/src/Servers/Kestrel/Core/test/StartLineTests.cs +++ b/src/Servers/Kestrel/Core/test/StartLineTests.cs @@ -524,16 +524,17 @@ public StartLineTests() var pair = DuplexPipe.CreateConnectionPair(options, options); Transport = pair.Transport; + var timeProvider = new MockTimeProvider(); var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), - timeProvider: new MockTimeProvider(), + timeProvider: timeProvider, httpParser: new HttpParser()); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, connectionContext: Mock.Of(), transport: Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), + timeoutControl: new TimeoutControl(timeoutHandler: null, timeProvider), memoryPool: MemoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs index 576b8799d9ca..3562700e30bf 100644 --- a/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs +++ b/src/Servers/Kestrel/Core/test/TimeoutControlTests.cs @@ -18,7 +18,7 @@ public TimeoutControlTests() { _mockTimeoutHandler = new Mock(); _timeProvider = new MockTimeProvider(); - _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object, _timeProvider.TimestampFrequency); + _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object, _timeProvider); } [Fact] @@ -29,9 +29,9 @@ public void DoesNotTimeOutWhenDebuggerIsAttached() _timeoutControl.Debugger = mockDebugger.Object; var now = _timeProvider.GetTimestamp(); - _timeoutControl.Initialize(now); + _timeoutControl.Initialize(); _timeoutControl.SetTimeout(TimeSpan.FromTicks(1), TimeoutReason.RequestHeaders); - _timeoutControl.Tick(now + 2 + Heartbeat.Interval.ToTicks(_timeProvider.TimestampFrequency)); + _timeoutControl.Tick(_timeProvider.GetTimestamp(now + 2, Heartbeat.Interval)); _mockTimeoutHandler.Verify(h => h.OnTimeout(It.IsAny()), Times.Never); } @@ -64,7 +64,7 @@ public void RequestBodyMinimumDataRateNotEnforcedDuringGracePeriod() // Initialize timestamp var now = _timeProvider.GetTimestamp(); - _timeoutControl.Initialize(now); + _timeoutControl.Initialize(); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); @@ -96,7 +96,7 @@ public void RequestBodyDataRateIsAveragedOverTimeSpentReadingRequestBody() // Initialize timestamp var now = _timeProvider.GetTimestamp(); - _timeoutControl.Initialize(now); + _timeoutControl.Initialize(); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); @@ -155,7 +155,7 @@ public void RequestBodyDataRateNotComputedOnPausedTime() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetTimestamp()); + _timeoutControl.Initialize(); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); @@ -211,7 +211,7 @@ public void ReadTimingNotPausedWhenResumeCalledBeforeNextTick() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetTimestamp()); + _timeoutControl.Initialize(); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); @@ -257,7 +257,7 @@ public void ReadTimingNotEnforcedWhenTimeoutIsSet() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetTimestamp()); + _timeoutControl.Initialize(); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); @@ -289,7 +289,7 @@ public void ReadTimingNotEnforcedWhenLowConnectionInputFlowControlAvailability() // Initialize timestamp var now = _timeProvider.GetTimestamp(); - _timeoutControl.Initialize(now); + _timeoutControl.Initialize(); _timeoutControl.InitializeHttp2(flowControl); _timeoutControl.StartRequestBody(minRate); @@ -335,7 +335,7 @@ public void ReadTimingOnlyCountsUpToOneHeartbeatIntervalPerTick() // Initialize timestamp var now = _timeProvider.GetTimestamp(); - _timeoutControl.Initialize(now); + _timeoutControl.Initialize(); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); @@ -373,7 +373,7 @@ public void WriteTimingAbortsConnectionWhenWriteDoesNotCompleteWithMinimumDataRa var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetTimestamp()); + _timeoutControl.Initialize(); // Should complete within 4 seconds, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 400); @@ -391,7 +391,7 @@ public void WriteTimingAbortsConnectionWhenSmallWriteDoesNotCompleteWithinGraceP var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(5)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetTimestamp()); + _timeoutControl.Initialize(); // Should complete within 1 second, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 100); @@ -415,7 +415,7 @@ public void WriteTimingTimeoutPushedOnConcurrentWrite() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetTimestamp()); + _timeoutControl.Initialize(); // Should complete within 5 seconds, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 500); @@ -448,7 +448,7 @@ public void WriteTimingAbortsConnectionWhenRepeatedSmallWritesDoNotCompleteWithM var writeSize = 100; // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetTimestamp()); + _timeoutControl.Initialize(); // 5 consecutive 100 byte writes. for (var i = 0; i < numWrites - 1; i++) @@ -479,7 +479,7 @@ public void WriteTimingOnlyCountsUpToOneHeartbeatIntervalPerTick() var minRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(2)); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetTimestamp()); + _timeoutControl.Initialize(); // Should complete within 4 seconds, but the timeout is adjusted by adding Heartbeat.Interval _timeoutControl.BytesWrittenToBuffer(minRate, 400); @@ -504,7 +504,7 @@ private void TickBodyWithMinimumDataRate(int bytesPerSecond) var minRate = new MinDataRate(bytesPerSecond, gracePeriod); // Initialize timestamp - _timeoutControl.Initialize(_timeProvider.GetTimestamp()); + _timeoutControl.Initialize(); _timeoutControl.StartRequestBody(minRate); _timeoutControl.StartTimingRead(); @@ -519,15 +519,15 @@ private void TickBodyWithMinimumDataRate(int bytesPerSecond) private void AdvanceClock(TimeSpan timeSpan) { - var endTime = _timeProvider.GetTimestamp() + timeSpan.ToTicks(_timeProvider.TimestampFrequency); + var endTime = _timeProvider.GetTimestamp(timeSpan); - while (_timeProvider.GetTimestamp() + Heartbeat.Interval.ToTicks(_timeProvider.TimestampFrequency) < endTime) + while (_timeProvider.GetTimestamp(Heartbeat.Interval) < endTime) { _timeProvider.Advance(Heartbeat.Interval); _timeoutControl.Tick(_timeProvider.GetTimestamp()); } - _timeProvider.SetTimestamp(endTime); + _timeProvider.AdvanceTo(endTime); _timeoutControl.Tick(_timeProvider.GetTimestamp()); } } diff --git a/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj b/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj index b7ba36177491..9840dbfaac1b 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj +++ b/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs b/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs index 7e0cb71f5bff..f6c14230fd09 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/QuicConnectionContextTests.cs @@ -505,8 +505,8 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() // Arrange using var httpEventSource = new HttpEventSourceListener(LoggerFactory); - var testTimeProvider = new TestTimeProvider(123456789L); - await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, testTimeProvider); + var timeProvider = new MockTimeProvider(); + await using var connectionListener = await QuicTestHelpers.CreateConnectionListenerFactory(LoggerFactory, timeProvider); var options = QuicTestHelpers.CreateClientConnectionOptions(connectionListener.EndPoint); await using var clientConnection = await QuicConnection.ConnectAsync(options); @@ -525,9 +525,9 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() Assert.Equal(1, quicConnectionContext.StreamPool.Count); QuicStreamContext pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); - Assert.Equal(testTimeProvider.GetTimestamp() + QuicConnectionContext.StreamPoolExpirySeconds * testTimeProvider.TimestampFrequency, pooledStream.PoolExpirationTimestamp); + Assert.Equal(timeProvider.GetTimestamp() + QuicConnectionContext.StreamPoolExpirySeconds * timeProvider.TimestampFrequency, pooledStream.PoolExpirationTimestamp); - testTimeProvider.Advance((long)(0.1 * testTimeProvider.TimestampFrequency)); + timeProvider.Advance(TimeSpan.FromSeconds(0.1)); testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); @@ -537,16 +537,16 @@ public async Task StreamPool_Heartbeat_ExpiredStreamRemoved() Assert.Equal(1, quicConnectionContext.StreamPool.Count); pooledStream = quicConnectionContext.StreamPool._array[0]; Assert.Same(stream1, pooledStream); - Assert.Equal(testTimeProvider.GetTimestamp() + QuicConnectionContext.StreamPoolExpirySeconds * testTimeProvider.TimestampFrequency, pooledStream.PoolExpirationTimestamp); + Assert.Equal(timeProvider.GetTimestamp() + QuicConnectionContext.StreamPoolExpirySeconds * timeProvider.TimestampFrequency, pooledStream.PoolExpirationTimestamp); Assert.Same(stream1, stream2); - testTimeProvider.Advance(QuicConnectionContext.StreamPoolExpirySeconds * testTimeProvider.TimestampFrequency); + timeProvider.Advance(TimeSpan.FromSeconds(QuicConnectionContext.StreamPoolExpirySeconds)); testHeartbeatFeature.RaiseHeartbeat(); // Not removed. Assert.Equal(1, quicConnectionContext.StreamPool.Count); - testTimeProvider.Advance(1); + timeProvider.Advance(TimeSpan.FromTicks(1)); testHeartbeatFeature.RaiseHeartbeat(); // Removed. Assert.Equal(0, quicConnectionContext.StreamPool.Count); @@ -716,28 +716,6 @@ private record RequestState( public int ActiveConcurrentConnections { get; set; } }; - private class TestTimeProvider : TimeProvider - { - private long _now; - - public TestTimeProvider(long now) - { - _now = now; - } - - public override DateTimeOffset GetUtcNow() - => throw new NotImplementedException(); - - public override long TimestampFrequency => Stopwatch.Frequency; - - public override long GetTimestamp() => _now; - - public void Advance(long ticks) => _now += ticks; - - public override ITimer CreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) - => throw new NotImplementedException(); - } - private class TestHeartbeatFeature : IConnectionHeartbeatFeature { private readonly List<(Action Action, object State)> _actions = new List<(Action, object)>(); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs index 20f997bb3430..7ba27b5848c6 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs @@ -39,7 +39,7 @@ public void Setup() serviceContext: serviceContext, connectionContext: null, transport: pair.Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), + timeoutControl: new TimeoutControl(timeoutHandler: null, TimeProvider.System), memoryPool: memoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs index 6114620b1343..02d4993f9107 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs @@ -35,7 +35,7 @@ public void Setup() serviceContext: serviceContext, connectionContext: null, transport: pair.Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), + timeoutControl: new TimeoutControl(timeoutHandler: null, TimeProvider.System), memoryPool: memoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs index 90bfc3371eeb..0472ad83b751 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs @@ -71,13 +71,13 @@ private TestHttp1Connection MakeHttp1Connection() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), httpParser: new HttpParser(), - dateHeaderValueManager: new DateHeaderValueManager(new MockTimeProvider())); + dateHeaderValueManager: new DateHeaderValueManager(TimeProvider.System)); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, connectionContext: null, transport: pair.Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), + timeoutControl: new TimeoutControl(timeoutHandler: null, TimeProvider.System), memoryPool: _memoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs index 6339e780c724..81ea77dc0c17 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs @@ -100,13 +100,13 @@ private TestHttp1Connection MakeHttp1Connection() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), httpParser: new HttpParser(), - dateHeaderValueManager: new DateHeaderValueManager(new MockTimeProvider())); + dateHeaderValueManager: new DateHeaderValueManager(TimeProvider.System)); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, connectionContext: null, transport: pair.Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), + timeoutControl: new TimeoutControl(timeoutHandler: null, TimeProvider.System), memoryPool: _memoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs index 5c731ee0b4f1..2efbd813a169 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs @@ -105,13 +105,13 @@ private TestHttp1Connection MakeHttp1Connection() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), httpParser: new HttpParser(), - dateHeaderValueManager: new DateHeaderValueManager(new MockTimeProvider())); + dateHeaderValueManager: new DateHeaderValueManager(TimeProvider.System)); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, connectionContext: null, transport: pair.Transport, - timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency), + timeoutControl: new TimeoutControl(timeoutHandler: null, TimeProvider.System), memoryPool: _memoryPool, connectionFeatures: new FeatureCollection()); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs index 69a6fe425773..9d2395186e80 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs @@ -68,8 +68,8 @@ public virtual void GlobalSetup() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), - dateHeaderValueManager: new DateHeaderValueManager(new MockTimeProvider()), - timeProvider: new MockTimeProvider()); + dateHeaderValueManager: new DateHeaderValueManager(TimeProvider.System), + timeProvider: TimeProvider.System); serviceContext.DateHeaderValueManager.OnHeartbeat(); var connectionContext = TestContextFactory.CreateHttpConnectionContext( diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs index f078b856a65f..196773c1f19c 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTimeoutControl.cs @@ -65,7 +65,7 @@ public void StopTimingWrite() { } - public void Tick(long now) + public void Tick(long timestamp) { } } diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs index fb77308d59da..b7fb6b556bd3 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs @@ -31,7 +31,7 @@ public void Setup() var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), httpParser: new HttpParser(), - dateHeaderValueManager: new DateHeaderValueManager(new MockTimeProvider())); + dateHeaderValueManager: new DateHeaderValueManager(TimeProvider.System)); var connectionContext = TestContextFactory.CreateHttpConnectionContext( serviceContext: serviceContext, @@ -39,7 +39,7 @@ public void Setup() transport: pair.Transport, memoryPool: _memoryPool, connectionFeatures: new FeatureCollection(), - timeoutControl: new TimeoutControl(timeoutHandler: null, new MockTimeProvider().TimestampFrequency)); + timeoutControl: new TimeoutControl(timeoutHandler: null, TimeProvider.System)); var http1Connection = new Http1Connection(connectionContext); diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs index 6f2f6c9b2817..feab3f68b0ea 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs @@ -22,7 +22,7 @@ public class ResponseHeaderCollectionBenchmark private const int InnerLoopCount = 128; private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel"); - private static readonly DateHeaderValueManager _dateHeaderValueManager = new DateHeaderValueManager(new MockTimeProvider()); + private static readonly DateHeaderValueManager _dateHeaderValueManager = new DateHeaderValueManager(TimeProvider.System); private HttpResponseHeaders _responseHeadersDirect; private HttpResponse _response; diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeadersWritingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeadersWritingBenchmark.cs index f3d8177f4d5b..2faf4a3189ad 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeadersWritingBenchmark.cs +++ b/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeadersWritingBenchmark.cs @@ -179,7 +179,7 @@ public void GlobalSetup() { _responseHeaders = new HttpResponseHeaders(); _responseHeadersDict = _responseHeaders; - _dateHeaderValueManager = new DateHeaderValueManager(new MockTimeProvider()); + _dateHeaderValueManager = new DateHeaderValueManager(TimeProvider.System); _dateHeaderValueManager.OnHeartbeat(); _writer = new Writer(); } diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index fd95802b20a9..f5bcbeec56e8 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -36,7 +36,7 @@ internal class Http3InMemory public Http3InMemory(ServiceContext serviceContext, MockTimeProvider mockTimeProvider, ITimeoutHandler timeoutHandler, ILoggerFactory loggerFactory) { _serviceContext = serviceContext; - _timeoutControl = new TimeoutControl(new TimeoutControlConnectionInvoker(this, timeoutHandler), mockTimeProvider.TimestampFrequency); + _timeoutControl = new TimeoutControl(new TimeoutControlConnectionInvoker(this, timeoutHandler), mockTimeProvider); _timeoutControl.Debugger = new TestDebugger(); _mockTimeProvider = mockTimeProvider; @@ -200,27 +200,22 @@ public void AdvanceTime(TimeSpan timeSpan) Logger.LogDebug("Advancing timeProvider {timeSpan}.", timeSpan); var timeProvider = _mockTimeProvider; - var endTime = timeProvider.GetTimestamp() + timeSpan.ToTicks(timeProvider.TimestampFrequency); - var interval = Heartbeat.Interval.ToTicks(timeProvider.TimestampFrequency); + var endTime = timeProvider.GetTimestamp(timeSpan); - while (timeProvider.GetTimestamp() + interval < endTime) + while (timeProvider.GetTimestamp(Heartbeat.Interval) < endTime) { - TriggerTick(timeProvider.GetTimestamp() + interval); + timeProvider.Advance(Heartbeat.Interval); + _timeoutControl.Tick(timeProvider.GetTimestamp()); } - TriggerTick(endTime); + timeProvider.AdvanceTo(endTime); + _timeoutControl.Tick(timeProvider.GetTimestamp()); } - public void TriggerTick(DateTimeOffset now) + public void TriggerTick(TimeSpan timeSpan = default) { - _mockTimeProvider.SetUtcNow(now); - Connection?.Tick(_mockTimeProvider.GetTimestamp()); - } - - public void TriggerTick(long timestamp) - { - _mockTimeProvider.SetTimestamp(timestamp); - _timeoutControl.Tick(timestamp); + _mockTimeProvider.Advance(timeSpan); + var timestamp = _mockTimeProvider.GetTimestamp(); Connection?.Tick(timestamp); } diff --git a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs index bc4eed17b1b1..d9035441c2fe 100644 --- a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs +++ b/src/Servers/Kestrel/shared/test/MockTimeProvider.cs @@ -12,39 +12,25 @@ public class MockTimeProvider : TimeProvider // 100_000_000; // 8 zeros, in between // 1_000_000_000; // Linux Stopwatch.Frequency; - private long _ticks; + private long _utcTicks; + private long _timestamp; public MockTimeProvider() { // Use a random DateTimeOffset to ensure tests that incorrectly use the current DateTimeOffset fail always instead of only rarely. - // Pick a date between the min DateTimeOffset and a year before the max DateTimeOffset so there's room to advance the clock. - _ticks = NextLong(0, - (DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch + TimeSpan.FromDays(365 * 10)).ToTicks(_timestampFrequency)); - } - - public MockTimeProvider(DateTimeOffset now) - { - _ticks = (now - DateTimeOffset.UnixEpoch).ToTicks(_timestampFrequency); + var tenYears = TimeSpan.FromDays(365 * 10).Ticks; + _utcTicks = DateTimeOffset.UtcNow.Ticks + Random.Shared.NextInt64(-tenYears, tenYears); + // Timestamps often measure system uptime. + _timestamp = Random.Shared.NextInt64(0, System.GetTimestamp() * 100); } public override DateTimeOffset GetUtcNow() { UtcNowCalled++; - var seconds = Interlocked.Read(ref _ticks) / (double)_timestampFrequency; - return DateTimeOffset.UnixEpoch + TimeSpan.FromSeconds(seconds); - } - - public void SetUtcNow(DateTimeOffset now) - { - Interlocked.Exchange(ref _ticks, (now - DateTimeOffset.UnixEpoch).ToTicks(_timestampFrequency)); + return new DateTimeOffset(Interlocked.Read(ref _utcTicks), TimeSpan.Zero); } - public override long GetTimestamp() => Interlocked.Read(ref _ticks); - - public void SetTimestamp(long now) - { - Interlocked.Exchange(ref _ticks, now); - } + public override long GetTimestamp() => Interlocked.Read(ref _timestamp); public override long TimestampFrequency => _timestampFrequency; @@ -52,12 +38,27 @@ public void SetTimestamp(long now) public void Advance(TimeSpan timeSpan) { - Interlocked.Add(ref _ticks, timeSpan.ToTicks(TimestampFrequency)); + Interlocked.Add(ref _utcTicks, timeSpan.Ticks); + Interlocked.Add(ref _timestamp, timeSpan.ToTicks(_timestampFrequency)); + } + + public void AdvanceTo(DateTimeOffset newUtcNow) + { + var nowTicks = newUtcNow.UtcTicks; + var priorTicks = Interlocked.Exchange(ref _utcTicks, nowTicks); + // Advance Timestamp by the same amount. + // Known timestamp frequencies are the same or larger than TicksPerSecond. + var timestampOffset = (nowTicks - priorTicks) * (_timestampFrequency / TimeSpan.TicksPerSecond); + Interlocked.Add(ref _timestamp, timestampOffset); } - private long NextLong(long minValue, long maxValue) + public void AdvanceTo(long timestamp) { - return (long)(Random.Shared.NextDouble() * (maxValue - minValue) + minValue); + var priorTimestamp = Interlocked.Exchange(ref _timestamp, timestamp); + // Advance UtcNow by the same amount. + // Known timestamp frequencies are the same or larger than TicksPerSecond. + var utcOffset = (long)((timestamp - priorTimestamp) * ((double)TimeSpan.TicksPerSecond / _timestampFrequency)); + Interlocked.Add(ref _utcTicks, utcOffset); } public override ITimer CreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs index 467f1761d4ef..d06ad5df401f 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2KeepAliveTests.cs @@ -28,13 +28,11 @@ public async Task KeepAlivePingTimeout_InfiniteTimeSpan_NoGoAway() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); - // Heartbeat - TriggerTick(now); + TriggerTick(); // Heartbeat that exceeds interval - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 2)); + TriggerTick(TimeSpan.FromSeconds(1.1 * 2)); await ExpectAsync(Http2FrameType.PING, withLength: 8, @@ -42,9 +40,9 @@ await ExpectAsync(Http2FrameType.PING, withStreamId: 0).DefaultTimeout(); // Heartbeat that exceeds timeout - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 3)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 4)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 20)); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1 * 20)); Assert.Equal(KeepAliveState.PingSent, _connection._keepAlive._state); @@ -58,13 +56,11 @@ public async Task IntervalExceeded_WithoutActivity_PingSent() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); - // Heartbeat - TriggerTick(now); + TriggerTick(); // Heartbeat that exceeds interval - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 2)); + TriggerTick(TimeSpan.FromSeconds(1.1 * 2)); await ExpectAsync(Http2FrameType.PING, withLength: 8, @@ -81,10 +77,8 @@ public async Task IntervalExceeded_WithActivity_NoPingSent() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); - // Heartbeat - TriggerTick(now); + TriggerTick(); await SendPingAsync(Http2PingFrameFlags.NONE).DefaultTimeout(); await ExpectAsync(Http2FrameType.PING, @@ -93,7 +87,7 @@ await ExpectAsync(Http2FrameType.PING, withStreamId: 0).DefaultTimeout(); // Heartbeat that exceeds interval - TriggerTick(now + TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false).DefaultTimeout(); } @@ -105,13 +99,11 @@ public async Task IntervalNotExceeded_NoPingSent() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = new DateTimeOffset(1, TimeSpan.Zero); - // Heartbeats - TriggerTick(now); - TriggerTick(now + TimeSpan.FromSeconds(1.1)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 2)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 3)); + TriggerTick(); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false).DefaultTimeout(); } @@ -123,19 +115,17 @@ public async Task IntervalExceeded_MultipleTimes_PingsNotSentWhileAwaitingOnAck( await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); - - TriggerTick(now); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 2)); + TriggerTick(); + TriggerTick(TimeSpan.FromSeconds(1.1 * 2)); await ExpectAsync(Http2FrameType.PING, withLength: 8, withFlags: (byte)Http2PingFrameFlags.NONE, withStreamId: 0).DefaultTimeout(); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 3)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 4)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 5)); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false).DefaultTimeout(); } @@ -147,11 +137,9 @@ public async Task IntervalExceeded_MultipleTimes_PingSentAfterAck() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); - // Heartbeats - TriggerTick(now); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 2)); + TriggerTick(); + TriggerTick(TimeSpan.FromSeconds(1.1 * 2)); await ExpectAsync(Http2FrameType.PING, withLength: 8, @@ -159,8 +147,8 @@ await ExpectAsync(Http2FrameType.PING, withStreamId: 0).DefaultTimeout(); await SendPingAsync(Http2PingFrameFlags.ACK).DefaultTimeout(); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 3)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 4)); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); await ExpectAsync(Http2FrameType.PING, withLength: 8, @@ -168,8 +156,8 @@ await ExpectAsync(Http2FrameType.PING, withStreamId: 0).DefaultTimeout(); await SendPingAsync(Http2PingFrameFlags.ACK).DefaultTimeout(); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 5)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 6)); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); await ExpectAsync(Http2FrameType.PING, withLength: 8, @@ -187,13 +175,11 @@ public async Task TimeoutExceeded_NoAck_GoAway() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); - // Heartbeat - TriggerTick(now); + TriggerTick(); // Heartbeat that exceeds interval - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 2)); + TriggerTick(TimeSpan.FromSeconds(1.1 * 2)); await ExpectAsync(Http2FrameType.PING, withLength: 8, @@ -201,10 +187,10 @@ await ExpectAsync(Http2FrameType.PING, withStreamId: 0).DefaultTimeout(); // Heartbeat that exceeds timeout - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 3)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 4)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 5)); - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 6)); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); Assert.Equal(KeepAliveState.Timeout, _connection._keepAlive._state); @@ -219,13 +205,11 @@ public async Task TimeoutExceeded_NonPingActivity_NoGoAway() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); - // Heartbeat - TriggerTick(now); + TriggerTick(); // Heartbeat that exceeds interval - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 2)); + TriggerTick(TimeSpan.FromSeconds(1.1 * 2)); Assert.Equal(KeepAliveState.PingSent, _connection._keepAlive._state); await ExpectAsync(Http2FrameType.PING, @@ -251,10 +235,8 @@ public async Task IntervalExceeded_StreamStarted_NoPingSent() await InitializeConnectionAsync(_noopApplication).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); - // Heartbeat - TriggerTick(now); + TriggerTick(); await StartStreamAsync(1, _browserRequestHeaders, endStream: true).DefaultTimeout(); @@ -264,7 +246,7 @@ await ExpectAsync(Http2FrameType.HEADERS, withStreamId: 1).DefaultTimeout(); // Heartbeat that exceeds interval - TriggerTick(now + TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false).DefaultTimeout(); } @@ -286,10 +268,8 @@ await InitializeConnectionAsync(async c => await c.Request.Body.FlushAsync(); }, expectedWindowUpdate: false).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); - // Heartbeat - TriggerTick(now); + TriggerTick(); await StartStreamAsync(1, _browserRequestHeaders, endStream: false).DefaultTimeout(); @@ -300,7 +280,7 @@ await InitializeConnectionAsync(async c => await SendDataAsync(1, new byte[16383], false).DefaultTimeout(); // Heartbeat that exceeds interval - TriggerTick(now + TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); // Send ping that will update the keep alive on the server await SendPingAsync(Http2PingFrameFlags.NONE).DefaultTimeout(); @@ -310,7 +290,7 @@ await ExpectAsync(Http2FrameType.PING, withStreamId: 0).DefaultTimeout(); // Heartbeat that exceeds interval - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 2)); + TriggerTick(TimeSpan.FromSeconds(1.1)); // Continue request delegate on server tcs.SetResult(); @@ -341,10 +321,8 @@ await InitializeConnectionAsync(async c => await c.Request.Body.FlushAsync(); }, expectedWindowUpdate: false).DefaultTimeout(); - DateTimeOffset now = _serviceContext.MockTimeProvider.GetUtcNow(); - // Heartbeat - TriggerTick(now); + TriggerTick(); await StartStreamAsync(1, _browserRequestHeaders, endStream: false).DefaultTimeout(); @@ -355,10 +333,10 @@ await InitializeConnectionAsync(async c => await SendDataAsync(1, new byte[16383], false).DefaultTimeout(); // Heartbeat that exceeds interval - TriggerTick(now + TimeSpan.FromSeconds(1.1)); + TriggerTick(TimeSpan.FromSeconds(1.1)); // Heartbeat that triggers keep alive ping - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 2)); + TriggerTick(TimeSpan.FromSeconds(1.1)); await ExpectAsync(Http2FrameType.PING, withLength: 8, @@ -369,7 +347,7 @@ await ExpectAsync(Http2FrameType.PING, await SendPingAsync(Http2PingFrameFlags.ACK).DefaultTimeout(); // Heartbeat that exceeds interval - TriggerTick(now + TimeSpan.FromSeconds(1.1 * 3)); + TriggerTick(TimeSpan.FromSeconds(1.1)); // Continue request delegate on server tcs.SetResult(); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index c4374b51ed59..a2f28d06c831 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Buffers; using System.Buffers.Binary; using System.Collections.Concurrent; @@ -118,6 +119,7 @@ protected static IEnumerable> ReadRateRequestHeader internal readonly DynamicHPackEncoder _hpackEncoder; private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize]; + private readonly MockTimeProvider _timeProvider = new(); internal readonly TimeoutControl _timeoutControl; protected readonly Mock _mockConnectionContext = new Mock(); internal readonly Mock _mockTimeoutHandler = new Mock(); @@ -162,7 +164,7 @@ public Http2TestBase() _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize); _hpackEncoder = new DynamicHPackEncoder(); - _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object, new MockTimeProvider().TimestampFrequency); + _timeoutControl = new TimeoutControl(_mockTimeoutHandler.Object, _timeProvider); _mockTimeoutControl = new Mock(_timeoutControl) { CallBase = true }; _timeoutControl.Debugger = Mock.Of(); @@ -387,12 +389,11 @@ public override void Initialize(TestContext context, MethodInfo methodInfo, obje { base.Initialize(context, methodInfo, testMethodArguments, testOutputHelper); - var timeProvider = new MockTimeProvider(); _serviceContext = new TestServiceContext(LoggerFactory) { Scheduler = PipeScheduler.Inline, - MockTimeProvider = timeProvider, - TimeProvider = timeProvider, + MockTimeProvider = _timeProvider, + TimeProvider = _timeProvider, }; TestSink.MessageLogged += context => @@ -475,7 +476,7 @@ protected void CreateConnection() _mockTimeoutHandler.Setup(h => h.OnTimeout(It.IsAny())) .Callback(r => httpConnection.OnTimeout(r)); - _timeoutControl.Initialize(_serviceContext.TimeProvider.GetTimestamp()); + _timeoutControl.Initialize(); } private class LifetimeHandlerInterceptor : IHttp2StreamLifetimeHandler @@ -1341,18 +1342,15 @@ protected void VerifyDecodedRequestHeaders(IEnumerable memoryPool, PipeScheduler writerScheduler) => new PipeOptions diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs index 6300576c7c9c..b1ff24b43bf8 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs @@ -290,8 +290,6 @@ await Http3Api.WaitForConnectionErrorAsync( [Fact] public async Task ControlStream_ClientToServer_Completes_ConnectionError() { - var now = _serviceContext.MockTimeProvider.GetUtcNow(); - await Http3Api.InitializeConnectionAsync(_noopApplication); var controlStream = await Http3Api.CreateControlStream(id: 0); @@ -302,8 +300,8 @@ public async Task ControlStream_ClientToServer_Completes_ConnectionError() // Wait for control stream to finish processing and exit. await controlStream.OnStreamCompletedTask.DefaultTimeout(); - Http3Api.TriggerTick(now); - Http3Api.TriggerTick(now + TimeSpan.FromSeconds(1)); + Http3Api.TriggerTick(); + Http3Api.TriggerTick(TimeSpan.FromSeconds(1)); await Http3Api.WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, @@ -339,16 +337,14 @@ public async Task GOAWAY_TriggersLifetimeNotification_ConnectionClosedRequested( [Fact] public async Task ControlStream_ServerToClient_ErrorInitializing_ConnectionError() { - var now = _serviceContext.MockTimeProvider.GetUtcNow(); - await Http3Api.InitializeConnectionAsync(_noopApplication); var controlStream = await Http3Api.GetInboundControlStream(); controlStream.StreamContext.Close(); - Http3Api.TriggerTick(now); - Http3Api.TriggerTick(now + TimeSpan.FromSeconds(1)); + Http3Api.TriggerTick(); + Http3Api.TriggerTick(TimeSpan.FromSeconds(1)); await Http3Api.WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs index 6a38f10fc957..8120b171059f 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TimeoutTests.cs @@ -124,7 +124,7 @@ await Http3Api.InitializeConnectionAsync(_ => public async Task HEADERS_IncompleteFrameReceivedWithinRequestHeadersTimeout_StreamError() { var timeProvider = _serviceContext.MockTimeProvider; - var now = timeProvider.GetTimestamp(); + var timestamp = timeProvider.GetTimestamp(); var limits = _serviceContext.ServerOptions.Limits; var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, null).DefaultTimeout(); @@ -138,12 +138,12 @@ public async Task HEADERS_IncompleteFrameReceivedWithinRequestHeadersTimeout_Str var serverRequestStream = Http3Api.Connection._streams[requestStream.StreamId]; - Http3Api.TriggerTick(now); - Http3Api.AdvanceTime(limits.RequestHeadersTimeout); + Http3Api.TriggerTick(); + Http3Api.TriggerTick(limits.RequestHeadersTimeout); - Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverRequestStream.StreamTimeoutTimestamp); + Assert.Equal(timeProvider.GetTimestamp(timestamp, limits.RequestHeadersTimeout), serverRequestStream.StreamTimeoutTimestamp); - Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); + Http3Api.TriggerTick(TimeSpan.FromTicks(1)); await requestStream.WaitForStreamErrorAsync( Http3ErrorCode.RequestRejected, @@ -158,7 +158,7 @@ public async Task HEADERS_HeaderFrameReceivedWithinRequestHeadersTimeout_Success { Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = pendingStreamsEnabled; - var now = _serviceContext.MockTimeProvider.GetTimestamp(); + var timestamp = _serviceContext.MockTimeProvider.GetTimestamp(); var limits = _serviceContext.ServerOptions.Limits; var headers = new[] { @@ -188,10 +188,10 @@ public async Task HEADERS_HeaderFrameReceivedWithinRequestHeadersTimeout_Success serverRequestStream = Http3Api.Connection._streams[requestStream.StreamId]; } - Http3Api.TriggerTick(now); + Http3Api.TriggerTick(); Http3Api.AdvanceTime(limits.RequestHeadersTimeout); - Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(_serviceContext.TimeProvider.TimestampFrequency), serverRequestStream.StreamTimeoutTimestamp); + Assert.Equal(_serviceContext.TimeProvider.GetTimestamp(timestamp, limits.RequestHeadersTimeout), serverRequestStream.StreamTimeoutTimestamp); await requestStream.SendHeadersAsync(headers).DefaultTimeout(); @@ -212,7 +212,7 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = true; var timeProvider = _serviceContext.MockTimeProvider; - var now = timeProvider.GetTimestamp(); + var timestamp = timeProvider.GetTimestamp(); var limits = _serviceContext.ServerOptions.Limits; await Http3Api.InitializeConnectionAsync(_noopApplication).DefaultTimeout(); @@ -225,10 +225,10 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str await outboundControlStream.OnUnidentifiedStreamCreatedTask.DefaultTimeout(); var serverInboundControlStream = Http3Api.Connection._unidentifiedStreams[outboundControlStream.StreamId]; - Http3Api.TriggerTick(now); + Http3Api.TriggerTick(); Http3Api.AdvanceTime(limits.RequestHeadersTimeout); - Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTimestamp); + Assert.Equal(timeProvider.GetTimestamp(timestamp, limits.RequestHeadersTimeout), serverInboundControlStream.StreamTimeoutTimestamp); Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); } @@ -239,8 +239,8 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = false; var timeProvider = _serviceContext.MockTimeProvider; - var now = timeProvider.GetTimestamp(); - Http3Api._timeoutControl.Initialize(now); + var timestamp = timeProvider.GetTimestamp(); + Http3Api._timeoutControl.Initialize(); var limits = _serviceContext.ServerOptions.Limits; var headers = new[] { @@ -261,12 +261,12 @@ public async Task ControlStream_HeaderNotReceivedWithinRequestHeadersTimeout_Str var serverInboundControlStream = Http3Api.Connection._streams[outboundControlStream.StreamId]; - Http3Api.TriggerTick(now); - Http3Api.AdvanceTime(limits.RequestHeadersTimeout); + Http3Api.TriggerTick(); + Http3Api.TriggerTick(limits.RequestHeadersTimeout); - Assert.Equal(now + limits.RequestHeadersTimeout.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTimestamp); + Assert.Equal(timeProvider.GetTimestamp(timestamp, limits.RequestHeadersTimeout), serverInboundControlStream.StreamTimeoutTimestamp); - Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); + Http3Api.TriggerTick(TimeSpan.FromTicks(1)); await outboundControlStream.WaitForStreamErrorAsync( Http3ErrorCode.StreamCreationError, @@ -277,7 +277,6 @@ await outboundControlStream.WaitForStreamErrorAsync( [Fact] public async Task ControlStream_HeaderReceivedWithinRequestHeadersTimeout_StreamError() { - var now = _serviceContext.MockTimeProvider.GetUtcNow(); var limits = _serviceContext.ServerOptions.Limits; await Http3Api.InitializeConnectionAsync(_noopApplication).DefaultTimeout(); @@ -285,14 +284,14 @@ public async Task ControlStream_HeaderReceivedWithinRequestHeadersTimeout_Stream var controlStream = await Http3Api.GetInboundControlStream().DefaultTimeout(); await controlStream.ExpectSettingsAsync().DefaultTimeout(); - Http3Api.TriggerTick(now); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); + Http3Api.TriggerTick(); + Http3Api.TriggerTick(limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); var outboundControlStream = await Http3Api.CreateControlStream(id: 0); await outboundControlStream.OnStreamCreatedTask.DefaultTimeout(); - Http3Api.TriggerTick(now + limits.RequestHeadersTimeout + TimeSpan.FromTicks(1)); + Http3Api.TriggerTick(); } [Theory] @@ -303,7 +302,6 @@ public async Task ControlStream_RequestHeadersTimeoutMaxValue_ExpirationIsMaxVal Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = pendingStreamEnabled; var timeProvider = _serviceContext.MockTimeProvider; - var now = timeProvider.GetTimestamp(); var limits = _serviceContext.ServerOptions.Limits; limits.RequestHeadersTimeout = TimeSpan.MaxValue; @@ -326,9 +324,9 @@ public async Task ControlStream_RequestHeadersTimeoutMaxValue_ExpirationIsMaxVal serverInboundControlStream = Http3Api.Connection._streams[outboundControlStream.StreamId]; } - Http3Api.TriggerTick(now); + Http3Api.TriggerTick(); - Assert.Equal(TimeSpan.MaxValue.ToTicks(timeProvider.TimestampFrequency), serverInboundControlStream.StreamTimeoutTimestamp); + Assert.Equal(TimeSpan.MaxValue.ToTicks(timeProvider), serverInboundControlStream.StreamTimeoutTimestamp); } [Fact] @@ -340,7 +338,7 @@ public async Task DATA_Received_TooSlowlyOnSmallRead_AbortsConnectionAfterGraceP // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); + Http3Api._timeoutControl.Initialize(); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -376,9 +374,7 @@ await Http3Api.WaitForConnectionErrorAsync( [Fact] public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() { - var now = _serviceContext.MockTimeProvider.GetTimestamp(); var limits = _serviceContext.ServerOptions.Limits; - var mockTimeProvider = _serviceContext.MockTimeProvider; // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinResponseDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); @@ -398,13 +394,13 @@ public async Task ResponseDrain_SlowerThanMinimumDataRate_AbortsConnection() await requestStream.OnDisposingTask.DefaultTimeout(); - Http3Api.TriggerTick(now); + Http3Api.TriggerTick(); Assert.Null(requestStream.StreamContext._error); - Http3Api.AdvanceTime(TimeSpan.FromTicks(1)); + Http3Api.TriggerTick(TimeSpan.FromTicks(1)); Assert.Null(requestStream.StreamContext._error); - Http3Api.AdvanceTime(limits.MinResponseDataRate.GracePeriod); + Http3Api.TriggerTick(limits.MinResponseDataRate.GracePeriod); requestStream.StartStreamDisposeTcs.TrySetResult(); @@ -453,7 +449,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnSmallWrite_AbortsC // Disable response buffering so "socket" backpressure is observed immediately. limits.MaxResponseBufferSize = 0; - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); + Http3Api._timeoutControl.Initialize(); var app = new EchoAppWithNotification(); var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(app.RunApp, _browserRequestHeaders, endStream: false); @@ -495,7 +491,7 @@ public async Task DATA_Sent_TooSlowlyDueToSocketBackPressureOnLargeWrite_AbortsC // Disable response buffering so "socket" backpressure is observed immediately. limits.MaxResponseBufferSize = 0; - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); + Http3Api._timeoutControl.Initialize(); var app = new EchoAppWithNotification(); var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(app.RunApp, _browserRequestHeaders, endStream: false); @@ -535,7 +531,7 @@ public async Task DATA_Received_TooSlowlyOnLargeRead_AbortsConnectionAfterRateTi // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); + Http3Api._timeoutControl.Initialize(); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -581,7 +577,7 @@ public async Task DATA_Received_TooSlowlyOnMultipleStreams_AbortsConnectionAfter // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); + Http3Api._timeoutControl.Initialize(); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -636,7 +632,7 @@ public async Task DATA_Received_TooSlowlyOnSecondStream_AbortsConnectionAfterNon // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); + Http3Api._timeoutControl.Initialize(); await Http3Api.InitializeConnectionAsync(_readRateApplication); @@ -692,7 +688,7 @@ public async Task DATA_Received_SlowlyWhenRateLimitDisabledPerRequest_DoesNotAbo // Use non-default value to ensure the min request and response rates aren't mixed up. limits.MinRequestBodyDataRate = new MinDataRate(480, TimeSpan.FromSeconds(2.5)); - Http3Api._timeoutControl.Initialize(mockTimeProvider.GetTimestamp()); + Http3Api._timeoutControl.Initialize(); await Http3Api.InitializeConnectionAsync(context => { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs index c88063eed7bb..8a0904cba657 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseDrainingTests.cs @@ -1,16 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Net; -using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; -using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; From ce719d324787e74ed880270c8f6fcbd6c3f622cd Mon Sep 17 00:00:00 2001 From: Chris R Date: Mon, 15 May 2023 11:51:34 -0700 Subject: [PATCH 09/17] Extra using --- .../Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs index 01b98139065e..ba907abd7c5b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeoutControl.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Diagnostics; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl; From f6b2480ba0892d6fbc2a8af386d85010fe7bbd16 Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 16 May 2023 10:14:00 -0700 Subject: [PATCH 10/17] Share MockTimeProvider with Security --- .../Authentication/test/CertificateTests.cs | 3 ++- .../Authentication/test/CookieTests.cs | 2 +- ...soft.AspNetCore.Authentication.Test.csproj | 1 + .../test/SharedAuthenticationTests.cs | 3 ++- .../Authentication/test/TestTimeProvider.cs | 23 ------------------- ...Server.Kestrel.Transport.Quic.Tests.csproj | 2 +- ...Core.Server.Kestrel.Microbenchmarks.csproj | 2 +- .../Sockets.BindTests.csproj | 2 +- .../Sockets.FunctionalTests.csproj | 2 +- src/Shared/test/Certificates/Certificates.cs | 3 ++- .../test/MockTimeProvider.cs | 9 +++++++- 11 files changed, 20 insertions(+), 32 deletions(-) delete mode 100644 src/Security/Authentication/test/TestTimeProvider.cs rename src/{Servers/Kestrel/shared => Shared}/test/MockTimeProvider.cs (88%) diff --git a/src/Security/Authentication/test/CertificateTests.cs b/src/Security/Authentication/test/CertificateTests.cs index 32fb14180fb7..f7b7d09c8b21 100644 --- a/src/Security/Authentication/test/CertificateTests.cs +++ b/src/Security/Authentication/test/CertificateTests.cs @@ -688,7 +688,8 @@ public async Task VerifyValidationResultNeverCachedAfter30Min(bool cache) { const string Expected = "John Doe"; var validationCount = 0; - var timeProvider = new TestTimeProvider(); + // The test certs are generated based off UtcNow. + var timeProvider = new MockTimeProvider(TimeProvider.System.GetUtcNow()); using var host = await CreateHost( new CertificateAuthenticationOptions diff --git a/src/Security/Authentication/test/CookieTests.cs b/src/Security/Authentication/test/CookieTests.cs index 2a0e76a631e4..e9f89dd6e04d 100644 --- a/src/Security/Authentication/test/CookieTests.cs +++ b/src/Security/Authentication/test/CookieTests.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies; public class CookieTests : SharedAuthenticationTests { - private readonly TestTimeProvider _timeProvider = new(); + private readonly MockTimeProvider _timeProvider = new(); protected override string DefaultScheme => CookieAuthenticationDefaults.AuthenticationScheme; protected override Type HandlerType => typeof(CookieAuthenticationHandler); diff --git a/src/Security/Authentication/test/Microsoft.AspNetCore.Authentication.Test.csproj b/src/Security/Authentication/test/Microsoft.AspNetCore.Authentication.Test.csproj index 2d5f2deb7350..6cc293b1b3da 100644 --- a/src/Security/Authentication/test/Microsoft.AspNetCore.Authentication.Test.csproj +++ b/src/Security/Authentication/test/Microsoft.AspNetCore.Authentication.Test.csproj @@ -14,6 +14,7 @@ + PreserveNewest diff --git a/src/Security/Authentication/test/SharedAuthenticationTests.cs b/src/Security/Authentication/test/SharedAuthenticationTests.cs index 1dbdcdad4291..28ca03bc68df 100644 --- a/src/Security/Authentication/test/SharedAuthenticationTests.cs +++ b/src/Security/Authentication/test/SharedAuthenticationTests.cs @@ -4,13 +4,14 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authentication.Tests; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Authentication; public abstract class SharedAuthenticationTests where TOptions : AuthenticationSchemeOptions { - protected TestTimeProvider TimeProvider { get; } = new(); + protected MockTimeProvider TimeProvider { get; } = new(); protected abstract string DefaultScheme { get; } protected virtual string DisplayName { get; } diff --git a/src/Security/Authentication/test/TestTimeProvider.cs b/src/Security/Authentication/test/TestTimeProvider.cs deleted file mode 100644 index aa168482b93d..000000000000 --- a/src/Security/Authentication/test/TestTimeProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.Authentication; - -public class TestTimeProvider : TimeProvider -{ - private DateTimeOffset _current; - - public TestTimeProvider() : this(DateTimeOffset.UtcNow) { } - - public TestTimeProvider(DateTimeOffset current) - { - _current = current; - } - - public override DateTimeOffset GetUtcNow() => _current; - - public void Advance(TimeSpan timeSpan) - { - _current += timeSpan; - } -} diff --git a/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj b/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj index 9840dbfaac1b..f11876cf5a7e 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj +++ b/src/Servers/Kestrel/Transport.Quic/test/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj b/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj index 192cd25e51ca..acb6b02307c2 100644 --- a/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj +++ b/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj b/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj index e38f1828db2f..5d7992a557a7 100644 --- a/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj +++ b/src/Servers/Kestrel/test/Sockets.BindTests/Sockets.BindTests.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj b/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj index f21ed3ba48fe..2b8e9b81899c 100644 --- a/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/Sockets.FunctionalTests/Sockets.FunctionalTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Shared/test/Certificates/Certificates.cs b/src/Shared/test/Certificates/Certificates.cs index d0c2a0c043b6..7b4b5eeeba97 100644 --- a/src/Shared/test/Certificates/Certificates.cs +++ b/src/Shared/test/Certificates/Certificates.cs @@ -3,6 +3,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.Authentication.Certificate; @@ -13,7 +14,7 @@ public static class Certificates static Certificates() { - DateTimeOffset now = DateTimeOffset.UtcNow; + var now = TimeProvider.System.GetUtcNow(); SelfSignedPrimaryRoot = MakeCert( "CN=Valid Self Signed Client EKU,OU=dev,DC=idunno-dev,DC=org", diff --git a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs b/src/Shared/test/MockTimeProvider.cs similarity index 88% rename from src/Servers/Kestrel/shared/test/MockTimeProvider.cs rename to src/Shared/test/MockTimeProvider.cs index d9035441c2fe..36097ad4e572 100644 --- a/src/Servers/Kestrel/shared/test/MockTimeProvider.cs +++ b/src/Shared/test/MockTimeProvider.cs @@ -24,6 +24,13 @@ public MockTimeProvider() _timestamp = Random.Shared.NextInt64(0, System.GetTimestamp() * 100); } + public MockTimeProvider(DateTimeOffset now) + { + _utcTicks = now.Ticks; + // Timestamps often measure system uptime. + _timestamp = Random.Shared.NextInt64(0, System.GetTimestamp() * 100); + } + public override DateTimeOffset GetUtcNow() { UtcNowCalled++; @@ -39,7 +46,7 @@ public override DateTimeOffset GetUtcNow() public void Advance(TimeSpan timeSpan) { Interlocked.Add(ref _utcTicks, timeSpan.Ticks); - Interlocked.Add(ref _timestamp, timeSpan.ToTicks(_timestampFrequency)); + Interlocked.Add(ref _timestamp, timeSpan.Ticks * (_timestampFrequency / TimeSpan.TicksPerSecond)); } public void AdvanceTo(DateTimeOffset newUtcNow) From 4cc651d5f49e48cc4fba27e91f53e73386a43855 Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 16 May 2023 10:39:24 -0700 Subject: [PATCH 11/17] Share MockTimeProvider with Identity --- .../MapIdentityTests.cs | 2 +- ...AspNetCore.Identity.FunctionalTests.csproj | 2 +- .../Microsoft.AspNetCore.Identity.Test.csproj | 3 ++- .../SecurityStampValidatorTest.cs | 9 +++++--- .../test/InMemory.Test/FunctionalTest.cs | 15 ++++++------ ...t.AspNetCore.Identity.InMemory.Test.csproj | 4 +++- src/Identity/test/Shared/TestTimeProvider.cs | 23 ------------------- 7 files changed, 21 insertions(+), 37 deletions(-) delete mode 100644 src/Identity/test/Shared/TestTimeProvider.cs diff --git a/src/Identity/test/Identity.FunctionalTests/MapIdentityTests.cs b/src/Identity/test/Identity.FunctionalTests/MapIdentityTests.cs index 4c58725dfd6a..42cad245feb0 100644 --- a/src/Identity/test/Identity.FunctionalTests/MapIdentityTests.cs +++ b/src/Identity/test/Identity.FunctionalTests/MapIdentityTests.cs @@ -70,7 +70,7 @@ public async Task CanLoginWithBearerToken(string addIdentityMode) [Fact] public async Task CanCustomizeBearerTokenExpiration() { - var clock = new TestTimeProvider(); + var clock = new MockTimeProvider(); var expireTimeSpan = TimeSpan.FromSeconds(42); await using var app = await CreateAppAsync(services => diff --git a/src/Identity/test/Identity.FunctionalTests/Microsoft.AspNetCore.Identity.FunctionalTests.csproj b/src/Identity/test/Identity.FunctionalTests/Microsoft.AspNetCore.Identity.FunctionalTests.csproj index 0b19674121ee..0504b59f1121 100644 --- a/src/Identity/test/Identity.FunctionalTests/Microsoft.AspNetCore.Identity.FunctionalTests.csproj +++ b/src/Identity/test/Identity.FunctionalTests/Microsoft.AspNetCore.Identity.FunctionalTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Identity/test/Identity.Test/Microsoft.AspNetCore.Identity.Test.csproj b/src/Identity/test/Identity.Test/Microsoft.AspNetCore.Identity.Test.csproj index 197835a8d54f..894af599ff4b 100644 --- a/src/Identity/test/Identity.Test/Microsoft.AspNetCore.Identity.Test.csproj +++ b/src/Identity/test/Identity.Test/Microsoft.AspNetCore.Identity.Test.csproj @@ -5,7 +5,8 @@ - + + diff --git a/src/Identity/test/Identity.Test/SecurityStampValidatorTest.cs b/src/Identity/test/Identity.Test/SecurityStampValidatorTest.cs index 13934e5a608b..facd6a3711a0 100644 --- a/src/Identity/test/Identity.Test/SecurityStampValidatorTest.cs +++ b/src/Identity/test/Identity.Test/SecurityStampValidatorTest.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -270,7 +271,7 @@ public async Task OnValidateIdentityDoesNotRejectsWhenNotExpired() public async Task OnValidateIdentityDoesNotExtendExpirationWhenSlidingIsDisabled() { var user = new PocoUser("test"); - var timeProvider = new TestTimeProvider(new DateTimeOffset(2013, 6, 11, 12, 34, 56, 0, TimeSpan.Zero)); + var timeProvider = new MockTimeProvider(); var httpContext = new Mock(); var userManager = MockHelpers.MockUserManager(); var identityOptions = new Mock>(); @@ -308,8 +309,10 @@ public async Task OnValidateIdentityDoesNotExtendExpirationWhenSlidingIsDisabled await SecurityStampValidator.ValidatePrincipalAsync(context); // Issued is moved forward, expires is not. - Assert.Equal(timeProvider.GetUtcNow(), context.Properties.IssuedUtc); - Assert.Equal(timeProvider.GetUtcNow() + TimeSpan.FromDays(1), context.Properties.ExpiresUtc); + var now = timeProvider.GetUtcNow(); + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Offset); // Truncate to the nearest second. + Assert.Equal(now, context.Properties.IssuedUtc); + Assert.Equal(now + TimeSpan.FromDays(1), context.Properties.ExpiresUtc); Assert.NotNull(context.Principal); } diff --git a/src/Identity/test/InMemory.Test/FunctionalTest.cs b/src/Identity/test/InMemory.Test/FunctionalTest.cs index f458f5d11e07..98aa844dddae 100644 --- a/src/Identity/test/InMemory.Test/FunctionalTest.cs +++ b/src/Identity/test/InMemory.Test/FunctionalTest.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity.Test; using Microsoft.AspNetCore.TestHost; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Net.Http.Headers; @@ -66,7 +67,7 @@ public async Task CookieContainsRoleClaim() [InlineData(false)] public async Task CanCreateMeLoginAndCookieStopsWorkingAfterExpiration(bool testCore) { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var server = await CreateServer(services => { services.ConfigureApplicationCookie(options => @@ -114,10 +115,10 @@ public async Task CanCreateMeLoginAndCookieStopsWorkingAfterExpiration(bool test [InlineData(false, false)] public async Task CanCreateMeLoginAndSecurityStampExtendsExpiration(bool rememberMe, bool testCore) { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var server = await CreateServer(services => { - services.Configure(o => o.TimeProvider = timeProvider); + services.AddSingleton(timeProvider); }, testCore: testCore); var transaction1 = await SendAsync(server, "http://example.com/createMe"); @@ -163,12 +164,12 @@ public async Task CanCreateMeLoginAndSecurityStampExtendsExpiration(bool remembe [InlineData(false)] public async Task CanAccessOldPrincipalDuringSecurityStampReplacement(bool testCore) { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var server = await CreateServer(services => { + services.AddSingleton(timeProvider); services.Configure(options => { - options.TimeProvider = timeProvider; options.OnRefreshingPrincipal = c => { var newId = new ClaimsIdentity(); @@ -216,7 +217,7 @@ public async Task CanAccessOldPrincipalDuringSecurityStampReplacement(bool testC [InlineData(false)] public async Task TwoFactorRememberCookieVerification(bool testCore) { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var server = await CreateServer(services => services.AddSingleton(timeProvider), testCore: testCore); var transaction1 = await SendAsync(server, "http://example.com/createMe"); @@ -245,7 +246,7 @@ public async Task TwoFactorRememberCookieVerification(bool testCore) [InlineData(false)] public async Task TwoFactorRememberCookieClearedBySecurityStampChange(bool testCore) { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var server = await CreateServer(services => services.AddSingleton(timeProvider), testCore: testCore); var transaction1 = await SendAsync(server, "http://example.com/createMe"); diff --git a/src/Identity/test/InMemory.Test/Microsoft.AspNetCore.Identity.InMemory.Test.csproj b/src/Identity/test/InMemory.Test/Microsoft.AspNetCore.Identity.InMemory.Test.csproj index 3acb0ea06c24..87ad230d34de 100644 --- a/src/Identity/test/InMemory.Test/Microsoft.AspNetCore.Identity.InMemory.Test.csproj +++ b/src/Identity/test/InMemory.Test/Microsoft.AspNetCore.Identity.InMemory.Test.csproj @@ -5,7 +5,8 @@ - + + @@ -18,3 +19,4 @@ + \ No newline at end of file diff --git a/src/Identity/test/Shared/TestTimeProvider.cs b/src/Identity/test/Shared/TestTimeProvider.cs deleted file mode 100644 index 13d8d529159f..000000000000 --- a/src/Identity/test/Shared/TestTimeProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.Identity.Test; - -internal class TestTimeProvider : TimeProvider -{ - private DateTimeOffset _current; - - public TestTimeProvider() : this(DateTimeOffset.UtcNow) { } - - public TestTimeProvider(DateTimeOffset current) - { - _current = current; - } - - public override DateTimeOffset GetUtcNow() => _current; - - public void Advance(TimeSpan timeSpan) - { - _current += timeSpan; - } -} From be0bb92a37bf593900b49116195a1c90f028311a Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 16 May 2023 11:19:30 -0700 Subject: [PATCH 12/17] Share MockTimeProvider with middleware --- ...soft.AspNetCore.OutputCaching.Tests.csproj | 4 ++++ .../test/OutputCacheMiddlewareTests.cs | 11 ++++++----- .../OutputCaching/test/TestUtils.cs | 19 ------------------- ...ft.AspNetCore.ResponseCaching.Tests.csproj | 4 ++++ .../test/ResponseCachingMiddlewareTests.cs | 16 ++++++++++------ .../ResponseCaching/test/TestUtils.cs | 19 ------------------- 6 files changed, 24 insertions(+), 49 deletions(-) diff --git a/src/Middleware/OutputCaching/test/Microsoft.AspNetCore.OutputCaching.Tests.csproj b/src/Middleware/OutputCaching/test/Microsoft.AspNetCore.OutputCaching.Tests.csproj index f3a12d8a3c3d..036adf5982bb 100644 --- a/src/Middleware/OutputCaching/test/Microsoft.AspNetCore.OutputCaching.Tests.csproj +++ b/src/Middleware/OutputCaching/test/Microsoft.AspNetCore.OutputCaching.Tests.csproj @@ -10,6 +10,10 @@ + + + + diff --git a/src/Middleware/OutputCaching/test/OutputCacheMiddlewareTests.cs b/src/Middleware/OutputCaching/test/OutputCacheMiddlewareTests.cs index d2623feea6eb..a353a200a577 100644 --- a/src/Middleware/OutputCaching/test/OutputCacheMiddlewareTests.cs +++ b/src/Middleware/OutputCaching/test/OutputCacheMiddlewareTests.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.OutputCaching.Memory; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Primitives; @@ -334,7 +335,7 @@ public void ContentIsNotModified_IfNoneMatch_MatchesAtLeastOneValue_True() [Fact] public void StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTime() { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var middleware = TestUtils.CreateTestMiddleware(options: new OutputCacheOptions { TimeProvider = timeProvider @@ -350,7 +351,7 @@ public void StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTime() [Fact] public void StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTimeOnlyOnce() { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var middleware = TestUtils.CreateTestMiddleware(options: new OutputCacheOptions { TimeProvider = timeProvider @@ -387,7 +388,7 @@ public void FinalizeCacheHeadersAsync_ResponseValidity_IgnoresExpiryIfAvailable( { // The Expires header should not be used when set in the response - var timeProvider = new TestTimeProvider(DateTimeOffset.MinValue); + var timeProvider = new MockTimeProvider(); var options = new OutputCacheOptions { TimeProvider = timeProvider @@ -410,7 +411,7 @@ public void FinalizeCacheHeadersAsync_ResponseValidity_UseMaxAgeIfAvailable() { // The MaxAge header should not be used if set in the response - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var sink = new TestSink(); var options = new OutputCacheOptions { @@ -436,7 +437,7 @@ public void FinalizeCacheHeadersAsync_ResponseValidity_UseMaxAgeIfAvailable() [Fact] public void FinalizeCacheHeadersAsync_ResponseValidity_UseSharedMaxAgeIfAvailable() { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var sink = new TestSink(); var options = new OutputCacheOptions { diff --git a/src/Middleware/OutputCaching/test/TestUtils.cs b/src/Middleware/OutputCaching/test/TestUtils.cs index 615cec55e535..b745adae97dc 100644 --- a/src/Middleware/OutputCaching/test/TestUtils.cs +++ b/src/Middleware/OutputCaching/test/TestUtils.cs @@ -347,25 +347,6 @@ public ValueTask SetAsync(string key, byte[] entry, string[]? tags, TimeSpan val } } -internal class TestTimeProvider : TimeProvider -{ - private DateTimeOffset _current; - - public TestTimeProvider() : this(DateTimeOffset.UtcNow) { } - - public TestTimeProvider(DateTimeOffset current) - { - _current = current; - } - - public override DateTimeOffset GetUtcNow() => _current; - - public void Advance(TimeSpan timeSpan) - { - _current += timeSpan; - } -} - internal class AllowTestPolicy : IOutputCachePolicy { public ValueTask CacheRequestAsync(OutputCacheContext context, CancellationToken cancellationToken) diff --git a/src/Middleware/ResponseCaching/test/Microsoft.AspNetCore.ResponseCaching.Tests.csproj b/src/Middleware/ResponseCaching/test/Microsoft.AspNetCore.ResponseCaching.Tests.csproj index dd7bcb50d1df..f07480e34325 100644 --- a/src/Middleware/ResponseCaching/test/Microsoft.AspNetCore.ResponseCaching.Tests.csproj +++ b/src/Middleware/ResponseCaching/test/Microsoft.AspNetCore.ResponseCaching.Tests.csproj @@ -10,6 +10,10 @@ + + + + diff --git a/src/Middleware/ResponseCaching/test/ResponseCachingMiddlewareTests.cs b/src/Middleware/ResponseCaching/test/ResponseCachingMiddlewareTests.cs index 212c77caf4fb..5bcd25d6e8e9 100644 --- a/src/Middleware/ResponseCaching/test/ResponseCachingMiddlewareTests.cs +++ b/src/Middleware/ResponseCaching/test/ResponseCachingMiddlewareTests.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Primitives; @@ -356,7 +357,7 @@ public void ContentIsNotModified_IfNoneMatch_MatchesAtLeastOneValue_True() [Fact] public void StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTime() { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var middleware = TestUtils.CreateTestMiddleware(options: new ResponseCachingOptions { TimeProvider = timeProvider @@ -372,7 +373,7 @@ public void StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTime() [Fact] public void StartResponseAsync_IfAllowResponseCaptureIsTrue_SetsResponseTimeOnlyOnce() { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var middleware = TestUtils.CreateTestMiddleware(options: new ResponseCachingOptions { TimeProvider = timeProvider @@ -442,7 +443,10 @@ public void FinalizeCacheHeadersAsync_DefaultResponseValidity_Is10Seconds() [Fact] public void FinalizeCacheHeadersAsync_ResponseValidity_UseExpiryIfAvailable() { - var timeProvider = new TestTimeProvider(DateTimeOffset.MinValue); + var timeProvider = new MockTimeProvider(); + var now = timeProvider.GetUtcNow(); + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Offset); // Round down to seconds. + timeProvider.AdvanceTo(now); var sink = new TestSink(); var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions { @@ -451,7 +455,7 @@ public void FinalizeCacheHeadersAsync_ResponseValidity_UseExpiryIfAvailable() var context = TestUtils.CreateTestContext(); context.ResponseTime = timeProvider.GetUtcNow(); - context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(timeProvider.GetUtcNow() + TimeSpan.FromSeconds(11)); + context.HttpContext.Response.Headers.Expires = HeaderUtilities.FormatDate(now + TimeSpan.FromSeconds(11)); middleware.FinalizeCacheHeaders(context); @@ -462,7 +466,7 @@ public void FinalizeCacheHeadersAsync_ResponseValidity_UseExpiryIfAvailable() [Fact] public void FinalizeCacheHeadersAsync_ResponseValidity_UseMaxAgeIfAvailable() { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var sink = new TestSink(); var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions { @@ -487,7 +491,7 @@ public void FinalizeCacheHeadersAsync_ResponseValidity_UseMaxAgeIfAvailable() [Fact] public void FinalizeCacheHeadersAsync_ResponseValidity_UseSharedMaxAgeIfAvailable() { - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var sink = new TestSink(); var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions { diff --git a/src/Middleware/ResponseCaching/test/TestUtils.cs b/src/Middleware/ResponseCaching/test/TestUtils.cs index 14b5c211cb34..a3a64267e550 100644 --- a/src/Middleware/ResponseCaching/test/TestUtils.cs +++ b/src/Middleware/ResponseCaching/test/TestUtils.cs @@ -389,22 +389,3 @@ public void Set(string key, IResponseCacheEntry entry, TimeSpan validFor) _storage[key] = entry; } } - -internal class TestTimeProvider : TimeProvider -{ - private DateTimeOffset _current; - - public TestTimeProvider() : this(DateTimeOffset.UtcNow) { } - - public TestTimeProvider(DateTimeOffset current) - { - _current = current; - } - - public override DateTimeOffset GetUtcNow() => _current; - - public void Advance(TimeSpan timeSpan) - { - _current += timeSpan; - } -} From 167dc74d013dff93382cbe06422802753d431c6b Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 16 May 2023 11:26:46 -0700 Subject: [PATCH 13/17] Share MockTimeProvider with SignalR --- .../TestTimeProvider.cs | 28 ------------------- .../SignalR/test/HubConnectionHandlerTests.cs | 8 +++--- .../Microsoft.AspNetCore.SignalR.Tests.csproj | 3 +- 3 files changed, 6 insertions(+), 33 deletions(-) delete mode 100644 src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/TestTimeProvider.cs diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/TestTimeProvider.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/TestTimeProvider.cs deleted file mode 100644 index bbde8664c233..000000000000 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/TestTimeProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.SignalR.Tests; - -public class TestTimeProvider : TimeProvider -{ - private long _nowTicks; - - public TestTimeProvider() - { - // Use a random DateTimeOffset to ensure tests that incorrectly use the current DateTimeOffset fail always instead of only rarely. - // Pick a date between the min DateTimeOffset and a day before the max DateTimeOffset so there's room to advance the clock. - _nowTicks = NextLong(0, long.MaxValue - (long)TimeSpan.FromDays(1).TotalSeconds) * TimestampFrequency; - } - - public override long GetTimestamp() => _nowTicks; - - public void Advance(TimeSpan offset) - { - Interlocked.Add(ref _nowTicks, (long)(offset.TotalSeconds * TimestampFrequency)); - } - - private static long NextLong(long minValue, long maxValue) - { - return (long)(Random.Shared.NextDouble() * (maxValue - minValue) + minValue); - } -} diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs index 8fe8e44d7520..ed13833e35e3 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs @@ -2735,7 +2735,7 @@ public async Task WritesPingMessageIfNothingWrittenWhenKeepAliveIntervalElapses( using (StartVerifiableLog()) { var interval = TimeSpan.FromMilliseconds(100); - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services => services.Configure(options => options.KeepAliveInterval = interval), LoggerFactory); @@ -2797,7 +2797,7 @@ public async Task ConnectionNotTimedOutIfClientNeverPings() using (StartVerifiableLog()) { var timeout = TimeSpan.FromMilliseconds(100); - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services => services.Configure(options => options.ClientTimeoutInterval = timeout), LoggerFactory); @@ -2833,7 +2833,7 @@ public async Task ConnectionTimesOutIfInitialPingAndThenNoMessages() using (StartVerifiableLog()) { var timeout = TimeSpan.FromMilliseconds(100); - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services => services.Configure(options => options.ClientTimeoutInterval = timeout), LoggerFactory); @@ -2860,7 +2860,7 @@ public async Task ReceivingMessagesPreventsConnectionTimeoutFromOccuring() using (StartVerifiableLog()) { var timeout = TimeSpan.FromMilliseconds(300); - var timeProvider = new TestTimeProvider(); + var timeProvider = new MockTimeProvider(); var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services => services.Configure(options => options.ClientTimeoutInterval = timeout), LoggerFactory); diff --git a/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj b/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj index 24f414747f69..e1654b2368a8 100644 --- a/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj +++ b/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj @@ -5,7 +5,8 @@ - + + From 18cc20005dae81f34b646eba9687ce9770a449a1 Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 16 May 2023 14:57:35 -0700 Subject: [PATCH 14/17] Fix references --- .../test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj | 1 + .../InMemory.FunctionalTests/InMemory.FunctionalTests.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj index 8ab8fb711da1..1bc7490209d6 100644 --- a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj +++ b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj index ecc1447c1f7c..576463f85fca 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj @@ -11,6 +11,7 @@ + From 4ed2c616eee960acf0140505c15ca2d7aaaa0f54 Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 16 May 2023 15:01:53 -0700 Subject: [PATCH 15/17] Check for negative time --- src/Shared/test/MockTimeProvider.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Shared/test/MockTimeProvider.cs b/src/Shared/test/MockTimeProvider.cs index 36097ad4e572..d9950a23c3a3 100644 --- a/src/Shared/test/MockTimeProvider.cs +++ b/src/Shared/test/MockTimeProvider.cs @@ -47,6 +47,11 @@ public void Advance(TimeSpan timeSpan) { Interlocked.Add(ref _utcTicks, timeSpan.Ticks); Interlocked.Add(ref _timestamp, timeSpan.Ticks * (_timestampFrequency / TimeSpan.TicksPerSecond)); + + if (_utcTicks < 0 || _timestamp < 0) + { + throw new InvalidOperationException("UtcNow or Timestamp became negative."); + } } public void AdvanceTo(DateTimeOffset newUtcNow) @@ -57,6 +62,11 @@ public void AdvanceTo(DateTimeOffset newUtcNow) // Known timestamp frequencies are the same or larger than TicksPerSecond. var timestampOffset = (nowTicks - priorTicks) * (_timestampFrequency / TimeSpan.TicksPerSecond); Interlocked.Add(ref _timestamp, timestampOffset); + + if (_utcTicks < 0 || _timestamp < 0) + { + throw new InvalidOperationException("UtcNow or Timestamp became negative."); + } } public void AdvanceTo(long timestamp) @@ -66,6 +76,11 @@ public void AdvanceTo(long timestamp) // Known timestamp frequencies are the same or larger than TicksPerSecond. var utcOffset = (long)((timestamp - priorTimestamp) * ((double)TimeSpan.TicksPerSecond / _timestampFrequency)); Interlocked.Add(ref _utcTicks, utcOffset); + + if (_utcTicks < 0 || _timestamp < 0) + { + throw new InvalidOperationException("UtcNow or Timestamp became negative."); + } } public override ITimer CreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) From 9bc59985bcf199981d5874e91fc44e56b06084d9 Mon Sep 17 00:00:00 2001 From: Chris R Date: Thu, 18 May 2023 14:28:30 -0700 Subject: [PATCH 16/17] Checked, negative time --- .../Internal/Infrastructure/TimeExtensions.cs | 14 +++++--- src/Shared/test/MockTimeProvider.cs | 36 ++++++++++--------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeExtensions.cs index 81b170d5b40b..94dbba5af700 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TimeExtensions.cs @@ -10,16 +10,22 @@ public static long ToTicks(this TimeSpan timeSpan, TimeProvider timeProvider) public static long ToTicks(this TimeSpan timeSpan, long tickFrequency) { + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan), timeSpan, string.Empty); + } if (timeSpan == TimeSpan.MaxValue) { return long.MaxValue; } - if (timeSpan == TimeSpan.MinValue) + if (tickFrequency == TimeSpan.TicksPerSecond) + { + return timeSpan.Ticks; + } + checked { - return long.MinValue; + return (long)(timeSpan.Ticks * ((double)tickFrequency / TimeSpan.TicksPerSecond)); } - // The tick frequency should be equal or greater than TicksPerSecond - return timeSpan.Ticks * (tickFrequency / TimeSpan.TicksPerSecond); } public static long GetTimestamp(this TimeProvider timeProvider, TimeSpan timeSpan) diff --git a/src/Shared/test/MockTimeProvider.cs b/src/Shared/test/MockTimeProvider.cs index d9950a23c3a3..b7527ad7590b 100644 --- a/src/Shared/test/MockTimeProvider.cs +++ b/src/Shared/test/MockTimeProvider.cs @@ -45,12 +45,14 @@ public override DateTimeOffset GetUtcNow() public void Advance(TimeSpan timeSpan) { + if (timeSpan < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(timeSpan), timeSpan, "Cannot go back in time."); + } Interlocked.Add(ref _utcTicks, timeSpan.Ticks); - Interlocked.Add(ref _timestamp, timeSpan.Ticks * (_timestampFrequency / TimeSpan.TicksPerSecond)); - - if (_utcTicks < 0 || _timestamp < 0) + checked { - throw new InvalidOperationException("UtcNow or Timestamp became negative."); + Interlocked.Add(ref _timestamp, (long)(timeSpan.Ticks * ((double)_timestampFrequency / TimeSpan.TicksPerSecond))); } } @@ -58,28 +60,28 @@ public void AdvanceTo(DateTimeOffset newUtcNow) { var nowTicks = newUtcNow.UtcTicks; var priorTicks = Interlocked.Exchange(ref _utcTicks, nowTicks); - // Advance Timestamp by the same amount. - // Known timestamp frequencies are the same or larger than TicksPerSecond. - var timestampOffset = (nowTicks - priorTicks) * (_timestampFrequency / TimeSpan.TicksPerSecond); - Interlocked.Add(ref _timestamp, timestampOffset); - - if (_utcTicks < 0 || _timestamp < 0) + if (priorTicks > nowTicks) { - throw new InvalidOperationException("UtcNow or Timestamp became negative."); + var priorTime = new DateTimeOffset(priorTicks, TimeSpan.Zero); + throw new ArgumentOutOfRangeException(nameof(newUtcNow), newUtcNow, $"Cannot go back in time. The prior time was {priorTime}"); } + // Advance Timestamp by the same amount. + var timestampOffset = (long)((nowTicks - priorTicks) * ((double)_timestampFrequency / TimeSpan.TicksPerSecond)); + Interlocked.Add(ref _timestamp, timestampOffset); } public void AdvanceTo(long timestamp) { var priorTimestamp = Interlocked.Exchange(ref _timestamp, timestamp); + if (priorTimestamp > timestamp) + { + throw new ArgumentOutOfRangeException(nameof(timestamp), timestamp, $"Cannot go back in time. The prior timestamp was {priorTimestamp}"); + } // Advance UtcNow by the same amount. - // Known timestamp frequencies are the same or larger than TicksPerSecond. - var utcOffset = (long)((timestamp - priorTimestamp) * ((double)TimeSpan.TicksPerSecond / _timestampFrequency)); - Interlocked.Add(ref _utcTicks, utcOffset); - - if (_utcTicks < 0 || _timestamp < 0) + checked { - throw new InvalidOperationException("UtcNow or Timestamp became negative."); + var utcOffset = (long)((timestamp - priorTimestamp) * ((double)TimeSpan.TicksPerSecond / _timestampFrequency)); + Interlocked.Add(ref _utcTicks, utcOffset); } } From c6eacb57a3adb9c2c122975f92ef59119abffebc Mon Sep 17 00:00:00 2001 From: Chris R Date: Fri, 19 May 2023 09:02:33 -0700 Subject: [PATCH 17/17] Round up --- .../ResponseCaching/test/ResponseCachingMiddlewareTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/ResponseCaching/test/ResponseCachingMiddlewareTests.cs b/src/Middleware/ResponseCaching/test/ResponseCachingMiddlewareTests.cs index 5bcd25d6e8e9..65451ba8dbd7 100644 --- a/src/Middleware/ResponseCaching/test/ResponseCachingMiddlewareTests.cs +++ b/src/Middleware/ResponseCaching/test/ResponseCachingMiddlewareTests.cs @@ -445,7 +445,7 @@ public void FinalizeCacheHeadersAsync_ResponseValidity_UseExpiryIfAvailable() { var timeProvider = new MockTimeProvider(); var now = timeProvider.GetUtcNow(); - now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Offset); // Round down to seconds. + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second + 1, now.Offset); // Round up to seconds. timeProvider.AdvanceTo(now); var sink = new TestSink(); var middleware = TestUtils.CreateTestMiddleware(testSink: sink, options: new ResponseCachingOptions