diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index cad0c8cc8ed..e701998082e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -4,7 +4,8 @@ - + + diff --git a/src/Polly.Core.Tests/Hedging/HedgingTimeProvider.cs b/src/Polly.Core.Tests/Hedging/HedgingTimeProvider.cs index f2bddba91ff..d6738df3075 100644 --- a/src/Polly.Core.Tests/Hedging/HedgingTimeProvider.cs +++ b/src/Polly.Core.Tests/Hedging/HedgingTimeProvider.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Polly.Utils; namespace Polly.Core.Tests.Hedging; @@ -25,15 +24,16 @@ public void Advance(TimeSpan diff) public List DelayEntries { get; } = new List(); - public override DateTimeOffset UtcNow => _utcNow; + public override DateTimeOffset GetUtcNow() => _utcNow; - public override void CancelAfter(CancellationTokenSource source, TimeSpan delay) + public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period) { - throw new NotSupportedException(); + return base.CreateTimer(callback, state, dueTime, period); } public override Task Delay(TimeSpan delayValue, CancellationToken cancellationToken = default) { + var entry = new DelayEntry(delayValue, new TaskCompletionSource(), _utcNow.Add(delayValue)); cancellationToken.Register(() => entry.Source.TrySetCanceled(cancellationToken)); DelayEntries.Add(entry); diff --git a/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs b/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs index ab166eec747..5725ac5ee02 100644 --- a/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs +++ b/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs @@ -272,7 +272,7 @@ private void CloseCircuit_NeedsLock(Outcome outcome, bool manu private bool PermitHalfOpenCircuitTest_NeedsLock() { - var now = _timeProvider.UtcNow; + var now = _timeProvider.GetUtcNow(); if (now >= _blockedUntil) { _blockedUntil = now + _breakDuration; @@ -307,7 +307,7 @@ private void OpenCircuit_NeedsLock(Outcome outcome, bool manua private void OpenCircuitFor_NeedsLock(Outcome outcome, TimeSpan breakDuration, bool manual, ResilienceContext context, out Task? scheduledTask) { scheduledTask = null; - var utcNow = _timeProvider.UtcNow; + var utcNow = _timeProvider.GetUtcNow(); _blockedUntil = IsDateTimeOverflow(utcNow, breakDuration) ? DateTimeOffset.MaxValue : utcNow + breakDuration; diff --git a/src/Polly.Core/CircuitBreaker/Health/RollingHealthMetrics.cs b/src/Polly.Core/CircuitBreaker/Health/RollingHealthMetrics.cs index 658aea7b7b8..f00806a858a 100644 --- a/src/Polly.Core/CircuitBreaker/Health/RollingHealthMetrics.cs +++ b/src/Polly.Core/CircuitBreaker/Health/RollingHealthMetrics.cs @@ -44,7 +44,7 @@ public override HealthInfo GetHealthInfo() private HealthWindow UpdateCurrentWindow() { - var now = TimeProvider.UtcNow; + var now = TimeProvider.GetUtcNow(); if (_currentWindow == null || now - _currentWindow.StartedAt >= _windowDuration) { _currentWindow = new() diff --git a/src/Polly.Core/CircuitBreaker/Health/SingleHealthMetrics.cs b/src/Polly.Core/CircuitBreaker/Health/SingleHealthMetrics.cs index 8afdd1470cc..41748f93229 100644 --- a/src/Polly.Core/CircuitBreaker/Health/SingleHealthMetrics.cs +++ b/src/Polly.Core/CircuitBreaker/Health/SingleHealthMetrics.cs @@ -15,7 +15,7 @@ public SingleHealthMetrics(TimeSpan samplingDuration, TimeProvider timeProvider) : base(timeProvider) { _samplingDuration = samplingDuration; - _startedAt = timeProvider.UtcNow; + _startedAt = timeProvider.GetUtcNow(); } public override void IncrementSuccess() @@ -32,7 +32,7 @@ public override void IncrementFailure() public override void Reset() { - _startedAt = TimeProvider.UtcNow; + _startedAt = TimeProvider.GetUtcNow(); _successes = 0; _failures = 0; } @@ -46,7 +46,7 @@ public override HealthInfo GetHealthInfo() private void TryReset() { - if (TimeProvider.UtcNow - _startedAt >= _samplingDuration) + if (TimeProvider.GetUtcNow() - _startedAt >= _samplingDuration) { Reset(); } diff --git a/src/Polly.Core/Hedging/Controller/HedgingController.cs b/src/Polly.Core/Hedging/Controller/HedgingController.cs index 1cab2b0cfe8..a33ea24da0a 100644 --- a/src/Polly.Core/Hedging/Controller/HedgingController.cs +++ b/src/Polly.Core/Hedging/Controller/HedgingController.cs @@ -15,7 +15,7 @@ public HedgingController(TimeProvider provider, HedgingHandler.Handler handler, _executionPool = new ObjectPool(() => { Interlocked.Increment(ref _rentedExecutions); - return new TaskExecution(handler); + return new TaskExecution(handler, provider); }, _ => { diff --git a/src/Polly.Core/Hedging/Controller/TaskExecution.cs b/src/Polly.Core/Hedging/Controller/TaskExecution.cs index 41e5be624d7..c8b9d94a0c5 100644 --- a/src/Polly.Core/Hedging/Controller/TaskExecution.cs +++ b/src/Polly.Core/Hedging/Controller/TaskExecution.cs @@ -23,11 +23,16 @@ internal sealed class TaskExecution { private readonly ResilienceContext _cachedContext = ResilienceContext.Get(); private readonly HedgingHandler.Handler _handler; + private readonly TimeProvider _timeProvider; private CancellationTokenSource? _cancellationSource; private CancellationTokenRegistration? _cancellationRegistration; private ResilienceContext? _activeContext; - public TaskExecution(HedgingHandler.Handler handler) => _handler = handler; + public TaskExecution(HedgingHandler.Handler handler, TimeProvider timeProvider) + { + _handler = handler; + _timeProvider = timeProvider; + } /// /// Gets the task that represents the execution of the hedged task. @@ -81,7 +86,7 @@ public async ValueTask InitializeAsync( int attempt) { Type = type; - _cancellationSource = CancellationTokenSourcePool.Get(); + _cancellationSource = CancellationTokenSourcePool.Get(TimeSpan.Zero, _timeProvider); Properties.Replace(snapshot.OriginalProperties); if (snapshot.OriginalCancellationToken.CanBeCanceled) diff --git a/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs b/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs index a4e0d3139d6..92d18a30697 100644 --- a/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs +++ b/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs @@ -5,7 +5,6 @@ using Polly.Hedging; using Polly.Hedging.Utils; using Polly.Strategy; -using Polly.Utils; namespace Polly; diff --git a/src/Polly.Core/Polly.Core.csproj b/src/Polly.Core/Polly.Core.csproj index 87ee858cb7d..ec6199e7813 100644 --- a/src/Polly.Core/Polly.Core.csproj +++ b/src/Polly.Core/Polly.Core.csproj @@ -24,6 +24,7 @@ + diff --git a/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs b/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs index 8fc889708fd..6b909dfba2a 100644 --- a/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs +++ b/src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs @@ -35,8 +35,7 @@ protected internal override async ValueTask ExecuteCoreAsync new CancellationTokenSource(), static cts => true); #endif - public static CancellationTokenSource Get() + public static CancellationTokenSource Get(TimeSpan delay, TimeProvider timeProvider) { #if NET6_0_OR_GREATER - return Pool.Get(); + if (timeProvider != TimeProvider.System) + { + return timeProvider.CreateCancellationTokenSource(delay); + } + + // we are only polling sources when system time provider is used + var pooledSource = Pool.Get(); + if (delay != System.Threading.Timeout.InfiniteTimeSpan) + { + pooledSource.CancelAfter(delay); + } + + return pooledSource; #else - return new CancellationTokenSource(); + return timeProvider.CreateCancellationTokenSource(delay); #endif } diff --git a/src/Polly.Core/Utils/TimeProvider.cs b/src/Polly.Core/Utils/TimeProvider.cs deleted file mode 100644 index 467c2f21c7e..00000000000 --- a/src/Polly.Core/Utils/TimeProvider.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Threading; - -namespace Polly.Utils; - -#pragma warning disable S3872 // Parameter names should not duplicate the names of their methods - -/// -/// TEMPORARY ONLY, to be replaced with System.TimeProvider - https://github.com/dotnet/runtime/issues/36617 later. -/// -/// We trimmed some of the API that's not relevant for us too. -internal abstract class TimeProvider -{ - private readonly double _tickFrequency; - - public static TimeProvider System { get; } = new SystemTimeProvider(); - - protected TimeProvider(long timestampFrequency) - { - TimestampFrequency = timestampFrequency; - _tickFrequency = (double)TimeSpan.TicksPerSecond / TimestampFrequency; - } - - public abstract DateTimeOffset UtcNow { get; } - - public long TimestampFrequency { get; } - - public abstract long GetTimestamp(); - - public TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) => new((long)((endingTimestamp - startingTimestamp) * _tickFrequency)); - - public TimeSpan GetElapsedTime(long startingTimestamp) => GetElapsedTime(startingTimestamp, GetTimestamp()); - - public abstract Task Delay(TimeSpan delay, CancellationToken cancellationToken = default); - - public abstract void CancelAfter(CancellationTokenSource source, TimeSpan delay); - - private sealed class SystemTimeProvider : TimeProvider - { - public SystemTimeProvider() - : base(Stopwatch.Frequency) - { - } - - public override long GetTimestamp() => Stopwatch.GetTimestamp(); - - public override Task Delay(TimeSpan delay, CancellationToken cancellationToken = default) => Task.Delay(delay, cancellationToken); - - public override void CancelAfter(CancellationTokenSource source, TimeSpan delay) => source.CancelAfter(delay); - - public override DateTimeOffset UtcNow => DateTimeOffset.UtcNow; - } -}