diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 30e988ae560..e5bfa20acd4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,7 +91,7 @@ jobs: uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: name: packages-${{ matrix.os_name }} - path: ./artifacts/nuget-packages + path: ./artifacts/package/release if-no-files-found: error - name: Upload signing file list diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000000..c3398254fd3 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,5 @@ + + + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props index e8b9af959fb..fb622139459 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,6 +1,6 @@ - 7.0.0 + 8.0.0 true 8.1.0 @@ -10,6 +10,7 @@ + @@ -20,6 +21,7 @@ + @@ -41,12 +43,4 @@ - - - - - - - - diff --git a/bench/Polly.Benchmarks/Polly.Benchmarks.csproj b/bench/Polly.Benchmarks/Polly.Benchmarks.csproj index 53a3e563d6a..0fc02332e3f 100644 --- a/bench/Polly.Benchmarks/Polly.Benchmarks.csproj +++ b/bench/Polly.Benchmarks/Polly.Benchmarks.csproj @@ -2,7 +2,7 @@ false Exe - net6.0;net7.0 + net6.0;net7.0;net8.0 enable Benchmark $(NoWarn);CA1822;SA1414;IDE0060 diff --git a/bench/Polly.Core.Benchmarks/Polly.Core.Benchmarks.csproj b/bench/Polly.Core.Benchmarks/Polly.Core.Benchmarks.csproj index d7742e9db96..fd0ec851ece 100644 --- a/bench/Polly.Core.Benchmarks/Polly.Core.Benchmarks.csproj +++ b/bench/Polly.Core.Benchmarks/Polly.Core.Benchmarks.csproj @@ -1,6 +1,6 @@  - net7.0 + net8.0;net7.0 Polly true Benchmark diff --git a/build.cake b/build.cake index 013c4260812..e535c0ba225 100644 --- a/build.cake +++ b/build.cake @@ -33,7 +33,7 @@ var artifactsDir = Directory("./artifacts"); var testResultsDir = System.IO.Path.Combine(artifactsDir, Directory("test-results")); // NuGet -var nupkgDestDir = System.IO.Path.Combine(artifactsDir, Directory("nuget-packages")); +var nupkgDestDir = System.IO.Path.Combine(artifactsDir, Directory("package"), Directory("release")); // Stryker / Mutation Testing var strykerConfig = MakeAbsolute(File("./eng/stryker-config.json")); @@ -65,17 +65,13 @@ Teardown(_ => Task("__Clean") .Does(() => { - DirectoryPath[] cleanDirectories = new DirectoryPath[] + CleanDirectories(new[] { testResultsDir, nupkgDestDir, artifactsDir, strykerOutput - }; - - CleanDirectories(cleanDirectories); - - foreach (var path in cleanDirectories) { EnsureDirectoryExists(path); } + }); foreach (var path in solutionPaths) { diff --git a/eng/Library.targets b/eng/Library.targets index 64004df8f31..b09e5c2a93a 100644 --- a/eng/Library.targets +++ b/eng/Library.targets @@ -14,6 +14,7 @@ true + 8.1.0 diff --git a/eng/stryker-config.json b/eng/stryker-config.json index ba638442829..bba70bf11b9 100644 --- a/eng/stryker-config.json +++ b/eng/stryker-config.json @@ -18,7 +18,8 @@ "block", "statement" ], - "target-framework": "net7.0", + "language-version": "Preview", + "target-framework": "net8.0", "thresholds": { "high": 100, "low": 100 diff --git a/global.json b/global.json index 0115ef0494c..33d26e563fb 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.403", + "version": "8.0.100", "allowPrerelease": false, "rollForward": "latestMajor" } diff --git a/src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs b/src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs index bb4adb10a3f..1258c4f5ae4 100644 --- a/src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs +++ b/src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs @@ -9,8 +9,8 @@ namespace Polly.CircuitBreaker; public sealed class CircuitBreakerManualControl { private readonly object _lock = new(); - private readonly HashSet> _onIsolate = new(); - private readonly HashSet> _onReset = new(); + private readonly HashSet> _onIsolate = []; + private readonly HashSet> _onReset = []; private bool _isolated; /// diff --git a/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs b/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs index 8e71fb79c7a..73fc1010629 100644 --- a/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs +++ b/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs @@ -254,10 +254,14 @@ private static bool IsDateTimeOverflow(DateTimeOffset utcNow, TimeSpan breakDura private void EnsureNotDisposed() { +#if NET8_0_OR_GREATER + ObjectDisposedException.ThrowIf(_disposed, this); +#else if (_disposed) { throw new ObjectDisposedException(nameof(CircuitStateController)); } +#endif } private void CloseCircuit_NeedsLock(Outcome outcome, bool manual, ResilienceContext context, out Task? scheduledTask) diff --git a/src/Polly.Core/CircuitBreaker/Controller/ScheduledTaskExecutor.cs b/src/Polly.Core/CircuitBreaker/Controller/ScheduledTaskExecutor.cs index b3b7cc530ef..7bc3f6f6d5f 100644 --- a/src/Polly.Core/CircuitBreaker/Controller/ScheduledTaskExecutor.cs +++ b/src/Polly.Core/CircuitBreaker/Controller/ScheduledTaskExecutor.cs @@ -17,10 +17,14 @@ internal sealed class ScheduledTaskExecutor : IDisposable public void ScheduleTask(Func taskFactory, ResilienceContext context, out Task task) { +#if NET8_0_OR_GREATER + ObjectDisposedException.ThrowIf(_disposed, this); +#else if (_disposed) { throw new ObjectDisposedException(nameof(ScheduledTaskExecutor)); } +#endif var source = new TaskCompletionSource(); task = source.Task; diff --git a/src/Polly.Core/CompatibilitySuppressions.xml b/src/Polly.Core/CompatibilitySuppressions.xml index a5bb6e45765..ac7575c1ecd 100644 --- a/src/Polly.Core/CompatibilitySuppressions.xml +++ b/src/Polly.Core/CompatibilitySuppressions.xml @@ -7,12 +7,6 @@ lib/netstandard2.0/Polly.Core.dll lib/net6.0/Polly.Core.dll - - CP0002 - M:Polly.CircuitBreaker.BrokenCircuitException`1.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext) - lib/netstandard2.0/Polly.Core.dll - lib/net6.0/Polly.Core.dll - CP0002 M:Polly.CircuitBreaker.IsolatedCircuitException.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext) @@ -25,10 +19,4 @@ lib/netstandard2.0/Polly.Core.dll lib/net6.0/Polly.Core.dll - - CP0002 - M:Polly.Timeout.TimeoutRejectedException.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext) - lib/netstandard2.0/Polly.Core.dll - lib/net6.0/Polly.Core.dll - \ No newline at end of file diff --git a/src/Polly.Core/Hedging/Controller/HedgingExecutionContext.cs b/src/Polly.Core/Hedging/Controller/HedgingExecutionContext.cs index 157ec750c9c..f1c580fe41d 100644 --- a/src/Polly.Core/Hedging/Controller/HedgingExecutionContext.cs +++ b/src/Polly.Core/Hedging/Controller/HedgingExecutionContext.cs @@ -10,8 +10,8 @@ internal sealed class HedgingExecutionContext : IAsyncDisposable { public readonly record struct ExecutionInfo(TaskExecution? Execution, bool Loaded, Outcome? Outcome); - private readonly List> _tasks = new(); - private readonly List> _executingTasks = new(); + private readonly List> _tasks = []; + private readonly List> _executingTasks = []; private readonly ObjectPool> _executionPool; private readonly TimeProvider _timeProvider; private readonly int _maxAttempts; @@ -122,7 +122,11 @@ public async ValueTask DisposeAsync() using var delayTaskCancellation = CancellationTokenSource.CreateLinkedTokenSource(PrimaryContext!.CancellationToken); +#if NET8_0_OR_GREATER + var delayTask = Task.Delay(hedgingDelay, _timeProvider, delayTaskCancellation.Token); +#else var delayTask = _timeProvider.Delay(hedgingDelay, delayTaskCancellation.Token); +#endif Task whenAnyHedgedTask = WaitForTaskCompetitionAsync(); var completedTask = await Task.WhenAny(whenAnyHedgedTask, delayTask).ConfigureAwait(ContinueOnCapturedContext); @@ -133,7 +137,11 @@ public async ValueTask DisposeAsync() // cancel the ongoing delay task // Stryker disable once boolean : no means to test this +#if NET8_0_OR_GREATER + await delayTaskCancellation.CancelAsync().ConfigureAwait(ContinueOnCapturedContext); +#else delayTaskCancellation.Cancel(throwOnFirstException: false); +#endif await whenAnyHedgedTask.ConfigureAwait(ContinueOnCapturedContext); diff --git a/src/Polly.Core/Polly.Core.csproj b/src/Polly.Core/Polly.Core.csproj index 6476f89a6eb..bb55baca269 100644 --- a/src/Polly.Core/Polly.Core.csproj +++ b/src/Polly.Core/Polly.Core.csproj @@ -1,7 +1,7 @@  - net6.0;netstandard2.0;net472;net462 + net8.0;net6.0;netstandard2.0;net472;net462 Polly.Core Polly enable @@ -29,6 +29,7 @@ + diff --git a/src/Polly.Core/PredicateBuilder.TResult.cs b/src/Polly.Core/PredicateBuilder.TResult.cs index e8f94007911..a0f05e486a3 100644 --- a/src/Polly.Core/PredicateBuilder.TResult.cs +++ b/src/Polly.Core/PredicateBuilder.TResult.cs @@ -6,7 +6,7 @@ namespace Polly; /// The type of the result. public partial class PredicateBuilder { - private readonly List>> _predicates = new(); + private readonly List>> _predicates = []; /// /// Adds a predicate for handling exceptions of the specified type. diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index a84ad565317..4045c01b593 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -7,3 +7,6 @@ Polly.CircuitBreaker.BreakDurationGeneratorArguments.FailureCount.get -> int Polly.CircuitBreaker.BreakDurationGeneratorArguments.FailureRate.get -> double Polly.CircuitBreaker.CircuitBreakerStrategyOptions.BreakDurationGenerator.get -> System.Func>? Polly.CircuitBreaker.CircuitBreakerStrategyOptions.BreakDurationGenerator.set -> void +Polly.ResiliencePipelineBuilderBase.TimeProvider.get -> System.TimeProvider? +Polly.ResiliencePipelineBuilderBase.TimeProvider.set -> void +Polly.StrategyBuilderContext.TimeProvider.get -> System.TimeProvider! diff --git a/src/Polly.Core/Registry/ConfigureBuilderContext.cs b/src/Polly.Core/Registry/ConfigureBuilderContext.cs index 1cf19ca6f56..56c8c483679 100644 --- a/src/Polly.Core/Registry/ConfigureBuilderContext.cs +++ b/src/Polly.Core/Registry/ConfigureBuilderContext.cs @@ -29,9 +29,9 @@ internal ConfigureBuilderContext(TKey strategyKey, string builderName, string? b /// internal string? BuilderInstanceName { get; } - internal List ReloadTokens { get; } = new(); + internal List ReloadTokens { get; } = []; - internal List DisposeCallbacks { get; } = new(); + internal List DisposeCallbacks { get; } = []; /// /// Reloads the pipeline when is canceled. diff --git a/src/Polly.Core/Registry/RegistryPipelineComponentBuilder.cs b/src/Polly.Core/Registry/RegistryPipelineComponentBuilder.cs index 6a3c0a00757..10b7f937f8a 100644 --- a/src/Polly.Core/Registry/RegistryPipelineComponentBuilder.cs +++ b/src/Polly.Core/Registry/RegistryPipelineComponentBuilder.cs @@ -59,7 +59,7 @@ private Builder CreateBuilder() builder.InstanceName = _instanceName; _configure(builder, context); - var timeProvider = builder.TimeProvider; + var timeProvider = builder.TimeProviderInternal; var telemetry = new ResilienceStrategyTelemetry( new ResilienceTelemetrySource(builder.Name, builder.InstanceName, null), builder.TelemetryListener); diff --git a/src/Polly.Core/ResiliencePipelineBuilderBase.cs b/src/Polly.Core/ResiliencePipelineBuilderBase.cs index 2484ac72ac0..5317d7c735c 100644 --- a/src/Polly.Core/ResiliencePipelineBuilderBase.cs +++ b/src/Polly.Core/ResiliencePipelineBuilderBase.cs @@ -1,5 +1,4 @@ using System.ComponentModel; - using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using Polly.Telemetry; @@ -19,7 +18,7 @@ namespace Polly; public abstract class ResiliencePipelineBuilderBase #pragma warning restore S1694 // An abstract class should have both abstract and concrete methods { - private readonly List _entries = new(); + private readonly List _entries = []; private bool _used; private protected ResiliencePipelineBuilderBase() @@ -69,16 +68,12 @@ private protected ResiliencePipelineBuilderBase(ResiliencePipelineBuilderBase ot public ResilienceContextPool? ContextPool { get; set; } /// - /// Gets or sets a that is used by strategies that work with time. + /// Gets or sets a that is used by strategies that work with time. /// - /// - /// This property is internal until we switch to official System.TimeProvider. - /// /// - /// The default value is . + /// The default value is and unless set, will be used. /// - [Required] - internal TimeProvider TimeProvider { get; set; } = TimeProvider.System; + public TimeProvider? TimeProvider { get; set; } /// /// Gets or sets the that is used by Polly to report resilience events. @@ -92,6 +87,8 @@ private protected ResiliencePipelineBuilderBase(ResiliencePipelineBuilderBase ot [EditorBrowsable(EditorBrowsableState.Never)] public TelemetryListener? TelemetryListener { get; set; } + internal TimeProvider TimeProviderInternal => TimeProvider ?? TimeProvider.System; + /// /// Gets the validator that is used for the validation. /// @@ -133,13 +130,13 @@ internal PipelineComponent BuildPipelineComponent() var source = new ResilienceTelemetrySource(Name, InstanceName, null); - return PipelineComponentFactory.CreateComposite(components, new ResilienceStrategyTelemetry(source, TelemetryListener), TimeProvider); + return PipelineComponentFactory.CreateComposite(components, new ResilienceStrategyTelemetry(source, TelemetryListener), TimeProviderInternal); } private PipelineComponent CreateComponent(Entry entry) { var source = new ResilienceTelemetrySource(Name, InstanceName, entry.Options.Name); - var context = new StrategyBuilderContext(new ResilienceStrategyTelemetry(source, TelemetryListener), TimeProvider); + var context = new StrategyBuilderContext(new ResilienceStrategyTelemetry(source, TelemetryListener), TimeProviderInternal); var strategy = entry.Factory(context); strategy.Options = entry.Options; diff --git a/src/Polly.Core/StrategyBuilderContext.cs b/src/Polly.Core/StrategyBuilderContext.cs index db1b3686088..5da3b183240 100644 --- a/src/Polly.Core/StrategyBuilderContext.cs +++ b/src/Polly.Core/StrategyBuilderContext.cs @@ -21,5 +21,5 @@ internal StrategyBuilderContext(ResilienceStrategyTelemetry telemetry, TimeProvi /// /// Gets the used by this strategy. /// - internal TimeProvider TimeProvider { get; } + public TimeProvider TimeProvider { get; } } diff --git a/src/Polly.Core/ToBeRemoved/TimeProvider.cs b/src/Polly.Core/ToBeRemoved/TimeProvider.cs deleted file mode 100644 index b6e644df1dc..00000000000 --- a/src/Polly.Core/ToBeRemoved/TimeProvider.cs +++ /dev/null @@ -1,456 +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.Diagnostics.CodeAnalysis; - -#pragma warning disable - -namespace System.Threading -{ - internal interface ITimer : IDisposable, IAsyncDisposable - { - bool Change(TimeSpan dueTime, TimeSpan period); - } -} - -namespace System -{ - // Temporary, will be removed - // Copied from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/TimeProvider.cs and trimmed some fat which is not relevant for internal stuff - - [ExcludeFromCodeCoverage] - internal abstract class TimeProvider - { - public static TimeProvider System { get; } = new SystemTimeProvider(); - - protected TimeProvider() - { - } - - public virtual DateTimeOffset GetUtcNow() => DateTimeOffset.UtcNow; - - private static readonly long MinDateTicks = DateTime.MinValue.Ticks; - private static readonly long MaxDateTicks = DateTime.MaxValue.Ticks; - - public DateTimeOffset GetLocalNow() - { - DateTimeOffset utcDateTime = GetUtcNow(); - TimeZoneInfo zoneInfo = LocalTimeZone; - if (zoneInfo is null) - { - throw new InvalidOperationException(); - } - - TimeSpan offset = zoneInfo.GetUtcOffset(utcDateTime); - - long localTicks = utcDateTime.Ticks + offset.Ticks; - if ((ulong)localTicks > (ulong)MaxDateTicks) - { - localTicks = localTicks < MinDateTicks ? MinDateTicks : MaxDateTicks; - } - - return new DateTimeOffset(localTicks, offset); - } - - public virtual TimeZoneInfo LocalTimeZone => TimeZoneInfo.Local; - - public virtual long TimestampFrequency => Stopwatch.Frequency; - - public virtual long GetTimestamp() => Stopwatch.GetTimestamp(); - - public TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) - { - long timestampFrequency = TimestampFrequency; - if (timestampFrequency <= 0) - { - throw new InvalidOperationException(); - } - - return new TimeSpan((long)((endingTimestamp - startingTimestamp) * ((double)TimeSpan.TicksPerSecond / timestampFrequency))); - } - - public TimeSpan GetElapsedTime(long startingTimestamp) => GetElapsedTime(startingTimestamp, GetTimestamp()); - - public virtual ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period) - { - if (callback is null) - { - throw new ArgumentNullException(nameof(callback)); - } - - return new SystemTimeProviderTimer(dueTime, period, callback, state); - } - - [ExcludeFromCodeCoverage] - private sealed class SystemTimeProviderTimer : ITimer - { - private readonly Timer _timer; - - public SystemTimeProviderTimer(TimeSpan dueTime, TimeSpan period, TimerCallback callback, object? state) - { - (uint duration, uint periodTime) = CheckAndGetValues(dueTime, period); - - // We need to ensure the timer roots itself. Timer created with a duration and period argument - // only roots the state object, so to root the timer we need the state object to reference the - // timer recursively. - var timerState = new TimerState(callback, state); - timerState.Timer = _timer = new Timer(static s => - { - TimerState ts = (TimerState)s!; - ts.Callback(ts.State); - }, timerState, duration, periodTime); - } - - private sealed class TimerState - { - public TimerState(TimerCallback callback, object? state) - { - Callback = callback; - State = state; - } - - public TimerCallback Callback { get; } - - public object? State { get; } - - public Timer? Timer { get; set; } - } - - public bool Change(TimeSpan dueTime, TimeSpan period) - { - (uint duration, uint periodTime) = CheckAndGetValues(dueTime, period); - try - { - return _timer.Change(duration, periodTime); - } - catch (ObjectDisposedException) - { - return false; - } - } - - public void Dispose() => _timer.Dispose(); - - public ValueTask DisposeAsync() - { - _timer.Dispose(); - return default; - } - - private static (uint duration, uint periodTime) CheckAndGetValues(TimeSpan dueTime, TimeSpan periodTime) - { - long dueTm = (long)dueTime.TotalMilliseconds; - long periodTm = (long)periodTime.TotalMilliseconds; - - const uint MaxSupportedTimeout = 0xfffffffe; - - if (dueTm < -1) - { - throw new ArgumentOutOfRangeException(nameof(dueTime)); - } - - if (dueTm > MaxSupportedTimeout) - { - throw new ArgumentOutOfRangeException(nameof(dueTime)); - } - - if (periodTm < -1) - { - throw new ArgumentOutOfRangeException(nameof(periodTm)); - } - - if (periodTm > MaxSupportedTimeout) - { - throw new ArgumentOutOfRangeException(nameof(periodTm)); - } - - return ((uint)dueTm, (uint)periodTm); - } - } - - [ExcludeFromCodeCoverage] - private sealed class SystemTimeProvider : TimeProvider - { - internal SystemTimeProvider() - { - } - } - } -} - -namespace System.Threading.Tasks -{ - /// - /// Provide extensions methods for operations with . - /// - /// - /// The Microsoft.Bcl.TimeProvider library interfaces are intended solely for use in building against pre-.NET 8 surface area. - /// If your code is being built against .NET 8 or higher, then this library should not be utilized. - /// - [ExcludeFromCodeCoverage] - internal static class TimeProviderTaskExtensions - { - private sealed class DelayState : TaskCompletionSource - { - public DelayState(CancellationToken cancellationToken) - : base(TaskCreationOptions.RunContinuationsAsynchronously) => CancellationToken = cancellationToken; - - public ITimer? Timer { get; set; } - public CancellationToken CancellationToken { get; } - public CancellationTokenRegistration Registration { get; set; } - } - - private sealed class WaitAsyncState : TaskCompletionSource - { - public WaitAsyncState(CancellationToken cancellationToken) - : base(TaskCreationOptions.RunContinuationsAsynchronously) => CancellationToken = cancellationToken; - - public readonly CancellationTokenSource ContinuationCancellation = new(); - public CancellationToken CancellationToken { get; } - public CancellationTokenRegistration Registration; - public ITimer? Timer; - } - - /// Creates a task that completes after a specified time interval. - /// The with which to interpret . - /// The to wait before completing the returned task, or to wait indefinitely. - /// A cancellation token to observe while waiting for the task to complete. - /// A task that represents the time delay. - /// The argument is null. - /// represents a negative time interval other than . - public static Task Delay(this TimeProvider timeProvider, TimeSpan delay, CancellationToken cancellationToken = default) - { - if (timeProvider == TimeProvider.System) - { - return Task.Delay(delay, cancellationToken); - } - - if (timeProvider is null) - { - throw new ArgumentNullException(nameof(timeProvider)); - } - - if (delay != Timeout.InfiniteTimeSpan && delay < TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(delay)); - } - - if (delay == TimeSpan.Zero) - { - return Task.CompletedTask; - } - - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - DelayState state = new(cancellationToken); - - state.Timer = timeProvider.CreateTimer(static delayState => - { - DelayState s = (DelayState)delayState!; - s.TrySetResult(true); - s.Registration.Dispose(); - s.Timer?.Dispose(); - }, state, delay, Timeout.InfiniteTimeSpan); - - state.Registration = cancellationToken.Register(static delayState => - { - DelayState s = (DelayState)delayState!; - s.TrySetCanceled(s.CancellationToken); - s.Registration.Dispose(); - s.Timer?.Dispose(); - }, state); - - // There are race conditions where the timer fires after we have attached the cancellation callback but before the - // registration is stored in state.Registration, or where cancellation is requested prior to the registration being - // stored into state.Registration, or where the timer could fire after it's been created but before it's been stored - // in state.Timer. In such cases, the cancellation registration and/or the Timer might be stored into state after the - // callbacks and thus left undisposed. So, we do a subsequent check here. If the task isn't completed by this point, - // then the callbacks won't have called TrySetResult (the callbacks invoke TrySetResult before disposing of the fields), - // in which case it will see both the timer and registration set and be able to Dispose them. If the task is completed - // by this point, then this is guaranteed to see s.Timer as non-null because it was deterministically set above. - if (state.Task.IsCompleted) - { - state.Registration.Dispose(); - state.Timer.Dispose(); - } - - return state.Task; - } - - /// - /// Gets a that will complete when this completes, - /// when the specified timeout expires, or when the specified has cancellation requested. - /// - /// The task for which to wait on until completion. - /// The timeout after which the should be faulted with a if it hasn't otherwise completed. - /// The with which to interpret . - /// The to monitor for a cancellation request. - /// The representing the asynchronous wait. It may or may not be the same instance as the current instance. - /// The argument is null. - /// The argument is null. - /// represents a negative time interval other than . - public static Task WaitAsync(this Task task, TimeSpan timeout, TimeProvider timeProvider, CancellationToken cancellationToken = default) - { - if (task is null) - { - throw new ArgumentNullException(nameof(task)); - } - - if (timeout != Timeout.InfiniteTimeSpan && timeout < TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(timeout)); - } - - if (timeProvider is null) - { - throw new ArgumentNullException(nameof(timeProvider)); - } - - if (task.IsCompleted) - { - return task; - } - - if (timeout == Timeout.InfiniteTimeSpan && !cancellationToken.CanBeCanceled) - { - return task; - } - - if (timeout == TimeSpan.Zero) - { - Task.FromException(new TimeoutException()); - } - - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - WaitAsyncState state = new(cancellationToken); - - state.Timer = timeProvider.CreateTimer(static s => - { - var state = (WaitAsyncState)s!; - - state.TrySetException(new TimeoutException()); - - state.Registration.Dispose(); - state.Timer?.Dispose(); - state.ContinuationCancellation.Cancel(); - }, state, timeout, Timeout.InfiniteTimeSpan); - - _ = task.ContinueWith(static (t, s) => - { - var state = (WaitAsyncState)s!; - - if (t.IsFaulted) - { - state.TrySetException(t.Exception!.InnerExceptions); - } - else if (t.IsCanceled) - { - state.TrySetCanceled(); - } - else - { - state.TrySetResult(true); - } - - state.Registration.Dispose(); - state.Timer?.Dispose(); - }, state, state.ContinuationCancellation.Token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - - state.Registration = cancellationToken.Register(static s => - { - var state = (WaitAsyncState)s!; - - state.TrySetCanceled(state.CancellationToken); - - state.Timer?.Dispose(); - state.ContinuationCancellation.Cancel(); - }, state); - - // See explanation in Delay for this final check - if (state.Task.IsCompleted) - { - state.Registration.Dispose(); - state.Timer.Dispose(); - } - - return state.Task; - } - - /// - /// Gets a that will complete when this completes, - /// when the specified timeout expires, or when the specified has cancellation requested. - /// - /// The task for which to wait on until completion. - /// The timeout after which the should be faulted with a if it hasn't otherwise completed. - /// The with which to interpret . - /// The to monitor for a cancellation request. - /// The representing the asynchronous wait. It may or may not be the same instance as the current instance. - /// The argument is null. - /// The argument is null. - /// represents a negative time interval other than . - public static async Task WaitAsync(this Task task, TimeSpan timeout, TimeProvider timeProvider, CancellationToken cancellationToken = default) - { - await ((Task)task).WaitAsync(timeout, timeProvider, cancellationToken).ConfigureAwait(false); - return task.Result; - } - - /// Initializes a new instance of the class that will be canceled after the specified . - /// The with which to interpret the . - /// The time interval to wait before canceling this . - /// The is negative and not equal to - /// or greater than maximum allowed timer duration. - /// that will be canceled after the specified . - /// - /// - /// The countdown for the delay starts during the call to the constructor. When the delay expires, - /// the constructed is canceled if it has - /// not been canceled already. - /// - /// - /// If running on .NET versions earlier than .NET 8.0, there is a constraint when invoking on the resultant object. - /// This action will not terminate the initial timer indicated by . However, this restriction does not apply on .NET 8.0 and later versions. - /// - /// - public static CancellationTokenSource CreateCancellationTokenSource(this TimeProvider timeProvider, TimeSpan delay) - { - if (timeProvider is null) - { - throw new ArgumentNullException(nameof(timeProvider)); - } - - if (delay != Timeout.InfiniteTimeSpan && delay < TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException(nameof(delay)); - } - - if (timeProvider == TimeProvider.System) - { - return new CancellationTokenSource(delay); - } - - var cts = new CancellationTokenSource(); - - ITimer timer = timeProvider.CreateTimer(static s => - { - try - { - ((CancellationTokenSource)s!).Cancel(); - } - catch (ObjectDisposedException) - { - // ok - } - }, cts, delay, Timeout.InfiniteTimeSpan); - - cts.Token.Register(static t => ((ITimer)t!).Dispose(), timer); - return cts; - } - } -} diff --git a/src/Polly.Core/Utils/CancellationTokenSourcePool.Disposable.cs b/src/Polly.Core/Utils/CancellationTokenSourcePool.Disposable.cs index 0510fa5cb5b..e9754965f62 100644 --- a/src/Polly.Core/Utils/CancellationTokenSourcePool.Disposable.cs +++ b/src/Polly.Core/Utils/CancellationTokenSourcePool.Disposable.cs @@ -2,6 +2,7 @@ namespace Polly.Utils; internal abstract partial class CancellationTokenSourcePool { +#if !NET8_0_OR_GREATER private sealed class DisposableCancellationTokenSourcePool : CancellationTokenSourcePool { private readonly TimeProvider _timeProvider; @@ -20,4 +21,5 @@ protected override CancellationTokenSource GetCore(TimeSpan delay) public override void Return(CancellationTokenSource source) => source.Dispose(); } +#endif } diff --git a/src/Polly.Core/Utils/CancellationTokenSourcePool.Pooled.cs b/src/Polly.Core/Utils/CancellationTokenSourcePool.Pooled.cs index 93b7acdfc11..4791a8fcc9f 100644 --- a/src/Polly.Core/Utils/CancellationTokenSourcePool.Pooled.cs +++ b/src/Polly.Core/Utils/CancellationTokenSourcePool.Pooled.cs @@ -5,11 +5,20 @@ internal abstract partial class CancellationTokenSourcePool #if NET6_0_OR_GREATER private sealed class PooledCancellationTokenSourcePool : CancellationTokenSourcePool { - public static readonly PooledCancellationTokenSourcePool SystemInstance = new(); + public static readonly PooledCancellationTokenSourcePool SystemInstance = new(TimeProvider.System); private readonly ObjectPool _pool; - public PooledCancellationTokenSourcePool() => _pool = new(static () => new CancellationTokenSource(), static cts => true); + public PooledCancellationTokenSourcePool(TimeProvider timeProvider) => _pool = new( + () => + { +#if NET8_0_OR_GREATER + return new CancellationTokenSource(System.Threading.Timeout.InfiniteTimeSpan, timeProvider); +#else + return new CancellationTokenSource(); +#endif + }, + static cts => true); protected override CancellationTokenSource GetCore(TimeSpan delay) { diff --git a/src/Polly.Core/Utils/CancellationTokenSourcePool.cs b/src/Polly.Core/Utils/CancellationTokenSourcePool.cs index cabb1f291d7..b11767f0b07 100644 --- a/src/Polly.Core/Utils/CancellationTokenSourcePool.cs +++ b/src/Polly.Core/Utils/CancellationTokenSourcePool.cs @@ -6,7 +6,14 @@ internal abstract partial class CancellationTokenSourcePool { public static CancellationTokenSourcePool Create(TimeProvider timeProvider) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER + if (timeProvider == TimeProvider.System) + { + return PooledCancellationTokenSourcePool.SystemInstance; + } + + return new PooledCancellationTokenSourcePool(timeProvider); +#elif NET6_0_OR_GREATER if (timeProvider == TimeProvider.System) { return PooledCancellationTokenSourcePool.SystemInstance; diff --git a/src/Polly.Core/Utils/Pipeline/ExecutionTrackingComponent.cs b/src/Polly.Core/Utils/Pipeline/ExecutionTrackingComponent.cs index 5a03e7f94fc..0aee198ec21 100644 --- a/src/Polly.Core/Utils/Pipeline/ExecutionTrackingComponent.cs +++ b/src/Polly.Core/Utils/Pipeline/ExecutionTrackingComponent.cs @@ -45,7 +45,11 @@ public override async ValueTask DisposeAsync() // so we will do "dummy" retries until there are no more executions. while (HasPendingExecutions) { +#if NET8_0_OR_GREATER + await Task.Delay(SleepDelay, _timeProvider).ConfigureAwait(false); +#else await _timeProvider.Delay(SleepDelay).ConfigureAwait(false); +#endif // stryker disable once equality : no means to test this if (_timeProvider.GetElapsedTime(start) > Timeout) diff --git a/src/Polly.Core/Utils/Pipeline/PipelineComponentFactory.cs b/src/Polly.Core/Utils/Pipeline/PipelineComponentFactory.cs index a671dc7e0f1..093858ae7b2 100644 --- a/src/Polly.Core/Utils/Pipeline/PipelineComponentFactory.cs +++ b/src/Polly.Core/Utils/Pipeline/PipelineComponentFactory.cs @@ -14,12 +14,25 @@ internal static class PipelineComponentFactory public static PipelineComponent WithDisposableCallbacks(PipelineComponent component, IEnumerable callbacks) { +#pragma warning disable CA1851 // Possible multiple enumerations of 'IEnumerable' collection +#if NET6_0_OR_GREATER + if (callbacks.TryGetNonEnumeratedCount(out var count)) + { + if (count == 0) + { + return component; + } + } + else if (!callbacks.Any()) +#else if (!callbacks.Any()) +#endif { return component; } return new ComponentWithDisposeCallbacks(component, callbacks.ToList()); +#pragma warning restore CA1851 // Possible multiple enumerations of 'IEnumerable' collection } public static PipelineComponent WithExecutionTracking(PipelineComponent component, TimeProvider timeProvider) => new ExecutionTrackingComponent(component, timeProvider); diff --git a/src/Polly.Core/Utils/Pipeline/ReloadableComponent.cs b/src/Polly.Core/Utils/Pipeline/ReloadableComponent.cs index f8a216e37dc..82b3caac674 100644 --- a/src/Polly.Core/Utils/Pipeline/ReloadableComponent.cs +++ b/src/Polly.Core/Utils/Pipeline/ReloadableComponent.cs @@ -52,7 +52,7 @@ private void TryRegisterOnReload() return; } - _tokenSource = CancellationTokenSource.CreateLinkedTokenSource(_reloadTokens.ToArray()); + _tokenSource = CancellationTokenSource.CreateLinkedTokenSource([.. _reloadTokens]); _registration = _tokenSource.Token.Register(() => { var context = ResilienceContextPool.Shared.Get().Initialize(isSynchronous: true); @@ -65,7 +65,7 @@ private void TryRegisterOnReload() } catch (Exception e) { - _reloadTokens = new List(); + _reloadTokens = []; _telemetry.Report(new(ResilienceEventSeverity.Error, ReloadFailedEvent), context, Outcome.FromException(e), new ReloadFailedArguments(e)); ResilienceContextPool.Shared.Return(context); } diff --git a/src/Polly.Core/Utils/TimeProviderExtensions.cs b/src/Polly.Core/Utils/TimeProviderExtensions.cs index 7e54f24583b..8c7ecf66314 100644 --- a/src/Polly.Core/Utils/TimeProviderExtensions.cs +++ b/src/Polly.Core/Utils/TimeProviderExtensions.cs @@ -35,15 +35,20 @@ public static Task DelayAsync(this TimeProvider timeProvider, TimeSpan delay, Re // the use of Thread.Sleep() here because it is not cancellable and to // simplify the code. Sync-over-async is not a concern here because it // only applies in the case of a resilience event and not on the hot path. - - // re the Sync-over-async I guess that would be a concern when using the LatencyChaosStrategy - // since that's running on the hot path, thoughts? +#if NET8_0_OR_GREATER + Task.Delay(delay, timeProvider, context.CancellationToken).GetAwaiter().GetResult(); +#else timeProvider.Delay(delay, context.CancellationToken).GetAwaiter().GetResult(); +#endif #pragma warning restore CA1849 return Task.CompletedTask; } +#if NET8_0_OR_GREATER + return Task.Delay(delay, timeProvider, context.CancellationToken); +#else return timeProvider.Delay(delay, context.CancellationToken); +#endif } } diff --git a/src/Polly.Extensions/DependencyInjection/ConfigureResiliencePipelineRegistryOptions.cs b/src/Polly.Extensions/DependencyInjection/ConfigureResiliencePipelineRegistryOptions.cs index 83ecd3ffd0e..639946a6063 100644 --- a/src/Polly.Extensions/DependencyInjection/ConfigureResiliencePipelineRegistryOptions.cs +++ b/src/Polly.Extensions/DependencyInjection/ConfigureResiliencePipelineRegistryOptions.cs @@ -5,5 +5,5 @@ namespace Polly.DependencyInjection; internal sealed class ConfigureResiliencePipelineRegistryOptions where TKey : notnull { - public List>> Actions { get; } = new(); + public List>> Actions { get; } = []; } diff --git a/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs b/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs index b6fdf2aa2ef..d345ec7ce41 100644 --- a/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs +++ b/src/Polly.Extensions/DependencyInjection/PollyServiceCollectionExtensions.cs @@ -245,6 +245,12 @@ private static void AddResiliencePipelineBuilder(this IServiceCollection service { var builder = new ResiliencePipelineBuilder(); builder.ConfigureTelemetry(serviceProvider.GetRequiredService>().Value); + + if (serviceProvider.GetService() is { } timeProvider) + { + builder.TimeProvider = timeProvider; + } + return builder; }); } diff --git a/src/Polly.Extensions/Polly.Extensions.csproj b/src/Polly.Extensions/Polly.Extensions.csproj index 429353154a5..172e3333756 100644 --- a/src/Polly.Extensions/Polly.Extensions.csproj +++ b/src/Polly.Extensions/Polly.Extensions.csproj @@ -1,6 +1,6 @@  - net6.0;netstandard2.0;net472;net462 + net8.0;net6.0;netstandard2.0;net472;net462 Polly.Extensions Polly enable @@ -20,7 +20,6 @@ - diff --git a/src/Polly.RateLimiting/Polly.RateLimiting.csproj b/src/Polly.RateLimiting/Polly.RateLimiting.csproj index 97be9f209d4..46e8f574290 100644 --- a/src/Polly.RateLimiting/Polly.RateLimiting.csproj +++ b/src/Polly.RateLimiting/Polly.RateLimiting.csproj @@ -1,6 +1,6 @@  - net6.0;netstandard2.0;net472;net462 + net8.0;net6.0;netstandard2.0;net472;net462 Polly.RateLimiting Polly.RateLimiting enable diff --git a/src/Polly.Testing/Polly.Testing.csproj b/src/Polly.Testing/Polly.Testing.csproj index d85ec8fba23..481df5fce1c 100644 --- a/src/Polly.Testing/Polly.Testing.csproj +++ b/src/Polly.Testing/Polly.Testing.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + net8.0;netstandard2.0 Polly.Testing Polly.Testing enable diff --git a/src/Polly/AsyncPolicy.ExecuteOverloads.cs b/src/Polly/AsyncPolicy.ExecuteOverloads.cs index 667b1d4cb4b..0d35960c437 100644 --- a/src/Polly/AsyncPolicy.ExecuteOverloads.cs +++ b/src/Polly/AsyncPolicy.ExecuteOverloads.cs @@ -10,7 +10,7 @@ public abstract partial class AsyncPolicy : PolicyBase, IAsyncPolicy /// The action to perform. [DebuggerStepThrough] public Task ExecuteAsync(Func action) => - ExecuteAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + ExecuteAsync((_, _) => action(), [], DefaultCancellationToken, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy. @@ -37,7 +37,7 @@ public Task ExecuteAsync(Func action, Context context) => /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. [DebuggerStepThrough] public Task ExecuteAsync(Func action, CancellationToken cancellationToken) => - ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + ExecuteAsync((_, ct) => action(ct), [], cancellationToken, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy. @@ -68,7 +68,7 @@ public Task ExecuteAsync(Func action, Context /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. [DebuggerStepThrough] public Task ExecuteAsync(Func action, CancellationToken cancellationToken, bool continueOnCapturedContext) => - ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, continueOnCapturedContext); + ExecuteAsync((_, ct) => action(ct), [], cancellationToken, continueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy. @@ -117,7 +117,7 @@ public async Task ExecuteAsync(Func action, Co /// The value returned by the action [DebuggerStepThrough] public Task ExecuteAsync(Func> action) => - ExecuteAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + ExecuteAsync((_, _) => action(), [], DefaultCancellationToken, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. @@ -149,7 +149,7 @@ public Task ExecuteAsync(Func> action, /// The value returned by the action [DebuggerStepThrough] public Task ExecuteAsync(Func> action, CancellationToken cancellationToken) => - ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + ExecuteAsync((_, ct) => action(ct), [], cancellationToken, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. @@ -185,7 +185,7 @@ public Task ExecuteAsync(FuncPlease use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. [DebuggerStepThrough] public Task ExecuteAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext) => - ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, continueOnCapturedContext); + ExecuteAsync((_, ct) => action(ct), [], cancellationToken, continueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. @@ -240,7 +240,7 @@ public async Task ExecuteAsync(FuncThe captured result [DebuggerStepThrough] public Task ExecuteAndCaptureAsync(Func action) => - ExecuteAndCaptureAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + ExecuteAndCaptureAsync((_, _) => action(), [], DefaultCancellationToken, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the captured result. @@ -270,7 +270,7 @@ public Task ExecuteAndCaptureAsync(Func action, Con /// A cancellation token which can be used to cancel the action. When a retry policy in use, also cancels any further retries. [DebuggerStepThrough] public Task ExecuteAndCaptureAsync(Func action, CancellationToken cancellationToken) => - ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + ExecuteAndCaptureAsync((_, ct) => action(ct), [], cancellationToken, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the captured result. @@ -303,7 +303,7 @@ public Task ExecuteAndCaptureAsync(FuncPlease use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. [DebuggerStepThrough] public Task ExecuteAndCaptureAsync(Func action, CancellationToken cancellationToken, bool continueOnCapturedContext) => - ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, continueOnCapturedContext); + ExecuteAndCaptureAsync((_, ct) => action(ct), [], cancellationToken, continueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the captured result. @@ -352,7 +352,7 @@ public async Task ExecuteAndCaptureAsync(FuncThe captured result [DebuggerStepThrough] public Task> ExecuteAndCaptureAsync(Func> action) => - ExecuteAndCaptureAsync((_, _) => action(), new Context(), DefaultCancellationToken, DefaultContinueOnCapturedContext); + ExecuteAndCaptureAsync((_, _) => action(), [], DefaultCancellationToken, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. @@ -386,7 +386,7 @@ public Task> ExecuteAndCaptureAsync(FuncThe captured result [DebuggerStepThrough] public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken) => - ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + ExecuteAndCaptureAsync((_, ct) => action(ct), [], cancellationToken, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. @@ -424,7 +424,7 @@ public Task> ExecuteAndCaptureAsync(FuncPlease use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. [DebuggerStepThrough] public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext) => - ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, continueOnCapturedContext); + ExecuteAndCaptureAsync((_, ct) => action(ct), [], cancellationToken, continueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. diff --git a/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs b/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs index e74d818070a..1daa607943e 100644 --- a/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs +++ b/src/Polly/AsyncPolicy.TResult.ExecuteOverloads.cs @@ -11,7 +11,7 @@ public abstract partial class AsyncPolicy : IAsyncPolicy /// The value returned by the action [DebuggerStepThrough] public Task ExecuteAsync(Func> action) => - ExecuteAsync((_, _) => action(), new Context(), CancellationToken.None, DefaultContinueOnCapturedContext); + ExecuteAsync((_, _) => action(), [], CancellationToken.None, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. @@ -41,7 +41,7 @@ public Task ExecuteAsync(Func> action, Context c /// The value returned by the action [DebuggerStepThrough] public Task ExecuteAsync(Func> action, CancellationToken cancellationToken) => - ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + ExecuteAsync((_, ct) => action(ct), [], cancellationToken, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. @@ -53,7 +53,7 @@ public Task ExecuteAsync(Func> action, /// Please use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. [DebuggerStepThrough] public Task ExecuteAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext) => - ExecuteAsync((_, ct) => action(ct), new Context(), cancellationToken, continueOnCapturedContext); + ExecuteAsync((_, ct) => action(ct), [], cancellationToken, continueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. @@ -127,7 +127,7 @@ public async Task ExecuteAsync(FuncThe captured result [DebuggerStepThrough] public Task> ExecuteAndCaptureAsync(Func> action) => - ExecuteAndCaptureAsync((_, _) => action(), new Context(), CancellationToken.None, DefaultContinueOnCapturedContext); + ExecuteAndCaptureAsync((_, _) => action(), [], CancellationToken.None, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. @@ -158,7 +158,7 @@ public Task> ExecuteAndCaptureAsync(FuncThe captured result [DebuggerStepThrough] public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken) => - ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, DefaultContinueOnCapturedContext); + ExecuteAndCaptureAsync((_, ct) => action(ct), [], cancellationToken, DefaultContinueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. @@ -170,7 +170,7 @@ public Task> ExecuteAndCaptureAsync(FuncPlease use asynchronous-defined policies when calling asynchronous ExecuteAsync (and similar) methods. [DebuggerStepThrough] public Task> ExecuteAndCaptureAsync(Func> action, CancellationToken cancellationToken, bool continueOnCapturedContext) => - ExecuteAndCaptureAsync((_, ct) => action(ct), new Context(), cancellationToken, continueOnCapturedContext); + ExecuteAndCaptureAsync((_, ct) => action(ct), [], cancellationToken, continueOnCapturedContext); /// /// Executes the specified asynchronous action within the policy and returns the result. diff --git a/src/Polly/CompatibilitySuppressions.xml b/src/Polly/CompatibilitySuppressions.xml index e76772a16fc..ff409018bff 100644 --- a/src/Polly/CompatibilitySuppressions.xml +++ b/src/Polly/CompatibilitySuppressions.xml @@ -1,132 +1,6 @@  - - CP0001 - T:Polly.CircuitBreaker.BrokenCircuitException - lib/net461/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.BrokenCircuitException`1 - lib/net461/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.CircuitState - lib/net461/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.IsolatedCircuitException - lib/net461/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.ExecutionRejectedException - lib/net461/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.Timeout.TimeoutRejectedException - lib/net461/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.BrokenCircuitException - lib/net472/Polly.dll - lib/net472/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.BrokenCircuitException`1 - lib/net472/Polly.dll - lib/net472/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.CircuitState - lib/net472/Polly.dll - lib/net472/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.IsolatedCircuitException - lib/net472/Polly.dll - lib/net472/Polly.dll - true - - - CP0001 - T:Polly.ExecutionRejectedException - lib/net472/Polly.dll - lib/net472/Polly.dll - true - - - CP0001 - T:Polly.Timeout.TimeoutRejectedException - lib/net472/Polly.dll - lib/net472/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.BrokenCircuitException - lib/netstandard2.0/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.BrokenCircuitException`1 - lib/netstandard2.0/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.CircuitState - lib/netstandard2.0/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.CircuitBreaker.IsolatedCircuitException - lib/netstandard2.0/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.ExecutionRejectedException - lib/netstandard2.0/Polly.dll - lib/netstandard2.0/Polly.dll - true - - - CP0001 - T:Polly.Timeout.TimeoutRejectedException - lib/netstandard2.0/Polly.dll - lib/netstandard2.0/Polly.dll - true - CP0002 M:Polly.Bulkhead.BulkheadRejectedException.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext) @@ -175,14 +49,4 @@ lib/netstandard2.0/Polly.dll lib/net6.0/Polly.dll - - CP0002 - M:Polly.Timeout.TimeoutRejectedException.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext) - lib/netstandard2.0/Polly.dll - lib/net6.0/Polly.dll - - - PKV006 - .NETStandard,Version=v1.1 - - \ No newline at end of file + diff --git a/src/Polly/Context.Dictionary.cs b/src/Polly/Context.Dictionary.cs index 8fd71070860..d0090ceba72 100644 --- a/src/Polly/Context.Dictionary.cs +++ b/src/Polly/Context.Dictionary.cs @@ -11,7 +11,7 @@ public partial class Context : IDictionary, IDictionary, IReadOn private Dictionary wrappedDictionary; - private Dictionary WrappedDictionary => wrappedDictionary ?? (wrappedDictionary = new Dictionary()); + private Dictionary WrappedDictionary => wrappedDictionary ?? (wrappedDictionary = []); /// /// Initializes a new instance of the class, with the specified and the supplied . diff --git a/src/Polly/ExceptionPredicates.cs b/src/Polly/ExceptionPredicates.cs index f8504e8e617..5bae4c9b15a 100644 --- a/src/Polly/ExceptionPredicates.cs +++ b/src/Polly/ExceptionPredicates.cs @@ -9,7 +9,7 @@ public class ExceptionPredicates internal void Add(ExceptionPredicate predicate) { - _predicates ??= new List(); // The ?? pattern here is sufficient; only a deliberately contrived example would lead to the same PolicyBuilder instance being used in a multi-threaded way to define policies simultaneously on multiple threads. + _predicates ??= []; // The ?? pattern here is sufficient; only a deliberately contrived example would lead to the same PolicyBuilder instance being used in a multi-threaded way to define policies simultaneously on multiple threads. _predicates.Add(predicate); } diff --git a/src/Polly/Policy.ExecuteOverloads.cs b/src/Polly/Policy.ExecuteOverloads.cs index 4d7e2e666aa..edfa5f41d4f 100644 --- a/src/Polly/Policy.ExecuteOverloads.cs +++ b/src/Polly/Policy.ExecuteOverloads.cs @@ -10,7 +10,7 @@ public abstract partial class Policy : ISyncPolicy /// The action to perform. [DebuggerStepThrough] public void Execute(Action action) => - Execute((_, _) => action(), new Context(), DefaultCancellationToken); + Execute((_, _) => action(), [], DefaultCancellationToken); /// /// Executes the specified action within the policy. @@ -37,7 +37,7 @@ public void Execute(Action action, Context context) => /// [DebuggerStepThrough] public void Execute(Action action, CancellationToken cancellationToken) => - Execute((_, ct) => action(ct), new Context(), cancellationToken); + Execute((_, ct) => action(ct), [], cancellationToken); /// /// Executes the specified action within the policy. @@ -83,7 +83,7 @@ public void Execute(Action action, Context context, /// The value returned by the action [DebuggerStepThrough] public TResult Execute(Func action) => - Execute((_, _) => action(), new Context(), DefaultCancellationToken); + Execute((_, _) => action(), [], DefaultCancellationToken); /// /// Executes the specified action within the policy and returns the result. @@ -124,7 +124,7 @@ public TResult Execute(Func action, Context context) /// The value returned by the action [DebuggerStepThrough] public TResult Execute(Func action, CancellationToken cancellationToken) => - Execute((_, ct) => action(ct), new Context(), cancellationToken); + Execute((_, ct) => action(ct), [], cancellationToken); /// /// Executes the specified action within the policy and returns the result. @@ -177,7 +177,7 @@ public TResult Execute(Func action /// The captured result [DebuggerStepThrough] public PolicyResult ExecuteAndCapture(Action action) => - ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); + ExecuteAndCapture((_, _) => action(), [], DefaultCancellationToken); /// /// Executes the specified action within the policy and returns the captured result. @@ -208,7 +208,7 @@ public PolicyResult ExecuteAndCapture(Action action, Context context) = /// The captured result [DebuggerStepThrough] public PolicyResult ExecuteAndCapture(Action action, CancellationToken cancellationToken) => - ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); + ExecuteAndCapture((_, ct) => action(ct), [], cancellationToken); /// /// Executes the specified action within the policy and returns the captured result. @@ -254,7 +254,7 @@ public PolicyResult ExecuteAndCapture(Action action, /// The captured result [DebuggerStepThrough] public PolicyResult ExecuteAndCapture(Func action) => - ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); + ExecuteAndCapture((_, _) => action(), [], DefaultCancellationToken); /// /// Executes the specified action within the policy and returns the captured result. @@ -286,7 +286,7 @@ public PolicyResult ExecuteAndCapture(Func a /// The cancellation token. /// The captured result public PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken) => - ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); + ExecuteAndCapture((_, ct) => action(ct), [], cancellationToken); /// /// Executes the specified action within the policy and returns the captured result. diff --git a/src/Polly/Policy.TResult.ExecuteOverloads.cs b/src/Polly/Policy.TResult.ExecuteOverloads.cs index a6f13261aa0..0ee2110329c 100644 --- a/src/Polly/Policy.TResult.ExecuteOverloads.cs +++ b/src/Polly/Policy.TResult.ExecuteOverloads.cs @@ -11,7 +11,7 @@ public abstract partial class Policy : ISyncPolicy /// The value returned by the action [DebuggerStepThrough] public TResult Execute(Func action) => - Execute((_, _) => action(), new Context(), DefaultCancellationToken); + Execute((_, _) => action(), [], DefaultCancellationToken); /// /// Executes the specified action within the policy and returns the result. @@ -49,7 +49,7 @@ public TResult Execute(Func action, Context context) => /// The value returned by the action [DebuggerStepThrough] public TResult Execute(Func action, CancellationToken cancellationToken) => - Execute((_, ct) => action(ct), new Context(), cancellationToken); + Execute((_, ct) => action(ct), [], cancellationToken); /// /// Executes the specified action within the policy and returns the result. @@ -98,7 +98,7 @@ public TResult Execute(Func action, Context /// The captured result [DebuggerStepThrough] public PolicyResult ExecuteAndCapture(Func action) => - ExecuteAndCapture((_, _) => action(), new Context(), DefaultCancellationToken); + ExecuteAndCapture((_, _) => action(), [], DefaultCancellationToken); /// /// Executes the specified action within the policy and returns the captured result. @@ -130,7 +130,7 @@ public PolicyResult ExecuteAndCapture(Func action, Co /// The captured result [DebuggerStepThrough] public PolicyResult ExecuteAndCapture(Func action, CancellationToken cancellationToken) => - ExecuteAndCapture((_, ct) => action(ct), new Context(), cancellationToken); + ExecuteAndCapture((_, ct) => action(ct), [], cancellationToken); /// /// Executes the specified action within the policy and returns the captured result. diff --git a/src/Polly/ResultPredicates.cs b/src/Polly/ResultPredicates.cs index 72b43db3a5c..6be1aa99fe9 100644 --- a/src/Polly/ResultPredicates.cs +++ b/src/Polly/ResultPredicates.cs @@ -9,7 +9,7 @@ public class ResultPredicates internal void Add(ResultPredicate predicate) { - _predicates ??= new List>(); // The ?? pattern here is sufficient; only a deliberately contrived example would lead to the same PolicyBuilder instance being used in a multi-threaded way to define policies simultaneously on multiple threads. + _predicates ??= []; // The ?? pattern here is sufficient; only a deliberately contrived example would lead to the same PolicyBuilder instance being used in a multi-threaded way to define policies simultaneously on multiple threads. _predicates.Add(predicate); } diff --git a/test/Polly.AotTest/Polly.AotTest.csproj b/test/Polly.AotTest/Polly.AotTest.csproj index 0f8b6725774..9ede9228685 100644 --- a/test/Polly.AotTest/Polly.AotTest.csproj +++ b/test/Polly.AotTest/Polly.AotTest.csproj @@ -2,10 +2,13 @@ enable Exe - true true + true net7.0 + + true + diff --git a/test/Polly.Core.Tests/CircuitBreaker/CircuitBreakerResiliencePipelineBuilderTests.cs b/test/Polly.Core.Tests/CircuitBreaker/CircuitBreakerResiliencePipelineBuilderTests.cs index a081a36655b..51c75f9e424 100644 --- a/test/Polly.Core.Tests/CircuitBreaker/CircuitBreakerResiliencePipelineBuilderTests.cs +++ b/test/Polly.Core.Tests/CircuitBreaker/CircuitBreakerResiliencePipelineBuilderTests.cs @@ -7,6 +7,7 @@ namespace Polly.Core.Tests.CircuitBreaker; public class CircuitBreakerResiliencePipelineBuilderTests { +#pragma warning disable IDE0028 public static TheoryData> ConfigureData = new() { builder => builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions @@ -22,6 +23,7 @@ public class CircuitBreakerResiliencePipelineBuilderTests ShouldHandle = _ => PredicateResult.True() }), }; +#pragma warning restore IDE0028 [MemberData(nameof(ConfigureData))] [Theory] diff --git a/test/Polly.Core.Tests/Fallback/FallbackResiliencePipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Fallback/FallbackResiliencePipelineBuilderExtensionsTests.cs index 6dbe198712d..ccb10ef02b8 100644 --- a/test/Polly.Core.Tests/Fallback/FallbackResiliencePipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Fallback/FallbackResiliencePipelineBuilderExtensionsTests.cs @@ -6,6 +6,7 @@ namespace Polly.Core.Tests.Fallback; public class FallbackResiliencePipelineBuilderExtensionsTests { +#pragma warning disable IDE0028 public static readonly TheoryData>> FallbackOverloadsGeneric = new() { builder => @@ -17,6 +18,7 @@ public class FallbackResiliencePipelineBuilderExtensionsTests }); } }; +#pragma warning restore IDE0028 [MemberData(nameof(FallbackOverloadsGeneric))] [Theory] diff --git a/test/Polly.Core.Tests/Fallback/FallbackResilienceStrategyTests.cs b/test/Polly.Core.Tests/Fallback/FallbackResilienceStrategyTests.cs index 8415d82e19d..131496df358 100644 --- a/test/Polly.Core.Tests/Fallback/FallbackResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/Fallback/FallbackResilienceStrategyTests.cs @@ -6,7 +6,7 @@ namespace Polly.Core.Tests.Fallback; public class FallbackResilienceStrategyTests { private readonly FallbackStrategyOptions _options = new(); - private readonly List> _args = new(); + private readonly List> _args = []; private readonly ResilienceStrategyTelemetry _telemetry; private FallbackHandler? _handler; diff --git a/test/Polly.Core.Tests/GenericResiliencePipelineBuilderTests.cs b/test/Polly.Core.Tests/GenericResiliencePipelineBuilderTests.cs index 64d19efdff4..bfe69b5f8cc 100644 --- a/test/Polly.Core.Tests/GenericResiliencePipelineBuilderTests.cs +++ b/test/Polly.Core.Tests/GenericResiliencePipelineBuilderTests.cs @@ -12,7 +12,7 @@ public class GenericResiliencePipelineBuilderTests public void Ctor_EnsureDefaults() { _builder.Name.Should().BeNull(); - _builder.TimeProvider.Should().Be(TimeProvider.System); + _builder.TimeProvider.Should().BeNull(); } [Fact] diff --git a/test/Polly.Core.Tests/Hedging/Controller/HedgingExecutionContextTests.cs b/test/Polly.Core.Tests/Hedging/Controller/HedgingExecutionContextTests.cs index f00b23cc56c..e8081852ba9 100644 --- a/test/Polly.Core.Tests/Hedging/Controller/HedgingExecutionContextTests.cs +++ b/test/Polly.Core.Tests/Hedging/Controller/HedgingExecutionContextTests.cs @@ -14,9 +14,9 @@ public class HedgingExecutionContextTests : IDisposable private readonly ResiliencePropertyKey _myKey = new("my-key"); private readonly CancellationTokenSource _cts; private readonly HedgingTimeProvider _timeProvider; - private readonly List> _createdExecutions = new(); - private readonly List> _returnedExecutions = new(); - private readonly List> _resets = new(); + private readonly List> _createdExecutions = []; + private readonly List> _returnedExecutions = []; + private readonly List> _resets = []; private readonly ResilienceContext _resilienceContext; private readonly AutoResetEvent _onReset = new(false); private int _maxAttempts = 2; diff --git a/test/Polly.Core.Tests/Hedging/Controller/TaskExecutionTests.cs b/test/Polly.Core.Tests/Hedging/Controller/TaskExecutionTests.cs index 51c549fdbee..5e3bb507607 100644 --- a/test/Polly.Core.Tests/Hedging/Controller/TaskExecutionTests.cs +++ b/test/Polly.Core.Tests/Hedging/Controller/TaskExecutionTests.cs @@ -14,7 +14,7 @@ public class TaskExecutionTests : IDisposable private readonly CancellationTokenSource _cts; private readonly HedgingTimeProvider _timeProvider; private readonly ResilienceStrategyTelemetry _telemetry; - private readonly List _args = new(); + private readonly List _args = []; private ResilienceContext _primaryContext = ResilienceContextPool.Shared.Get(); public TaskExecutionTests() diff --git a/test/Polly.Core.Tests/Hedging/HedgingActions.cs b/test/Polly.Core.Tests/Hedging/HedgingActions.cs index e2267d250ab..37911f2e9b2 100644 --- a/test/Polly.Core.Tests/Hedging/HedgingActions.cs +++ b/test/Polly.Core.Tests/Hedging/HedgingActions.cs @@ -10,12 +10,12 @@ public HedgingActions(TimeProvider timeProvider) { _timeProvider = timeProvider; - Functions = new() - { + Functions = + [ GetApples, GetOranges, GetPears - }; + ]; Generator = args => { diff --git a/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs b/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs index 37b13d8633a..7e6365178eb 100644 --- a/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs @@ -155,7 +155,11 @@ public async Task ExecuteAsync_ShouldReturnAnyPossibleResult() var result = await strategy.ExecuteAsync(_primaryTasks.SlowTask); result.Should().NotBeNull(); +#if NET8_0_OR_GREATER + _timeProvider.TimerEntries.Should().HaveCount(8); +#else _timeProvider.TimerEntries.Should().HaveCount(5); +#endif result.Should().Be("Oranges"); } @@ -620,7 +624,7 @@ public async Task ExecuteAsync_EnsureBackgroundWorkInSuccessfulCallNotCancelled( { // arrange using var cts = new CancellationTokenSource(); - List backgroundTasks = new List(); + List backgroundTasks = []; ConfigureHedging(BackgroundWork); var strategy = Create(); diff --git a/test/Polly.Core.Tests/Hedging/HedgingTimeProvider.cs b/test/Polly.Core.Tests/Hedging/HedgingTimeProvider.cs index babb0998755..2b8ee973cb4 100644 --- a/test/Polly.Core.Tests/Hedging/HedgingTimeProvider.cs +++ b/test/Polly.Core.Tests/Hedging/HedgingTimeProvider.cs @@ -1,3 +1,5 @@ +using System; + namespace Polly.Core.Tests.Hedging; internal class HedgingTimeProvider : TimeProvider @@ -26,7 +28,13 @@ public void Advance(TimeSpan diff) public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period) { - var entry = new TimerEntry(dueTime, new TaskCompletionSource(), _utcNow.Add(dueTime), () => callback(state)); + var timeStamp = dueTime switch + { + _ when dueTime == global::System.Threading.Timeout.InfiniteTimeSpan => DateTimeOffset.MaxValue, + _ => _utcNow.Add(dueTime) + }; + + var entry = new TimerEntry(dueTime, new TaskCompletionSource(), timeStamp, () => callback(state)); TimerEntries.Enqueue(entry); Advance(AutoAdvance); diff --git a/test/Polly.Core.Tests/Hedging/PrimaryStringTasks.cs b/test/Polly.Core.Tests/Hedging/PrimaryStringTasks.cs index 7e3f095f734..926968b2a65 100644 --- a/test/Polly.Core.Tests/Hedging/PrimaryStringTasks.cs +++ b/test/Polly.Core.Tests/Hedging/PrimaryStringTasks.cs @@ -19,13 +19,22 @@ public static ValueTask InstantTask() public async ValueTask FastTask(CancellationToken token) { +#if NET8_0_OR_GREATER + await Task.Delay(TimeSpan.FromMilliseconds(10), _timeProvider, token); +#else await _timeProvider.Delay(TimeSpan.FromMilliseconds(10), token); +#endif return FastTaskResult; } public async ValueTask SlowTask(CancellationToken token) { +#if NET8_0_OR_GREATER + await Task.Delay(TimeSpan.FromDays(1), _timeProvider, token); +#else await _timeProvider.Delay(TimeSpan.FromDays(1), token); +#endif + return SlowTaskResult; } } diff --git a/test/Polly.Core.Tests/Helpers/FakeTimeProvider.cs b/test/Polly.Core.Tests/Helpers/FakeTimeProvider.cs deleted file mode 100644 index 6946ac944a6..00000000000 --- a/test/Polly.Core.Tests/Helpers/FakeTimeProvider.cs +++ /dev/null @@ -1,375 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#pragma warning disable - -// Replace with Microsoft.Extensions.TimeProvider.Testing when TimeProvider is used (see https://github.com/App-vNext/Polly/pull/1144) -// Based on https://github.com/dotnet/extensions/blob/14917b87e8fc81f10d44ceea52d9b24e50e26550/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/FakeTimeProvider.cs - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Threading; -using Microsoft.Extensions.Time.Testing; - -namespace Microsoft.Extensions.Time.Testing; - -/// -/// A synthetic time provider used to enable deterministic behavior in tests. -/// -internal class FakeTimeProvider : TimeProvider -{ - internal readonly HashSet Waiters = new(); - private DateTimeOffset _now = new(2000, 1, 1, 0, 0, 0, 0, TimeSpan.Zero); - private TimeZoneInfo _localTimeZone = TimeZoneInfo.Utc; - private int _wakeWaitersGate; - private TimeSpan _autoAdvanceAmount; - - /// - /// Initializes a new instance of the class. - /// - /// - /// This creates a provider whose time is initially set to midnight January 1st 2000. - /// The provider is set to not automatically advance time each time it is read. - /// - public FakeTimeProvider() - { - Start = _now; - } - - /// - /// Initializes a new instance of the class. - /// - /// The initial time and date reported by the provider. - /// - /// The provider is set to not automatically advance time each time it is read. - /// - public FakeTimeProvider(DateTimeOffset startDateTime) - { - _now = startDateTime; - Start = _now; - } - - /// - /// Gets the starting date and time for this provider. - /// - public DateTimeOffset Start { get; } - - /// - /// Gets or sets the amount of time by which time advances whenever the clock is read. - /// - /// - /// This defaults to . - /// - public TimeSpan AutoAdvanceAmount - { - get => _autoAdvanceAmount; - set - { - _autoAdvanceAmount = value; - } - } - - /// - public override DateTimeOffset GetUtcNow() - { - DateTimeOffset result; - - lock (Waiters) - { - result = _now; - _now += _autoAdvanceAmount; - } - - WakeWaiters(); - return result; - } - - /// - /// Sets the date and time in the UTC time zone. - /// - /// The date and time in the UTC time zone. - public void SetUtcNow(DateTimeOffset value) - { - lock (Waiters) - { - if (value < _now) - { - throw new ArgumentOutOfRangeException(nameof(value), $"Cannot go back in time. Current time is {_now}."); - } - - _now = value; - } - - WakeWaiters(); - } - - /// - /// Advances time by a specific amount. - /// - /// The amount of time to advance the clock by. - /// - /// Advancing time affects the timers created from this provider, and all other operations that are directly or - /// indirectly using this provider as a time source. Whereas when using , time - /// marches forward automatically in hardware, for the fake time provider the application is responsible for - /// doing this explicitly by calling this method. - /// - public void Advance(TimeSpan delta) - { - lock (Waiters) - { - _now += delta; - } - - WakeWaiters(); - } - - /// - public override long GetTimestamp() - { - // Notionally we're multiplying by frequency and dividing by ticks per second, - // which are the same value for us. Don't actually do the math as the full - // precision of ticks (a long) cannot be represented in a double during division. - // For test stability we want a reproducible result. - // - // The same issue could occur converting back, in GetElapsedTime(). Unfortunately - // that isn't virtual so we can't do the same trick. However, if tests advance - // the clock in multiples of 1ms or so this loss of precision will not be visible. - Debug.Assert(TimestampFrequency == TimeSpan.TicksPerSecond, "Assuming frequency equals ticks per second"); - return _now.Ticks; - } - - /// - public override TimeZoneInfo LocalTimeZone => _localTimeZone; - - /// - /// Sets the local time zone. - /// - /// The local time zone. - public void SetLocalTimeZone(TimeZoneInfo localTimeZone) => _localTimeZone = localTimeZone; - - /// - /// Gets the amount by which the value from increments per second. - /// - /// - /// This is fixed to the value of . - /// - public override long TimestampFrequency => TimeSpan.TicksPerSecond; - - /// - /// Returns a string representation this provider's idea of current time. - /// - /// A string representing the provider's current time. - public override string ToString() => GetUtcNow().ToString("yyyy-MM-ddTHH:mm:ss.fff", CultureInfo.InvariantCulture); - - /// - public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period) - { - var timer = new Timer(this, callback, state); - _ = timer.Change(dueTime, period); - return timer; - } - - internal void RemoveWaiter(Waiter waiter) - { - lock (Waiters) - { - _ = Waiters.Remove(waiter); - } - } - - internal void AddWaiter(Waiter waiter, long dueTime) - { - lock (Waiters) - { - waiter.ScheduledOn = _now.Ticks; - waiter.WakeupTime = _now.Ticks + dueTime; - _ = Waiters.Add(waiter); - } - - WakeWaiters(); - } - - private void WakeWaiters() - { - if (Interlocked.CompareExchange(ref _wakeWaitersGate, 1, 0) == 1) - { - // some other thread is already in here, so let it take care of things - return; - } - - while (true) - { - Waiter? candidate = null; - lock (Waiters) - { - // find an expired waiter - foreach (var waiter in Waiters) - { - if (waiter.WakeupTime > _now.Ticks) - { - // not expired yet - } - else if (candidate is null) - { - // our first candidate - candidate = waiter; - } - else if (waiter.WakeupTime < candidate.WakeupTime) - { - // found a waiter with an earlier wake time, it's our new candidate - candidate = waiter; - } - else if (waiter.WakeupTime > candidate.WakeupTime) - { - // the waiter has a later wake time, so keep the current candidate - } - else if (waiter.ScheduledOn < candidate.ScheduledOn) - { - // the new waiter has the same wake time aa the candidate, pick whichever was scheduled earliest to maintain order - candidate = waiter; - } - } - } - - if (candidate == null) - { - // didn't find a candidate to wake, we're done - _wakeWaitersGate = 0; - return; - } - - // invoke the callback - candidate.InvokeCallback(); - - // see if we need to reschedule the waiter - if (candidate.Period > 0) - { - // update the waiter's state - candidate.ScheduledOn = _now.Ticks; - candidate.WakeupTime += candidate.Period; - } - else - { - // this waiter is never running again, so remove from the set. - RemoveWaiter(candidate); - } - } - } -} - -// We keep all timer state here in order to prevent Timer instances from being self-referential, -// which would block them being collected when someone forgets to call Dispose on the timer. With -// this arrangement, the Timer object will always be collectible, which will end up calling Dispose -// on this object due to the timer's finalizer. -internal sealed class Waiter -{ - private readonly TimerCallback _callback; - private readonly object? _state; - - public long ScheduledOn { get; set; } = -1; - public long WakeupTime { get; set; } = -1; - public long Period { get; } - - public Waiter(TimerCallback callback, object? state, long period) - { - _callback = callback; - _state = state; - Period = period; - } - - public void InvokeCallback() - { - _callback(_state); - } -} - -// This implements the timer abstractions and is a thin wrapper around a waiter object. -// The main role of this type is to create the waiter, add it to the waiter list, and ensure it gets -// removed from the waiter list when the dispose is disposed or collected. -internal sealed class Timer : ITimer -{ - private const uint MaxSupportedTimeout = 0xfffffffe; - - private Waiter? _waiter; - private FakeTimeProvider? _timeProvider; - private TimerCallback? _callback; - private object? _state; - - public Timer(FakeTimeProvider timeProvider, TimerCallback callback, object? state) - { - _timeProvider = timeProvider; - _callback = callback; - _state = state; - } - - public bool Change(TimeSpan dueTime, TimeSpan period) - { - var dueTimeMs = (long)dueTime.TotalMilliseconds; - var periodMs = (long)period.TotalMilliseconds; - - if (_timeProvider == null) - { - // timer has been disposed - return false; - } - - if (_waiter != null) - { - // remove any previous waiter - _timeProvider.RemoveWaiter(_waiter); - _waiter = null; - } - - if (dueTimeMs < 0) - { - // this waiter will never wake up, so just bail - return true; - } - - if (periodMs < 0 || periodMs == Timeout.Infinite) - { - // normalize - period = TimeSpan.Zero; - } - - _waiter = new Waiter(_callback!, _state, period.Ticks); - _timeProvider.AddWaiter(_waiter, dueTime.Ticks); - return true; - } - - // In case the timer is not disposed, this will remove the Waiter instance from the provider. - ~Timer() => Dispose(false); - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public ValueTask DisposeAsync() - { - Dispose(true); - GC.SuppressFinalize(this); -#if NET5_0_OR_GREATER - return ValueTask.CompletedTask; -#else - return default; -#endif - } - - private void Dispose(bool _) - { - if (_waiter != null) - { - _timeProvider!.RemoveWaiter(_waiter); - _waiter = null; - } - - _timeProvider = null; - _callback = null; - _state = null; - } -} diff --git a/test/Polly.Core.Tests/Polly.Core.Tests.csproj b/test/Polly.Core.Tests/Polly.Core.Tests.csproj index 51e78af5d4b..3a56ef265ad 100644 --- a/test/Polly.Core.Tests/Polly.Core.Tests.csproj +++ b/test/Polly.Core.Tests/Polly.Core.Tests.csproj @@ -1,15 +1,17 @@  - net7.0;net6.0 + net8.0;net7.0;net6.0 $(TargetFrameworks);net481 Test enable 100 $(NoWarn);SA1600;SA1204;SA1602;S6608 [Polly.Core]* + true + diff --git a/test/Polly.Core.Tests/ResiliencePipelineBuilderTests.cs b/test/Polly.Core.Tests/ResiliencePipelineBuilderTests.cs index db3ed78c984..43e1b48acd5 100644 --- a/test/Polly.Core.Tests/ResiliencePipelineBuilderTests.cs +++ b/test/Polly.Core.Tests/ResiliencePipelineBuilderTests.cs @@ -16,7 +16,19 @@ public void Ctor_EnsureDefaults() var builder = new ResiliencePipelineBuilder(); builder.Name.Should().BeNull(); - builder.TimeProvider.Should().Be(TimeProvider.System); + builder.TimeProvider.Should().BeNull(); + } + + [Fact] + public void TimeProviderInternal_Ok() + { + var builder = new ResiliencePipelineBuilder(); + builder.TimeProviderInternal.Should().Be(TimeProvider.System); + + var timeProvider = Substitute.For(); + builder.TimeProvider = timeProvider; + + builder.TimeProvider.Should().Be(timeProvider); } [Fact] diff --git a/test/Polly.Core.Tests/ResiliencePipelineTTests.Async.cs b/test/Polly.Core.Tests/ResiliencePipelineTTests.Async.cs index 125548504d6..c22471a427d 100644 --- a/test/Polly.Core.Tests/ResiliencePipelineTTests.Async.cs +++ b/test/Polly.Core.Tests/ResiliencePipelineTTests.Async.cs @@ -3,10 +3,9 @@ namespace Polly.Core.Tests; -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public partial class ResiliencePipelineTests { +#pragma warning disable IDE0028 public static TheoryData, ValueTask>> ExecuteAsyncGenericStrategyData = new() { async strategy => @@ -59,8 +58,9 @@ public partial class ResiliencePipelineTests return new ValueTask("res"); }, context)).Should().Be("res"); - } + }, }; +#pragma warning restore IDE0028 [MemberData(nameof(ExecuteAsyncGenericStrategyData))] [Theory] diff --git a/test/Polly.Core.Tests/ResiliencePipelineTTests.Sync.cs b/test/Polly.Core.Tests/ResiliencePipelineTTests.Sync.cs index 60dc613c269..d242e3aa62a 100644 --- a/test/Polly.Core.Tests/ResiliencePipelineTTests.Sync.cs +++ b/test/Polly.Core.Tests/ResiliencePipelineTTests.Sync.cs @@ -5,6 +5,7 @@ namespace Polly.Core.Tests; public partial class ResiliencePipelineTests { +#pragma warning disable IDE0028 public static TheoryData>> ExecuteGenericStrategyData = new() { strategy => @@ -73,8 +74,9 @@ public partial class ResiliencePipelineTests return "res"; }, context).Should().Be("res"); - } + }, }; +#pragma warning restore IDE0028 [MemberData(nameof(ExecuteGenericStrategyData))] [Theory] diff --git a/test/Polly.Core.Tests/ResiliencePipelineTests.cs b/test/Polly.Core.Tests/ResiliencePipelineTests.cs index 95d4cc939fe..f12ae724c32 100644 --- a/test/Polly.Core.Tests/ResiliencePipelineTests.cs +++ b/test/Polly.Core.Tests/ResiliencePipelineTests.cs @@ -4,12 +4,18 @@ namespace Polly.Core.Tests; -#pragma warning disable S3966 // Objects should not be disposed more than once - public partial class ResiliencePipelineTests { public static readonly CancellationToken CancellationToken = new CancellationTokenSource().Token; +#pragma warning disable IDE0028 + public static TheoryData ResilienceContextPools = new() + { + null, + ResilienceContextPool.Shared, + }; +#pragma warning restore IDE0028 + [Fact] public async Task DisposeAsync_NullPipeline_OK() { diff --git a/test/Polly.Core.Tests/Retry/RetryResiliencePipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Retry/RetryResiliencePipelineBuilderExtensionsTests.cs index f73886985ba..51801b11bf7 100644 --- a/test/Polly.Core.Tests/Retry/RetryResiliencePipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Retry/RetryResiliencePipelineBuilderExtensionsTests.cs @@ -5,10 +5,9 @@ namespace Polly.Core.Tests.Retry; -#pragma warning disable CA2012 // Use ValueTasks correctly - public class RetryResiliencePipelineBuilderExtensionsTests { +#pragma warning disable IDE0028 public static readonly TheoryData> OverloadsData = new() { builder => @@ -22,7 +21,7 @@ public class RetryResiliencePipelineBuilderExtensionsTests }); AssertStrategy(builder, DelayBackoffType.Exponential, 3, TimeSpan.FromSeconds(2)); - } + }, }; public static readonly TheoryData>> OverloadsDataGeneric = new() @@ -38,8 +37,9 @@ public class RetryResiliencePipelineBuilderExtensionsTests }); AssertStrategy(builder, DelayBackoffType.Exponential, 3, TimeSpan.FromSeconds(2)); - } + }, }; +#pragma warning restore IDE0028 [MemberData(nameof(OverloadsData))] [Theory] diff --git a/test/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs b/test/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs index d0727bda922..0a94d396896 100644 --- a/test/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs @@ -10,7 +10,7 @@ public class RetryResilienceStrategyTests { private readonly RetryStrategyOptions _options = new(); private readonly FakeTimeProvider _timeProvider = new(); - private readonly List> _args = new(); + private readonly List> _args = []; private ResilienceStrategyTelemetry _telemetry; public RetryResilienceStrategyTests() diff --git a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs index 64947e22a85..aa58aaa7d0f 100644 --- a/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Behavior/BehaviorChaosStrategyTests.cs @@ -2,11 +2,12 @@ using Polly.Telemetry; namespace Polly.Core.Tests.Simmy.Behavior; + public class BehaviorChaosStrategyTests { private readonly ResilienceStrategyTelemetry _telemetry; private readonly BehaviorStrategyOptions _options; - private readonly List> _args = new(); + private readonly List> _args = []; public BehaviorChaosStrategyTests() { diff --git a/test/Polly.Core.Tests/Simmy/Fault/FaultChaosPipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Fault/FaultChaosPipelineBuilderExtensionsTests.cs index 17fab140a8c..34f4408b7e2 100644 --- a/test/Polly.Core.Tests/Simmy/Fault/FaultChaosPipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Fault/FaultChaosPipelineBuilderExtensionsTests.cs @@ -7,6 +7,7 @@ namespace Polly.Core.Tests.Simmy.Fault; public class FaultChaosPipelineBuilderExtensionsTests { +#pragma warning disable IDE0028 public static readonly TheoryData> FaultStrategy = new() { builder => @@ -21,8 +22,9 @@ public class FaultChaosPipelineBuilderExtensionsTests AssertFaultStrategy(builder, true, 0.6) .Fault.Should().BeOfType(typeof(InvalidOperationException)); - } + }, }; +#pragma warning restore IDE0028 private static void AssertFaultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate) where TException : Exception diff --git a/test/Polly.Core.Tests/Simmy/Fault/FaultChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Fault/FaultChaosStrategyTests.cs index c7014e1a5ce..1a076c6791e 100644 --- a/test/Polly.Core.Tests/Simmy/Fault/FaultChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Fault/FaultChaosStrategyTests.cs @@ -7,7 +7,7 @@ namespace Polly.Core.Tests.Simmy.Fault; public class FaultChaosStrategyTests { private readonly ResilienceStrategyTelemetry _telemetry; - private readonly List> _args = new(); + private readonly List> _args = []; public FaultChaosStrategyTests() => _telemetry = TestUtilities.CreateResilienceTelemetry(arg => _args.Add(arg)); diff --git a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs index 10b733b4f52..361ab2d75ba 100644 --- a/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Latency/LatencyChaosStrategyTests.cs @@ -11,7 +11,7 @@ public class LatencyChaosStrategyTests : IDisposable private readonly LatencyStrategyOptions _options; private readonly CancellationTokenSource _cancellationSource; private readonly TimeSpan _delay = TimeSpan.FromMilliseconds(500); - private readonly List> _args = new(); + private readonly List> _args = []; public LatencyChaosStrategyTests() { diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs index 946b2a442f8..bfc56cfb9e2 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosPipelineBuilderExtensionsTests.cs @@ -7,6 +7,7 @@ namespace Polly.Core.Tests.Simmy.Outcomes; public class OutcomeChaosPipelineBuilderExtensionsTests { +#pragma warning disable IDE0028 public static readonly TheoryData>> ResultStrategy = new() { builder => @@ -20,8 +21,9 @@ public class OutcomeChaosPipelineBuilderExtensionsTests }); AssertResultStrategy(builder, true, 0.6, new(100)); - } + }, }; +#pragma warning restore IDE0028 private static void AssertResultStrategy(ResiliencePipelineBuilder builder, bool enabled, double injectionRate, Outcome outcome) { diff --git a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs index 7a7e8fb631e..ada7f9e8a1a 100644 --- a/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs +++ b/test/Polly.Core.Tests/Simmy/Outcomes/OutcomeChaosStrategyTests.cs @@ -6,7 +6,7 @@ namespace Polly.Core.Tests.Simmy.Outcomes; public class OutcomeChaosStrategyTests { private readonly ResilienceStrategyTelemetry _telemetry; - private readonly List> _args = new(); + private readonly List> _args = []; public OutcomeChaosStrategyTests() => _telemetry = TestUtilities.CreateResilienceTelemetry(arg => _args.Add(arg)); diff --git a/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs b/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs index fb2d56f1154..90b58338be1 100644 --- a/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs +++ b/test/Polly.Core.Tests/Telemetry/ResilienceStrategyTelemetryTests.cs @@ -4,7 +4,7 @@ namespace Polly.Core.Tests.Telemetry; public class ResilienceStrategyTelemetryTests { - private readonly List> _args = new(); + private readonly List> _args = []; private readonly ResilienceTelemetrySource _source; private readonly ResilienceStrategyTelemetry _sut; diff --git a/test/Polly.Core.Tests/Timeout/TimeoutResilienceStrategyTests.cs b/test/Polly.Core.Tests/Timeout/TimeoutResilienceStrategyTests.cs index 7bebeb49bcd..089d1b09ff8 100644 --- a/test/Polly.Core.Tests/Timeout/TimeoutResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/Timeout/TimeoutResilienceStrategyTests.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using Microsoft.Extensions.Time.Testing; using NSubstitute; using Polly.Telemetry; @@ -12,7 +13,7 @@ public class TimeoutResilienceStrategyTests : IDisposable private readonly TimeoutStrategyOptions _options; private readonly CancellationTokenSource _cancellationSource; private readonly TimeSpan _delay = TimeSpan.FromSeconds(12); - private readonly List> _args = new(); + private readonly List> _args = []; public TimeoutResilienceStrategyTests() { @@ -120,18 +121,24 @@ public async Task Execute_Timeout_EnsureStackTrace() SetTimeout(TimeSpan.FromSeconds(2)); var sut = CreateSut(); - var outcome = await sut.ExecuteOutcomeAsync(async (c, _) => - { - var delay = _timeProvider.Delay(TimeSpan.FromSeconds(4), c.CancellationToken); - _timeProvider.Advance(TimeSpan.FromSeconds(2)); - await delay; + var outcome = await sut.ExecuteOutcomeAsync( + async (c, _) => + { + var delay = _timeProvider.Delay(TimeSpan.FromSeconds(4), c.CancellationToken); + _timeProvider.Advance(TimeSpan.FromSeconds(2)); + await delay; + + return Outcome.FromResult("dummy"); + }, + ResilienceContextPool.Shared.Get(), + "state"); - return Outcome.FromResult("dummy"); - }, - ResilienceContextPool.Shared.Get(), - "state"); outcome.Exception.Should().BeOfType(); - outcome.Exception!.StackTrace.Should().Contain("Execute_Timeout_EnsureStackTrace"); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + outcome.Exception!.StackTrace.Should().NotBeEmpty(); + } } [Fact] diff --git a/test/Polly.Core.Tests/Timeout/TimeoutTestUtils.cs b/test/Polly.Core.Tests/Timeout/TimeoutTestUtils.cs index 302cdc8be4a..ce037d8cf8c 100644 --- a/test/Polly.Core.Tests/Timeout/TimeoutTestUtils.cs +++ b/test/Polly.Core.Tests/Timeout/TimeoutTestUtils.cs @@ -8,6 +8,7 @@ public static class TimeoutTestUtils public static TimeoutGeneratorArguments TimeoutGeneratorArguments() => new(ResilienceContextPool.Shared.Get()); +#pragma warning disable IDE0028 public static readonly TheoryData InvalidTimeouts = new() { TimeSpan.MinValue, @@ -21,4 +22,5 @@ public static class TimeoutTestUtils TimeSpan.FromSeconds(1), TimeSpan.FromHours(1), }; +#pragma warning restore IDE0028 } diff --git a/test/Polly.Core.Tests/Utils/Pipeline/PipelineComponentFactoryTests.cs b/test/Polly.Core.Tests/Utils/Pipeline/PipelineComponentFactoryTests.cs index 5beed7f1567..1a23c41ec23 100644 --- a/test/Polly.Core.Tests/Utils/Pipeline/PipelineComponentFactoryTests.cs +++ b/test/Polly.Core.Tests/Utils/Pipeline/PipelineComponentFactoryTests.cs @@ -5,22 +5,63 @@ namespace Polly.Core.Tests.Utils.Pipeline; public class PipelineComponentFactoryTests { - [Fact] - public void WithDisposableCallbacks_NoCallbacks_ReturnsOriginalComponent() +#pragma warning disable IDE0028 + public static TheoryData> EmptyCallbacks = new() + { + Array.Empty(), + Enumerable.Empty(), + new List(), + new EmptyActionEnumerable(), // Explicitly does not provide TryGetNonEnumeratedCount() + }; + + public static TheoryData> NonEmptyCallbacks = new() + { + new[] { () => { } }, + Enumerable.TakeWhile(Enumerable.Repeat(() => { }, 50), (_, i) => i < 1), // Defeat optimisation for TryGetNonEnumeratedCount() + new List { () => { } }, + }; +#pragma warning restore IDE0028 + + [Theory] + [MemberData(nameof(EmptyCallbacks))] + public void WithDisposableCallbacks_NoCallbacks_ReturnsOriginalComponent(IEnumerable callbacks) { var component = Substitute.For(); - var result = PipelineComponentFactory.WithDisposableCallbacks(component, new List()); + var result = PipelineComponentFactory.WithDisposableCallbacks(component, callbacks); result.Should().BeSameAs(component); } - [Fact] - public void PipelineComponentFactory_Should_Return_WrapperComponent_With_Callbacks() + [Theory] + [MemberData(nameof(NonEmptyCallbacks))] + public void PipelineComponentFactory_Should_Return_WrapperComponent_With_Callbacks(IEnumerable callbacks) { var component = Substitute.For(); - var callbacks = new List { () => { } }; var result = PipelineComponentFactory.WithDisposableCallbacks(component, callbacks); result.Should().BeOfType(); } + + private sealed class EmptyActionEnumerable : IEnumerable, IEnumerator + { + public Action Current => null!; + + object IEnumerator.Current => null!; + + public void Dispose() + { + // No-op + } + + public IEnumerator GetEnumerator() => this; + + public bool MoveNext() => false; + + public void Reset() + { + // No-op + } + + IEnumerator IEnumerable.GetEnumerator() => this; + } } diff --git a/test/Polly.Core.Tests/Utils/Pipeline/ReloadablePipelineComponentTests.cs b/test/Polly.Core.Tests/Utils/Pipeline/ReloadablePipelineComponentTests.cs index 371e4d88219..b60d1e17110 100644 --- a/test/Polly.Core.Tests/Utils/Pipeline/ReloadablePipelineComponentTests.cs +++ b/test/Polly.Core.Tests/Utils/Pipeline/ReloadablePipelineComponentTests.cs @@ -8,7 +8,7 @@ public class ReloadablePipelineComponentTests : IDisposable { private readonly FakeTelemetryListener _listener; private readonly ResilienceStrategyTelemetry _telemetry; - private readonly List _synchronousFlags = new(); + private readonly List _synchronousFlags = []; private CancellationTokenSource _cancellationTokenSource; public ReloadablePipelineComponentTests() @@ -56,7 +56,7 @@ public async Task ChangeTriggered_EnsureOldStrategyDisposed() { var telemetry = TestUtilities.CreateResilienceTelemetry(_listener); var component = Substitute.For(); - await using var sut = CreateSut(component, () => new(Substitute.For(), new List(), telemetry)); + await using var sut = CreateSut(component, () => new(Substitute.For(), [], telemetry)); for (var i = 0; i < 10; i++) { @@ -131,10 +131,10 @@ public async Task DisposeError_EnsureReported() private ReloadableComponent CreateSut(PipelineComponent? initial = null, Func? factory = null) { - factory ??= () => new ReloadableComponent.Entry(PipelineComponent.Empty, new List(), _telemetry); + factory ??= () => new ReloadableComponent.Entry(PipelineComponent.Empty, [], _telemetry); return (ReloadableComponent)PipelineComponentFactory.CreateReloadable( - new ReloadableComponent.Entry(initial ?? PipelineComponent.Empty, new List { _cancellationTokenSource.Token }, _telemetry), + new ReloadableComponent.Entry(initial ?? PipelineComponent.Empty, [_cancellationTokenSource.Token], _telemetry), factory); } diff --git a/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs b/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs index e149792fbf1..4179e5932d8 100644 --- a/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs +++ b/test/Polly.Extensions.Tests/DependencyInjection/PollyServiceCollectionExtensionTests.cs @@ -1,8 +1,10 @@ using System.Globalization; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using NSubstitute; using Polly.DependencyInjection; using Polly.Registry; using Polly.Telemetry; @@ -14,7 +16,7 @@ public class PollyServiceCollectionExtensionTests private const string Key = "my-pipeline"; private ServiceCollection _services; - public PollyServiceCollectionExtensionTests() => _services = new ServiceCollection(); + public PollyServiceCollectionExtensionTests() => _services = []; [Fact] public void AddResiliencePipeline_ArgValidation() @@ -23,7 +25,7 @@ public void AddResiliencePipeline_ArgValidation() Assert.Throws(() => AddResiliencePipeline(Key)); Assert.Throws(() => AddResiliencePipeline(Key)); - _services = new ServiceCollection(); + _services = []; Assert.Throws(() => _services.AddResiliencePipeline( Key, (Action>)null!)); @@ -250,6 +252,37 @@ public void AddResiliencePipeline_Multiple_Ok() .HaveCount(30); } + [InlineData(true)] + [InlineData(false)] + [Theory] + public void AddResiliencePipeline_EnsureTimeProvider(bool timeProviderRegistered) + { + var timeProvider = Substitute.For(); + var asserted = false; + + if (timeProviderRegistered) + { + _services.TryAddSingleton(timeProvider); + } + + _services.AddResiliencePipeline("dummy", builder => + { + if (timeProviderRegistered) + { + builder.TimeProvider.Should().Be(timeProvider); + } + else + { + builder.TimeProvider.Should().BeNull(); + } + + asserted = true; + }); + + CreateProvider().GetPipeline("dummy"); + asserted.Should().BeTrue(); + } + [Fact] public void AddResiliencePipelineRegistry_Ok() { diff --git a/test/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj b/test/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj index 0dbfcf24307..b33283d0398 100644 --- a/test/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj +++ b/test/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj @@ -1,6 +1,6 @@  - net7.0;net6.0 + net8.0;net7.0;net6.0 $(TargetFrameworks);net481 Test enable diff --git a/test/Polly.Extensions.Tests/Telemetry/TelemetryListenerImplTests.cs b/test/Polly.Extensions.Tests/Telemetry/TelemetryListenerImplTests.cs index b6b5a387638..bc86c98e304 100644 --- a/test/Polly.Extensions.Tests/Telemetry/TelemetryListenerImplTests.cs +++ b/test/Polly.Extensions.Tests/Telemetry/TelemetryListenerImplTests.cs @@ -12,7 +12,7 @@ public class TelemetryListenerImplTests : IDisposable { private readonly FakeLogger _logger; private readonly ILoggerFactory _loggerFactory; - private readonly List _events = new(); + private readonly List _events = []; private Action>? _onEvent; public TelemetryListenerImplTests() => _loggerFactory = TestUtilities.CreateLoggerFactory(out _logger); diff --git a/test/Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj b/test/Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj index 002a146ce14..412e2580fb0 100644 --- a/test/Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj +++ b/test/Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj @@ -1,6 +1,6 @@  - net7.0;net6.0 + net8.0;net7.0;net6.0 $(TargetFrameworks);net481 Test enable diff --git a/test/Polly.RateLimiting.Tests/RateLimiterResiliencePipelineBuilderExtensionsTests.cs b/test/Polly.RateLimiting.Tests/RateLimiterResiliencePipelineBuilderExtensionsTests.cs index 9cf0f4f404a..60326769100 100644 --- a/test/Polly.RateLimiting.Tests/RateLimiterResiliencePipelineBuilderExtensionsTests.cs +++ b/test/Polly.RateLimiting.Tests/RateLimiterResiliencePipelineBuilderExtensionsTests.cs @@ -9,6 +9,7 @@ namespace Polly.RateLimiting.Tests; public class RateLimiterResiliencePipelineBuilderExtensionsTests { +#pragma warning disable IDE0028 public static readonly TheoryData> Data = new() { builder => @@ -41,6 +42,7 @@ public class RateLimiterResiliencePipelineBuilderExtensionsTests AssertRateLimiterStrategy(builder, strategy => strategy.Wrapper.Should().BeNull()); } }; +#pragma warning restore IDE0028 [MemberData(nameof(Data))] [Theory(Skip = "https://github.com/stryker-mutator/stryker-net/issues/2144")] diff --git a/test/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs b/test/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs index 73c234a9493..42911a81b9f 100644 --- a/test/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs +++ b/test/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerAsyncSpecs.cs @@ -2358,7 +2358,7 @@ await breaker.Awaiting(x => x.RaiseExceptionAsync()) [Fact] public async Task Should_call_onbreak_with_a_state_of_half_open() { - List transitionedStates = new List(); + List transitionedStates = []; Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; Action onReset = _ => { }; diff --git a/test/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs b/test/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs index d65c1c59412..d2140938f8b 100644 --- a/test/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs +++ b/test/Polly.Specs/CircuitBreaker/AdvancedCircuitBreakerSpecs.cs @@ -1601,7 +1601,7 @@ public void Should_only_allow_single_execution_on_first_entering_halfopen_state_ permitFirstExecutionEnd.Set(); #pragma warning disable xUnit1031 // Do not use blocking task operations in test method - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + Task.WaitAll([firstExecution, secondExecution], testTimeoutToExposeDeadlocks).Should().BeTrue(); #pragma warning restore xUnit1031 // Do not use blocking task operations in test method if (firstExecution.IsFaulted) @@ -1713,7 +1713,7 @@ public void Should_allow_single_execution_per_break_duration_in_halfopen_state__ permitFirstExecutionEnd.Set(); #pragma warning disable xUnit1031 // Do not use blocking task operations in test method - Task.WaitAll(new[] { firstExecution, secondExecution }, testTimeoutToExposeDeadlocks).Should().BeTrue(); + Task.WaitAll([firstExecution, secondExecution], testTimeoutToExposeDeadlocks).Should().BeTrue(); #pragma warning restore xUnit1031 // Do not use blocking task operations in test method if (firstExecution.IsFaulted) @@ -2032,8 +2032,12 @@ public void Should_call_onbreak_when_breaking_circuit_first_time_but_not_for_sub #pragma warning disable xUnit1031 // Do not use blocking task operations in test method longRunningExecution.Wait(testTimeoutToExposeDeadlocks).Should().BeTrue(); #pragma warning restore xUnit1031 // Do not use blocking task operations in test method + if (longRunningExecution.IsFaulted) + { throw longRunningExecution!.Exception!; + } + longRunningExecution.Status.Should().Be(TaskStatus.RanToCompletion); // onBreak() should still only have been called once. @@ -2355,7 +2359,7 @@ public void Should_call_onbreak_with_a_state_of_closed() [Fact] public void Should_call_onbreak_with_a_state_of_half_open() { - List transitionedStates = new List(); + List transitionedStates = []; Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; Action onReset = _ => { }; diff --git a/test/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs b/test/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs index 571c66005ee..0a46d9ee986 100644 --- a/test/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs +++ b/test/Polly.Specs/CircuitBreaker/CircuitBreakerAsyncSpecs.cs @@ -1074,7 +1074,7 @@ await breaker.Awaiting(x => x.RaiseExceptionAsync()) [Fact] public async Task Should_call_onbreak_with_a_state_of_half_open() { - List transitionedStates = new List(); + List transitionedStates = []; Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; Action onReset = _ => { }; diff --git a/test/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs b/test/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs index 4aab6a90879..ba15f042984 100644 --- a/test/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs +++ b/test/Polly.Specs/CircuitBreaker/CircuitBreakerSpecs.cs @@ -1067,7 +1067,7 @@ public void Should_call_onbreak_with_a_state_of_closed() [Fact] public void Should_call_onbreak_with_a_state_of_half_open() { - List transitionedStates = new List(); + List transitionedStates = []; Action onBreak = (_, state, _, _) => { transitionedStates.Add(state); }; Action onReset = _ => { }; diff --git a/test/Polly.Specs/ContextSpecs.cs b/test/Polly.Specs/ContextSpecs.cs index f4709e8fc2b..4c3ec4f8093 100644 --- a/test/Polly.Specs/ContextSpecs.cs +++ b/test/Polly.Specs/ContextSpecs.cs @@ -25,7 +25,7 @@ public void Should_assign_OperationKey_and_context_data_from_constructor() [Fact] public void NoArgsCtor_should_assign_no_OperationKey() { - Context context = new Context(); + Context context = []; context.OperationKey.Should().BeNull(); } diff --git a/test/Polly.Specs/Helpers/Caching/StubCacheProvider.cs b/test/Polly.Specs/Helpers/Caching/StubCacheProvider.cs index 0c80c611f5f..dce2456be56 100644 --- a/test/Polly.Specs/Helpers/Caching/StubCacheProvider.cs +++ b/test/Polly.Specs/Helpers/Caching/StubCacheProvider.cs @@ -17,7 +17,7 @@ public CacheItem(object? value, Ttl ttl) public readonly object? Value; } - private readonly Dictionary cachedValues = new(); + private readonly Dictionary cachedValues = []; public (bool, object?) TryGet(string key) { diff --git a/test/Polly.Specs/Polly.Specs.csproj b/test/Polly.Specs/Polly.Specs.csproj index 9f173f8b1d6..43cf95ff43e 100644 --- a/test/Polly.Specs/Polly.Specs.csproj +++ b/test/Polly.Specs/Polly.Specs.csproj @@ -1,7 +1,7 @@  - net6.0;net7.0 + net6.0;net7.0;net8.0 $(TargetFrameworks);net481 enable Test diff --git a/test/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs b/test/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs index 4594098feee..44bd8acbbaa 100644 --- a/test/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs +++ b/test/Polly.Specs/RateLimit/RateLimitPolicyTResultSpecsBase.cs @@ -35,7 +35,7 @@ public void Ratelimiter_specifies_correct_wait_until_next_execution_by_custom_fa // (do nothing - time not advanced) // Act - try another execution. - Context contextToPassIn = new Context(); + Context contextToPassIn = []; var resultExpectedBlocked = TryExecuteThroughPolicy(rateLimiter, contextToPassIn, new ResultClassWithRetryAfter(ResultPrimitive.Good)); // Assert - should be blocked - time not advanced. diff --git a/test/Polly.TestUtils/FakeLogger.cs b/test/Polly.TestUtils/FakeLogger.cs index 22bbc7e1e00..75e54555587 100644 --- a/test/Polly.TestUtils/FakeLogger.cs +++ b/test/Polly.TestUtils/FakeLogger.cs @@ -6,7 +6,7 @@ namespace Polly.TestUtils; public class FakeLogger : ILogger { - private readonly List _records = new(); + private readonly List _records = []; public bool Enabled { get; set; } = true; diff --git a/test/Polly.TestUtils/Polly.TestUtils.csproj b/test/Polly.TestUtils/Polly.TestUtils.csproj index 85f854e91bb..66f550f0165 100644 --- a/test/Polly.TestUtils/Polly.TestUtils.csproj +++ b/test/Polly.TestUtils/Polly.TestUtils.csproj @@ -1,6 +1,6 @@  - net7.0;net6.0 + net8.0;net7.0;net6.0 $(TargetFrameworks);net481 Library enable diff --git a/test/Polly.Testing.Tests/Polly.Testing.Tests.csproj b/test/Polly.Testing.Tests/Polly.Testing.Tests.csproj index 2469343b97e..7fd94fffdc4 100644 --- a/test/Polly.Testing.Tests/Polly.Testing.Tests.csproj +++ b/test/Polly.Testing.Tests/Polly.Testing.Tests.csproj @@ -1,6 +1,6 @@  - net7.0;net6.0 + net8.0;net7.0;net6.0 $(TargetFrameworks);net481 Test enable