diff --git a/src/SignalR/common/Shared/ISystemClock.cs b/src/SignalR/common/Shared/ISystemClock.cs
deleted file mode 100644
index e3e1ed385866..000000000000
--- a/src/SignalR/common/Shared/ISystemClock.cs
+++ /dev/null
@@ -1,12 +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.Internal;
-
-internal interface ISystemClock
-{
- ///
- /// Retrieves ticks for the current system up time.
- ///
- long CurrentTicks { get; }
-}
diff --git a/src/SignalR/common/Shared/SystemClock.cs b/src/SignalR/common/Shared/SystemClock.cs
deleted file mode 100644
index 124079fe93b2..000000000000
--- a/src/SignalR/common/Shared/SystemClock.cs
+++ /dev/null
@@ -1,13 +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.Internal;
-
-///
-/// Provides access to the normal system clock.
-///
-internal sealed class SystemClock : ISystemClock
-{
- ///
- public long CurrentTicks => Environment.TickCount64;
-}
diff --git a/src/SignalR/server/Core/src/HubConnectionContext.cs b/src/SignalR/server/Core/src/HubConnectionContext.cs
index 185fb1b4f52e..a2a9f24429ef 100644
--- a/src/SignalR/server/Core/src/HubConnectionContext.cs
+++ b/src/SignalR/server/Core/src/HubConnectionContext.cs
@@ -10,7 +10,6 @@
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
-using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.Logging;
@@ -29,11 +28,11 @@ public partial class HubConnectionContext
private readonly ILogger _logger;
private readonly CancellationTokenSource _connectionAbortedTokenSource = new CancellationTokenSource();
private readonly TaskCompletionSource _abortCompletedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- private readonly long _keepAliveInterval;
- private readonly long _clientTimeoutInterval;
+ private readonly TimeSpan _keepAliveInterval;
+ private readonly TimeSpan _clientTimeoutInterval;
private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1);
private readonly object _receiveMessageTimeoutLock = new object();
- private readonly ISystemClock _systemClock;
+ private readonly TimeProvider _timeProvider;
private readonly CancellationTokenRegistration _closedRegistration;
private readonly CancellationTokenRegistration? _closedRequestedRegistration;
@@ -46,7 +45,7 @@ public partial class HubConnectionContext
private readonly int _streamBufferCapacity;
private readonly long? _maxMessageSize;
private bool _receivedMessageTimeoutEnabled;
- private long _receivedMessageElapsedTicks;
+ private TimeSpan _receivedMessageElapsed;
private long _receivedMessageTick;
private ClaimsPrincipal? _user;
@@ -58,8 +57,9 @@ public partial class HubConnectionContext
/// The options to configure the HubConnectionContext.
public HubConnectionContext(ConnectionContext connectionContext, HubConnectionContextOptions contextOptions, ILoggerFactory loggerFactory)
{
- _keepAliveInterval = (long)contextOptions.KeepAliveInterval.TotalMilliseconds;
- _clientTimeoutInterval = (long)contextOptions.ClientTimeoutInterval.TotalMilliseconds;
+ _timeProvider = contextOptions.TimeProvider ?? TimeProvider.System;
+ _keepAliveInterval = contextOptions.KeepAliveInterval;
+ _clientTimeoutInterval = contextOptions.ClientTimeoutInterval;
_streamBufferCapacity = contextOptions.StreamBufferCapacity;
_maxMessageSize = contextOptions.MaximumReceiveMessageSize;
@@ -76,8 +76,7 @@ public HubConnectionContext(ConnectionContext connectionContext, HubConnectionCo
HubCallerContext = new DefaultHubCallerContext(this);
- _systemClock = contextOptions.SystemClock ?? new SystemClock();
- _lastSendTick = _systemClock.CurrentTicks;
+ _lastSendTick = _timeProvider.GetTimestamp();
var maxInvokeLimit = contextOptions.MaximumParallelInvocations;
ActiveInvocationLimit = new ChannelBasedSemaphore(maxInvokeLimit);
@@ -614,7 +613,8 @@ private async Task AbortAsyncSlow()
private void KeepAliveTick()
{
- var currentTime = _systemClock.CurrentTicks;
+ var currentTime = _timeProvider.GetTimestamp();
+ var elapsed = _timeProvider.GetElapsedTime(Volatile.Read(ref _lastSendTick), currentTime);
// Implements the keep-alive tick behavior
// Each tick, we check if the time since the last send is larger than the keep alive duration (in ticks).
@@ -622,7 +622,7 @@ private void KeepAliveTick()
// true "ping rate" of the server could be (_hubOptions.KeepAliveInterval + HubEndPoint.KeepAliveTimerInterval),
// because if the interval elapses right after the last tick of this timer, it won't be detected until the next tick.
- if (currentTime - Volatile.Read(ref _lastSendTick) > _keepAliveInterval)
+ if (elapsed > _keepAliveInterval)
{
// Haven't sent a message for the entire keep-alive duration, so send a ping.
// If the transport channel is full, this will fail, but that's OK because
@@ -657,11 +657,11 @@ private void CheckClientTimeout()
{
if (_receivedMessageTimeoutEnabled)
{
- _receivedMessageElapsedTicks = _systemClock.CurrentTicks - _receivedMessageTick;
+ _receivedMessageElapsed = _timeProvider.GetElapsedTime(_receivedMessageTick);
- if (_receivedMessageElapsedTicks >= _clientTimeoutInterval)
+ if (_receivedMessageElapsed >= _clientTimeoutInterval)
{
- Log.ClientTimeout(_logger, TimeSpan.FromMilliseconds(_clientTimeoutInterval));
+ Log.ClientTimeout(_logger, _clientTimeoutInterval);
AbortAllowReconnect();
}
}
@@ -707,7 +707,7 @@ internal void BeginClientTimeout()
lock (_receiveMessageTimeoutLock)
{
_receivedMessageTimeoutEnabled = true;
- _receivedMessageTick = _systemClock.CurrentTicks;
+ _receivedMessageTick = _timeProvider.GetTimestamp();
}
}
@@ -717,7 +717,7 @@ internal void StopClientTimeout()
{
// we received a message so stop the timer and reset it
// it will resume after the message has been processed
- _receivedMessageElapsedTicks = 0;
+ _receivedMessageElapsed = TimeSpan.Zero;
_receivedMessageTick = 0;
_receivedMessageTimeoutEnabled = false;
}
diff --git a/src/SignalR/server/Core/src/HubConnectionContextOptions.cs b/src/SignalR/server/Core/src/HubConnectionContextOptions.cs
index fb965fcbe9ba..af5dd734c266 100644
--- a/src/SignalR/server/Core/src/HubConnectionContextOptions.cs
+++ b/src/SignalR/server/Core/src/HubConnectionContextOptions.cs
@@ -1,8 +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 Microsoft.AspNetCore.Internal;
-
namespace Microsoft.AspNetCore.SignalR;
///
@@ -30,7 +28,7 @@ public class HubConnectionContextOptions
///
public long? MaximumReceiveMessageSize { get; set; }
- internal ISystemClock SystemClock { get; set; } = default!;
+ internal TimeProvider TimeProvider { get; set; } = default!;
///
/// Gets or sets the maximum parallel hub method invocations.
diff --git a/src/SignalR/server/Core/src/HubConnectionHandler.cs b/src/SignalR/server/Core/src/HubConnectionHandler.cs
index 3269d6f7afcd..ab3d0f5bbd7b 100644
--- a/src/SignalR/server/Core/src/HubConnectionHandler.cs
+++ b/src/SignalR/server/Core/src/HubConnectionHandler.cs
@@ -3,7 +3,6 @@
using System.Linq;
using Microsoft.AspNetCore.Connections;
-using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.DependencyInjection;
@@ -31,7 +30,7 @@ public class HubConnectionHandler : ConnectionHandler where THub : Hub
private readonly int _maxParallelInvokes;
// Internal for testing
- internal ISystemClock SystemClock { get; set; } = new SystemClock();
+ internal TimeProvider TimeProvider { get; set; } = TimeProvider.System;
///
/// Initializes a new instance of the class.
@@ -120,7 +119,7 @@ public override async Task OnConnectedAsync(ConnectionContext connection)
ClientTimeoutInterval = _hubOptions.ClientTimeoutInterval ?? _globalHubOptions.ClientTimeoutInterval ?? HubOptionsSetup.DefaultClientTimeoutInterval,
StreamBufferCapacity = _hubOptions.StreamBufferCapacity ?? _globalHubOptions.StreamBufferCapacity ?? HubOptionsSetup.DefaultStreamBufferCapacity,
MaximumReceiveMessageSize = _maximumMessageSize,
- SystemClock = SystemClock,
+ TimeProvider = TimeProvider,
MaximumParallelInvocations = _maxParallelInvokes,
};
diff --git a/src/SignalR/server/Core/src/Microsoft.AspNetCore.SignalR.Core.csproj b/src/SignalR/server/Core/src/Microsoft.AspNetCore.SignalR.Core.csproj
index 741fa83d85ed..db4c7d7e81df 100644
--- a/src/SignalR/server/Core/src/Microsoft.AspNetCore.SignalR.Core.csproj
+++ b/src/SignalR/server/Core/src/Microsoft.AspNetCore.SignalR.Core.csproj
@@ -15,8 +15,6 @@
-
-
diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/MockSystemClock.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/TestTimeProvider.cs
similarity index 64%
rename from src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/MockSystemClock.cs
rename to src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/TestTimeProvider.cs
index 3268451c0cfd..bbde8664c233 100644
--- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/MockSystemClock.cs
+++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/TestTimeProvider.cs
@@ -1,31 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Microsoft.AspNetCore.Internal;
-
namespace Microsoft.AspNetCore.SignalR.Tests;
-public class MockSystemClock : ISystemClock
+public class TestTimeProvider : TimeProvider
{
private long _nowTicks;
- public MockSystemClock()
+ 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).TotalMilliseconds);
+ _nowTicks = NextLong(0, long.MaxValue - (long)TimeSpan.FromDays(1).TotalSeconds) * TimestampFrequency;
}
- public long CurrentTicks
+ public override long GetTimestamp() => _nowTicks;
+
+ public void Advance(TimeSpan offset)
{
- get => _nowTicks;
- set
- {
- Interlocked.Exchange(ref _nowTicks, value);
- }
+ Interlocked.Add(ref _nowTicks, (long)(offset.TotalSeconds * TimestampFrequency));
}
- private long NextLong(long minValue, long maxValue)
+ 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 eefd6c4c60bd..943b6efa6a21 100644
--- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs
+++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs
@@ -2735,13 +2735,13 @@ public async Task WritesPingMessageIfNothingWrittenWhenKeepAliveIntervalElapses(
{
using (StartVerifiableLog())
{
- var intervalInMS = 100;
- var clock = new MockSystemClock();
+ var interval = TimeSpan.FromMilliseconds(100);
+ var timeProvider = new TestTimeProvider();
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
services.Configure(options =>
- options.KeepAliveInterval = TimeSpan.FromMilliseconds(intervalInMS)), LoggerFactory);
+ options.KeepAliveInterval = interval), LoggerFactory);
var connectionHandler = serviceProvider.GetService>();
- connectionHandler.SystemClock = clock;
+ connectionHandler.TimeProvider = timeProvider;
using (var client = new TestClient(new NewtonsoftJsonHubProtocol()))
{
@@ -2752,7 +2752,7 @@ public async Task WritesPingMessageIfNothingWrittenWhenKeepAliveIntervalElapses(
var heartbeatCount = 5;
for (var i = 0; i < heartbeatCount; i++)
{
- clock.CurrentTicks = clock.CurrentTicks + intervalInMS + 1;
+ timeProvider.Advance(interval + TimeSpan.FromMilliseconds(1));
client.TickHeartbeat();
}
@@ -2797,13 +2797,13 @@ public async Task ConnectionNotTimedOutIfClientNeverPings()
{
using (StartVerifiableLog())
{
- var timeoutInMS = 100;
- var clock = new MockSystemClock();
+ var timeout = TimeSpan.FromMilliseconds(100);
+ var timeProvider = new TestTimeProvider();
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
services.Configure(options =>
- options.ClientTimeoutInterval = TimeSpan.FromMilliseconds(timeoutInMS)), LoggerFactory);
+ options.ClientTimeoutInterval = timeout), LoggerFactory);
var connectionHandler = serviceProvider.GetService>();
- connectionHandler.SystemClock = clock;
+ connectionHandler.TimeProvider = timeProvider;
using (var client = new TestClient(new NewtonsoftJsonHubProtocol()))
{
@@ -2814,7 +2814,7 @@ public async Task ConnectionNotTimedOutIfClientNeverPings()
// We go over the 100 ms timeout interval multiple times
for (var i = 0; i < 3; i++)
{
- clock.CurrentTicks = clock.CurrentTicks + timeoutInMS + 1;
+ timeProvider.Advance(timeout + TimeSpan.FromMilliseconds(1));
client.TickHeartbeat();
}
@@ -2833,13 +2833,13 @@ public async Task ConnectionTimesOutIfInitialPingAndThenNoMessages()
{
using (StartVerifiableLog())
{
- var timeoutInMS = 100;
- var clock = new MockSystemClock();
+ var timeout = TimeSpan.FromMilliseconds(100);
+ var timeProvider = new TestTimeProvider();
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
services.Configure(options =>
- options.ClientTimeoutInterval = TimeSpan.FromMilliseconds(timeoutInMS)), LoggerFactory);
+ options.ClientTimeoutInterval = timeout), LoggerFactory);
var connectionHandler = serviceProvider.GetService>();
- connectionHandler.SystemClock = clock;
+ connectionHandler.TimeProvider = timeProvider;
using (var client = new TestClient(new NewtonsoftJsonHubProtocol()))
{
@@ -2847,7 +2847,7 @@ public async Task ConnectionTimesOutIfInitialPingAndThenNoMessages()
await client.Connected.DefaultTimeout();
await client.SendHubMessageAsync(PingMessage.Instance);
- clock.CurrentTicks = clock.CurrentTicks + timeoutInMS + 1;
+ timeProvider.Advance(timeout + TimeSpan.FromMilliseconds(1));
client.TickHeartbeat();
await connectionHandlerTask.DefaultTimeout();
@@ -2860,13 +2860,13 @@ public async Task ReceivingMessagesPreventsConnectionTimeoutFromOccuring()
{
using (StartVerifiableLog())
{
- var timeoutInMS = 300;
- var clock = new MockSystemClock();
+ var timeout = TimeSpan.FromMilliseconds(300);
+ var timeProvider = new TestTimeProvider();
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
services.Configure(options =>
- options.ClientTimeoutInterval = TimeSpan.FromMilliseconds(timeoutInMS)), LoggerFactory);
+ options.ClientTimeoutInterval = timeout), LoggerFactory);
var connectionHandler = serviceProvider.GetService>();
- connectionHandler.SystemClock = clock;
+ connectionHandler.TimeProvider = timeProvider;
using (var client = new TestClient(new NewtonsoftJsonHubProtocol()))
{
@@ -2876,7 +2876,7 @@ public async Task ReceivingMessagesPreventsConnectionTimeoutFromOccuring()
for (int i = 0; i < 10; i++)
{
- clock.CurrentTicks = clock.CurrentTicks + timeoutInMS - 1;
+ timeProvider.Advance(timeout - TimeSpan.FromMilliseconds(1));
client.TickHeartbeat();
await client.SendHubMessageAsync(PingMessage.Instance);
}